platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
84
selective-vpn-api/app/transport_singbox_profiles_history.go
Normal file
84
selective-vpn-api/app/transport_singbox_profiles_history.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
transportcfgpkg "selective-vpn-api/app/transportcfg"
|
||||
)
|
||||
|
||||
func appendSingBoxHistory(rec singBoxProfileHistoryRecord) error {
|
||||
profileID := sanitizeID(rec.ProfileID)
|
||||
if profileID == "" {
|
||||
return fmt.Errorf("empty profile id")
|
||||
}
|
||||
rec.ProfileID = profileID
|
||||
if strings.TrimSpace(rec.At) == "" {
|
||||
rec.At = time.Now().UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
if strings.TrimSpace(rec.ID) == "" {
|
||||
rec.ID = "h-" + newTransportToken(8)
|
||||
}
|
||||
if rec.Action == "" {
|
||||
rec.Action = "unknown"
|
||||
}
|
||||
if rec.Status == "" {
|
||||
rec.Status = "success"
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(rec, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stamp := transportcfgpkg.SanitizeHistoryStamp(rec.At, time.Now().UTC())
|
||||
fileName := fmt.Sprintf("%s-%s-%s-%s.json", stamp, rec.ProfileID, rec.Action, rec.ID)
|
||||
return transportcfgpkg.WriteFileAtomic(filepath.Join(singBoxHistoryRootDir, fileName), append(data, '\n'), 0o644)
|
||||
}
|
||||
|
||||
func loadSingBoxHistoryRecords(profileID string, limit int) ([]singBoxProfileHistoryRecord, error) {
|
||||
rawRecords, err := transportcfgpkg.ReadJSONFiles(singBoxHistoryRootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id := sanitizeID(profileID)
|
||||
out := make([]singBoxProfileHistoryRecord, 0, len(rawRecords))
|
||||
for _, data := range rawRecords {
|
||||
var rec singBoxProfileHistoryRecord
|
||||
if err := json.Unmarshal(data, &rec); err != nil {
|
||||
continue
|
||||
}
|
||||
if sanitizeID(rec.ProfileID) != id {
|
||||
continue
|
||||
}
|
||||
out = append(out, rec)
|
||||
}
|
||||
transportcfgpkg.SortRecordsDescByAt(out, func(rec singBoxProfileHistoryRecord) string { return rec.At })
|
||||
if limit > 0 && len(out) > limit {
|
||||
out = out[:limit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func selectSingBoxRollbackCandidate(records []singBoxProfileHistoryRecord, historyID string) (singBoxProfileHistoryRecord, bool) {
|
||||
return transportcfgpkg.SelectRecordCandidate(
|
||||
records,
|
||||
historyID,
|
||||
func(rec singBoxProfileHistoryRecord) string { return rec.ID },
|
||||
func(rec singBoxProfileHistoryRecord) bool { return rec.Action == "apply" && rec.Status == "success" },
|
||||
)
|
||||
}
|
||||
|
||||
func decodeHistoryPrevConfig(rec singBoxProfileHistoryRecord) ([]byte, bool, error) {
|
||||
return transportcfgpkg.DecodeBase64Optional(rec.PrevConfigB64, rec.PrevConfigExist)
|
||||
}
|
||||
|
||||
func joinSingBoxIssueMessages(issues []SingBoxProfileIssue) string {
|
||||
parts := make([]string, 0, len(issues))
|
||||
for _, it := range issues {
|
||||
parts = append(parts, it.Message)
|
||||
}
|
||||
return transportcfgpkg.JoinMessages(parts)
|
||||
}
|
||||
Reference in New Issue
Block a user