package handlers import ( "log" "net/http" "github.com/google/uuid" "github.com/gorilla/mux" "github.com/orohi/vpn-panel/internal/db" "github.com/orohi/vpn-panel/internal/models" "github.com/orohi/vpn-panel/internal/nodeclient" ) func (s *Server) syncNodeConfig(n *models.Node) error { cfg, err := db.NodeProtocolConfigPayload(s.DB, n.ID) if err != nil { return err } return nodeclient.PushConfig(n, cfg) } func (s *Server) protocols(w http.ResponseWriter, r *http.Request) { protocols, err := db.ListProtocolsDetailed(s.DB) if err != nil { http.Error(w, "db error", http.StatusInternalServerError) return } nodes, _ := db.ListNodes(s.DB) s.render(w, "protocols.html", pageData{ "Title": "Протоколы", "UserName": s.Auth.CurrentName(r), "Protocols": protocols, "Nodes": nodes, "Active": "protocols", "Flash": r.URL.Query().Get("ok"), "Error": r.URL.Query().Get("err"), }) } func (s *Server) toggleProtocol(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["id"] id, err := uuid.Parse(idStr) if err != nil { http.Error(w, "invalid id", http.StatusBadRequest) return } if err := db.ToggleProtocol(s.DB, id); err != nil { http.Error(w, "db error", http.StatusInternalServerError) return } http.Redirect(w, r, "/admin/protocols", http.StatusSeeOther) } func (s *Server) nodeProtocolAction(w http.ResponseWriter, r *http.Request) { nodeID, err := uuid.Parse(mux.Vars(r)["id"]) if err != nil { http.Error(w, "invalid node id", http.StatusBadRequest) return } protocolID, err := uuid.Parse(mux.Vars(r)["protocolID"]) if err != nil { http.Error(w, "invalid protocol id", http.StatusBadRequest) return } action := r.FormValue("action") if action == "" { _ = r.ParseForm() action = r.FormValue("action") } n, err := db.GetNode(s.DB, nodeID) if err != nil || n == nil { http.NotFound(w, r) return } p, err := db.GetProtocol(s.DB, protocolID) if err != nil || p == nil { http.NotFound(w, r) return } list, err := db.ListNodeProtocols(s.DB, nodeID) if err != nil { http.Error(w, "db error", http.StatusInternalServerError) return } var current models.NodeProtocol for _, item := range list { if item.ProtocolID == protocolID { current = item break } } installed, enabled := current.Installed, current.Enabled switch action { case "install": installed, enabled = true, true case "uninstall": installed, enabled = false, false case "enable": installed, enabled = true, true case "disable": enabled = false if !installed { installed = false } default: http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?err=bad_action", http.StatusSeeOther) return } if err := db.SetNodeProtocolFlags(s.DB, nodeID, protocolID, installed, enabled); err != nil { log.Printf("set node protocol: %v", err) http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?err=proto_save", http.StatusSeeOther) return } // If panel catalog was off and we install/enable — turn protocol on in catalog. if (action == "install" || action == "enable") && !p.Enabled { _ = db.SetProtocolEnabled(s.DB, protocolID, true) } if action == "uninstall" { // If nowhere else has it installed, turn catalog off. detailed, _ := db.ListProtocolsDetailed(s.DB) for _, item := range detailed { if item.ID == protocolID && len(item.InstalledOn) == 0 { _ = db.SetProtocolEnabled(s.DB, protocolID, false) } } } if n.Status == models.NodeStatusOnline { if err := s.syncNodeConfig(n); err != nil { http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?err=sync_failed", http.StatusSeeOther) return } } http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?ok=protocol_"+action, http.StatusSeeOther) } func (s *Server) nodeSyncProtocols(w http.ResponseWriter, r *http.Request) { nodeID, err := uuid.Parse(mux.Vars(r)["id"]) if err != nil { http.Error(w, "invalid id", http.StatusBadRequest) return } n, err := db.GetNode(s.DB, nodeID) if err != nil || n == nil { http.NotFound(w, r) return } hr, err := nodeclient.Health(n) if err != nil { _ = db.UpdateNodeStatus(s.DB, nodeID, models.NodeStatusOffline, err.Error()) http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?err=offline", http.StatusSeeOther) return } _ = db.MarkNodeSeen(s.DB, nodeID, hr.Version, models.NodeStatusOnline) // Push panel → node (desired state). Do not wipe local DB from empty agent. if err := s.syncNodeConfig(n); err != nil { http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?err=sync_failed", http.StatusSeeOther) return } http.Redirect(w, r, "/admin/nodes/"+nodeID.String()+"?ok=synced", http.StatusSeeOther) }