package handlers import ( "context" "encoding/json" "fmt" "net/http" "sort" "strconv" "strings" "amnezia-share/internal/settings" "amnezia-share/internal/share" "amnezia-share/internal/web" ) type serverRow struct { ID int Title string Protocols []string Flag string Speed string Disabled bool PanelName string PanelHost string } type adminServersView struct { adminLayout Rows []serverRow AllProtocols []string PanelReachable bool PanelErr string } // AdminServersGet lists the known panel servers with their editable labels, // allowed protocols, flag emoji, speed badge and disabled state // (admin/server_labels.php). func AdminServersGet(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ok, errText := flashFromQuery(r) view := adminServersView{ adminLayout: newAdminLayout(a, r, "servers"), AllProtocols: share.GuestProtocols, } view.OK, view.Error = ok, errText labels := a.Settings.ServerLabels(ctx) protosMap := a.Settings.JSONMapStringSlice(ctx, settings.KeyProtocolsJSON) flagsMap := a.Settings.JSONMapString(ctx, settings.KeyFlagsJSON) speedsMap := a.Settings.JSONMapString(ctx, settings.KeySpeedsJSON) disabledMap := a.Settings.DisabledServers(ctx) baseURL := a.Settings.PanelURL(ctx) token := a.Settings.PanelToken(ctx) panelServers := map[int]struct{ Name, Host string }{} if baseURL != "" && token != "" { list, err := a.Panel.ListServers(ctx, baseURL, token) if err != nil { view.PanelErr = err.Error() } else { view.PanelReachable = true for _, s := range list { panelServers[s.ID] = struct{ Name, Host string }{s.Name, s.Host} if _, known := labels[s.ID]; !known { labels[s.ID] = s.Name } } } } ids := make([]int, 0, len(labels)) for id := range labels { ids = append(ids, id) } sort.Ints(ids) for _, id := range ids { row := serverRow{ ID: id, Title: labels[id], Protocols: protosMap[id], Flag: flagsMap[id], Speed: speedsMap[id], Disabled: disabledMap[id], } if len(row.Protocols) == 0 { row.Protocols = append([]string(nil), share.DefaultServerProtocols...) } if p, ok := panelServers[id]; ok { row.PanelName, row.PanelHost = p.Name, p.Host } view.Rows = append(view.Rows, row) } a.Render(w, r, "page_admin_servers", view) } } func protocolSelected(list []string, proto string) bool { for _, p := range list { if p == proto { return true } } return false } func upsertServerLabel(ctx context.Context, a *web.App, id int, title string) error { title = strings.TrimSpace(title) if title == "" { return nil } _, err := a.Pool.Exec(ctx, ` INSERT INTO panel_server_labels (panel_server_id, title, updated_at) VALUES ($1,$2,NOW()) ON CONFLICT (panel_server_id) DO UPDATE SET title=EXCLUDED.title, updated_at=NOW()`, id, title) return err } func intMapToJSON[T any](m map[int]T) string { out := make(map[string]T, len(m)) for k, v := range m { out[strconv.Itoa(k)] = v } b, _ := json.Marshal(out) return string(b) } func intSetToJSON(m map[int]bool) string { ids := make([]int, 0, len(m)) for k, v := range m { if v { ids = append(ids, k) } } sort.Ints(ids) b, _ := json.Marshal(ids) return string(b) } func saveServerJSONSettings(ctx context.Context, a *web.App, protosMap map[int][]string, flagsMap, speedsMap map[int]string, disabledSet map[int]bool) error { if err := a.Settings.Set(ctx, settings.KeyProtocolsJSON, intMapToJSON(protosMap)); err != nil { return err } if err := a.Settings.Set(ctx, settings.KeyFlagsJSON, intMapToJSON(flagsMap)); err != nil { return err } if err := a.Settings.Set(ctx, settings.KeySpeedsJSON, intMapToJSON(speedsMap)); err != nil { return err } return a.Settings.Set(ctx, settings.KeyDisabledServers, intSetToJSON(disabledSet)) } // AdminServersPost saves the whole server table, adds a new server id, or // removes one server's label row. func AdminServersPost(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() _ = r.ParseForm() var err error var okMsg string switch r.FormValue("action") { case "save": protosMap := a.Settings.JSONMapStringSlice(ctx, settings.KeyProtocolsJSON) flagsMap := a.Settings.JSONMapString(ctx, settings.KeyFlagsJSON) speedsMap := a.Settings.JSONMapString(ctx, settings.KeySpeedsJSON) disabledSet := a.Settings.DisabledServers(ctx) for _, idStr := range r.Form["server_ids"] { id, perr := strconv.Atoi(strings.TrimSpace(idStr)) if perr != nil { continue } title := r.FormValue(fmt.Sprintf("servers[%d][title]", id)) if uerr := upsertServerLabel(ctx, a, id, title); uerr != nil { err = uerr } protos := r.Form[fmt.Sprintf("servers[%d][protocols][]", id)] if len(protos) > 0 { protosMap[id] = protos } else { delete(protosMap, id) } flag := strings.TrimSpace(r.FormValue(fmt.Sprintf("servers[%d][flag]", id))) if flag != "" { flagsMap[id] = flag } else { delete(flagsMap, id) } speed := strings.TrimSpace(r.FormValue(fmt.Sprintf("servers[%d][speed]", id))) if speed != "" { speedsMap[id] = speed } else { delete(speedsMap, id) } if r.FormValue(fmt.Sprintf("servers[%d][disabled]", id)) == "1" { disabledSet[id] = true } else { delete(disabledSet, id) } } if err == nil { err = saveServerJSONSettings(ctx, a, protosMap, flagsMap, speedsMap, disabledSet) } if err == nil { okMsg = "Настройки серверов сохранены." } case "add": id := formInt(r, "new_server_id", 0) title := r.FormValue("new_server_title") if id <= 0 || strings.TrimSpace(title) == "" { err = errUnknownAction break } err = upsertServerLabel(ctx, a, id, title) if err == nil { okMsg = "Сервер добавлен." } case "delete_label": id := formInt(r, "server_id", 0) _, derr := a.Pool.Exec(ctx, `DELETE FROM panel_server_labels WHERE panel_server_id=$1`, id) err = derr if err == nil { protosMap := a.Settings.JSONMapStringSlice(ctx, settings.KeyProtocolsJSON) flagsMap := a.Settings.JSONMapString(ctx, settings.KeyFlagsJSON) speedsMap := a.Settings.JSONMapString(ctx, settings.KeySpeedsJSON) disabledSet := a.Settings.DisabledServers(ctx) delete(protosMap, id) delete(flagsMap, id) delete(speedsMap, id) delete(disabledSet, id) err = saveServerJSONSettings(ctx, a, protosMap, flagsMap, speedsMap, disabledSet) } if err == nil { okMsg = "Сервер удалён из списка." } default: err = errUnknownAction } errText := "" if err != nil { errText = err.Error() } redirectWithFlash(w, r, a, "admin/servers", okMsg, errText) } }