package dashboard import ( "fmt" "net/http" "strconv" "strings" ) type settingsPageData struct { PageData Settings orchestratorSettingsDTO Inbound inboundChecksDTO } func (s *Server) handleSettingsPage(w http.ResponseWriter, r *http.Request) { settings, err := s.CA.GetOrchestratorSettings(r.Context()) inbound, inboundErr := s.CA.GetInboundChecks(r.Context()) if err == nil { err = inboundErr } data := settingsPageData{Settings: settings, Inbound: inbound} data.ActiveNav = "settings" data.Banner = bannerFor(err) s.renderPage(w, "settings_page", data) } // renderSettingsForm re-fetches the current settings and renders the // settings_form fragment, tagging actionErr (if any) on the shared error // banner — same pattern as renderIPsTable: always reflect true current // state regardless of whether the mutation itself succeeded. func (s *Server) renderSettingsForm(w http.ResponseWriter, r *http.Request, actionErr error) { settings, getErr := s.CA.GetOrchestratorSettings(r.Context()) if actionErr == nil { actionErr = getErr } inbound, inboundErr := s.CA.GetInboundChecks(r.Context()) if actionErr == nil { actionErr = inboundErr } s.renderFragment(w, "settings_form", settingsPageData{Settings: settings, Inbound: inbound}, actionErr) } func (s *Server) handleSettingsPut(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { s.renderSettingsForm(w, r, fmt.Errorf("invalid form: %w", err)) return } seconds, err := strconv.Atoi(r.PostFormValue("fip_settle_seconds")) if err != nil { s.renderSettingsForm(w, r, &apiErr{Status: http.StatusBadRequest, Message: "пауза должна быть целым числом секунд"}) return } retentionCycles, err := strconv.Atoi(r.PostFormValue("history_retention_cycles")) if err != nil { s.renderSettingsForm(w, r, &apiErr{Status: http.StatusBadRequest, Message: "глубина истории должна быть целым числом циклов"}) return } _, err = s.CA.PutOrchestratorSettings(r.Context(), seconds, retentionCycles) s.renderSettingsForm(w, r, err) } // handleInboundChecksPut parses the comma-separated ports field of the // second form on /settings and saves it via PUT // /api/v1/admin/config/inbound-checks. A separate form/handler from // fip_settle_seconds above — the two settings are unrelated and shouldn't // share one submit. func (s *Server) handleInboundChecksPut(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { s.renderSettingsForm(w, r, fmt.Errorf("invalid form: %w", err)) return } raw := strings.TrimSpace(r.PostFormValue("ports")) var ports []int if raw != "" { for _, part := range strings.Split(raw, ",") { part = strings.TrimSpace(part) if part == "" { continue } p, err := strconv.Atoi(part) if err != nil { s.renderSettingsForm(w, r, &apiErr{Status: http.StatusBadRequest, Message: "порты должны быть целыми числами через запятую"}) return } ports = append(ports, p) } } icmp := r.PostFormValue("icmp") == "on" _, err := s.CA.PutInboundChecks(r.Context(), ports, icmp) s.renderSettingsForm(w, r, err) }