platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
transportcfgpkg "selective-vpn-api/app/transportcfg"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func deriveSingBoxProfileID(name, protocol string, existing []SingBoxProfile) string {
|
||||
base := sanitizeID(name)
|
||||
if base == "" {
|
||||
base = sanitizeID(protocol)
|
||||
}
|
||||
if base == "" {
|
||||
base = "singbox-profile"
|
||||
}
|
||||
cand := base
|
||||
|
||||
used := make(map[string]struct{}, len(existing))
|
||||
for _, it := range existing {
|
||||
used[it.ID] = struct{}{}
|
||||
}
|
||||
if _, ok := used[cand]; !ok {
|
||||
return cand
|
||||
}
|
||||
for i := 2; i < 1000; i++ {
|
||||
v := base + "-" + strconv.Itoa(i)
|
||||
if _, ok := used[v]; !ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findSingBoxProfileIndex(items []SingBoxProfile, id string) int {
|
||||
for i := range items {
|
||||
if strings.TrimSpace(items[i].ID) == id {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ensureSingBoxProfilesActiveID(st *singBoxProfilesState) {
|
||||
if st == nil {
|
||||
return
|
||||
}
|
||||
if len(st.Items) == 0 {
|
||||
st.ActiveProfileID = ""
|
||||
return
|
||||
}
|
||||
active := strings.TrimSpace(st.ActiveProfileID)
|
||||
if active != "" {
|
||||
for _, it := range st.Items {
|
||||
if it.ID == active && it.Enabled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, it := range st.Items {
|
||||
if it.Enabled {
|
||||
st.ActiveProfileID = it.ID
|
||||
return
|
||||
}
|
||||
}
|
||||
st.ActiveProfileID = st.Items[0].ID
|
||||
}
|
||||
|
||||
func normalizeSingBoxProfilesState(in singBoxProfilesState) singBoxProfilesState {
|
||||
out := in
|
||||
if out.Version == 0 {
|
||||
out.Version = singBoxProfilesStateVersion
|
||||
}
|
||||
if out.Revision < 0 {
|
||||
out.Revision = 0
|
||||
}
|
||||
|
||||
byID := map[string]SingBoxProfile{}
|
||||
for _, raw := range in.Items {
|
||||
it := normalizeSingBoxProfile(raw)
|
||||
if it.ID == "" {
|
||||
continue
|
||||
}
|
||||
cur, ok := byID[it.ID]
|
||||
if !ok || preferSingBoxProfile(it, cur) {
|
||||
byID[it.ID] = it
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(byID))
|
||||
for id := range byID {
|
||||
keys = append(keys, id)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
out.Items = make([]SingBoxProfile, 0, len(keys))
|
||||
for _, id := range keys {
|
||||
out.Items = append(out.Items, byID[id])
|
||||
}
|
||||
ensureSingBoxProfilesActiveID(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeSingBoxProfile(in SingBoxProfile) SingBoxProfile {
|
||||
out := in
|
||||
out.ID = sanitizeID(in.ID)
|
||||
if out.ID == "" {
|
||||
return SingBoxProfile{}
|
||||
}
|
||||
out.Name = strings.TrimSpace(in.Name)
|
||||
if out.Name == "" {
|
||||
out.Name = out.ID
|
||||
}
|
||||
if mode, ok := normalizeSingBoxProfileMode(in.Mode); ok {
|
||||
out.Mode = mode
|
||||
} else {
|
||||
out.Mode = SingBoxProfileModeTyped
|
||||
}
|
||||
out.Protocol = normalizeSingBoxProtocol(in.Protocol)
|
||||
out.SchemaVersion = normalizeSingBoxSchemaVersion(in.SchemaVersion)
|
||||
if out.ProfileRevision <= 0 {
|
||||
out.ProfileRevision = 1
|
||||
}
|
||||
if out.RenderRevision < 0 {
|
||||
out.RenderRevision = 0
|
||||
}
|
||||
out.LastValidatedAt = strings.TrimSpace(in.LastValidatedAt)
|
||||
out.LastAppliedAt = strings.TrimSpace(in.LastAppliedAt)
|
||||
out.LastError = strings.TrimSpace(in.LastError)
|
||||
out.CreatedAt = strings.TrimSpace(in.CreatedAt)
|
||||
out.UpdatedAt = strings.TrimSpace(in.UpdatedAt)
|
||||
out.Typed = cloneMapDeep(in.Typed)
|
||||
out.RawConfig = cloneMapDeep(in.RawConfig)
|
||||
out.Meta = cloneMapDeep(in.Meta)
|
||||
|
||||
secretMap, err := readSingBoxSecrets(out.ID)
|
||||
if err == nil {
|
||||
out.HasSecrets = len(secretMap) > 0
|
||||
out.SecretsMasked = transportcfgpkg.MaskStringMap(secretMap, "******")
|
||||
} else {
|
||||
out.HasSecrets = in.HasSecrets
|
||||
out.SecretsMasked = transportcfgpkg.CloneStringMap(in.SecretsMasked)
|
||||
}
|
||||
if !out.HasSecrets {
|
||||
out.SecretsMasked = nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func preferSingBoxProfile(cand, cur SingBoxProfile) bool {
|
||||
if cand.ProfileRevision != cur.ProfileRevision {
|
||||
return cand.ProfileRevision > cur.ProfileRevision
|
||||
}
|
||||
cu := strings.TrimSpace(cand.UpdatedAt)
|
||||
ou := strings.TrimSpace(cur.UpdatedAt)
|
||||
if cu != ou {
|
||||
if cu == "" {
|
||||
return false
|
||||
}
|
||||
if ou == "" {
|
||||
return true
|
||||
}
|
||||
return cu > ou
|
||||
}
|
||||
return strings.TrimSpace(cand.CreatedAt) > strings.TrimSpace(cur.CreatedAt)
|
||||
}
|
||||
Reference in New Issue
Block a user