package api import ( "encoding/json" "fmt" "net/http" "time" "proxmox-router/internal/firewall" "proxmox-router/internal/routing" ) // ======================== Firewall Summary ======================== func (s *Server) handleFirewallSummary(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, s.fwMgr.GetSummary()) } func (s *Server) handleResetFirewallCounters(w http.ResponseWriter, r *http.Request) { if err := s.fwMgr.ResetCounters(); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true}) } // ======================== Filter Rules ======================== func (s *Server) handleListFilterRules(w http.ResponseWriter, r *http.Request) { rules := s.fwMgr.ListFilterRules() writeJSON(w, http.StatusOK, rules) } func (s *Server) handleSaveFilterRule(w http.ResponseWriter, r *http.Request) { var rule firewall.FilterRule if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if rule.ID == "" { rule.ID = fmt.Sprintf("filter-%d", time.Now().UnixNano()) } if err := s.fwMgr.SaveFilterRule(rule); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": rule.ID}) } func (s *Server) handleDeleteFilterRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.fwMgr.DeleteFilterRule(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleMoveFilterRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Direction string `json:"direction"` // "up" or "down" } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.MoveFilterRule(id, req.Direction); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleSetFilterRuleState(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Disabled bool `json:"disabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.SetFilterRuleState(id, req.Disabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id, "disabled": req.Disabled}) } // ======================== NAT Rules ======================== func (s *Server) handleListNATRules(w http.ResponseWriter, r *http.Request) { rules := s.fwMgr.ListNATRules() writeJSON(w, http.StatusOK, rules) } func (s *Server) handleSaveNATRule(w http.ResponseWriter, r *http.Request) { var rule firewall.NATRule if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if rule.ID == "" { rule.ID = fmt.Sprintf("nat-%d", time.Now().UnixNano()) } if err := s.fwMgr.SaveNATRule(rule); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": rule.ID}) } func (s *Server) handleDeleteNATRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.fwMgr.DeleteNATRule(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleMoveNATRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Direction string `json:"direction"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.MoveNATRule(id, req.Direction); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleSetNATRuleState(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Disabled bool `json:"disabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.SetNATRuleState(id, req.Disabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id, "disabled": req.Disabled}) } // ======================== Mangle Rules ======================== func (s *Server) handleListMangleRules(w http.ResponseWriter, r *http.Request) { rules := s.fwMgr.ListMangleRules() writeJSON(w, http.StatusOK, rules) } func (s *Server) handleSaveMangleRule(w http.ResponseWriter, r *http.Request) { var rule firewall.MangleRule if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if rule.ID == "" { rule.ID = fmt.Sprintf("mangle-%d", time.Now().UnixNano()) } if err := s.fwMgr.SaveMangleRule(rule); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": rule.ID}) } func (s *Server) handleDeleteMangleRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.fwMgr.DeleteMangleRule(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleMoveMangleRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Direction string `json:"direction"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.MoveMangleRule(id, req.Direction); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleSetMangleRuleState(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Disabled bool `json:"disabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.SetMangleRuleState(id, req.Disabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id, "disabled": req.Disabled}) } // ======================== Address Lists ======================== func (s *Server) handleListAddressLists(w http.ResponseWriter, r *http.Request) { lists := s.fwMgr.ListAddressLists() writeJSON(w, http.StatusOK, lists) } func (s *Server) handleSaveAddressListEntry(w http.ResponseWriter, r *http.Request) { var entry firewall.AddressListEntry if err := json.NewDecoder(r.Body).Decode(&entry); err != nil || entry.Name == "" || entry.Address == "" { writeError(w, http.StatusBadRequest, "name and address are required") return } if err := s.fwMgr.SaveAddressListEntry(entry); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": entry.ID}) } func (s *Server) handleDeleteAddressListEntry(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.fwMgr.DeleteAddressListEntry(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleSetAddressListEntryState(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Disabled bool `json:"disabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.SetAddressListEntryState(id, req.Disabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id, "disabled": req.Disabled}) } // ======================== Geo-Location ======================== func (s *Server) handleGeoIPStatus(w http.ResponseWriter, r *http.Request) { status := s.fwMgr.GeoIP().GetStatus() writeJSON(w, http.StatusOK, status) } func (s *Server) handleGeoIPConfig(w http.ResponseWriter, r *http.Request) { var req struct { AutoUpdate bool `json:"auto_update"` IntervalHours int `json:"interval_hours"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.fwMgr.GeoIP().SetConfig(req.AutoUpdate, req.IntervalHours); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "config": req}) } func (s *Server) handleListGeoCountries(w http.ResponseWriter, r *http.Request) { countries := s.fwMgr.GeoIP().ListCountries() writeJSON(w, http.StatusOK, countries) } func (s *Server) handleToggleGeoCountry(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") var req struct { Enabled bool `json:"enabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.fwMgr.GeoIP().ToggleCountry(code, req.Enabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "code": code, "enabled": req.Enabled}) } func (s *Server) handleUpdateGeoCountry(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") count, err := s.fwMgr.GeoIP().SyncCountryZone(code) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "code": code, "subnets": count}) } func (s *Server) handleGeoIPUpdateNow(w http.ResponseWriter, r *http.Request) { if err := s.fwMgr.GeoIP().UpdateNow(); err != nil { writeError(w, http.StatusConflict, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "message": "update started"}) } func (s *Server) handleGeoIPBatchToggle(w http.ResponseWriter, r *http.Request) { var req struct { Preset string `json:"preset"` Codes []string `json:"codes"` Enabled bool `json:"enabled"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.fwMgr.GeoIP().BatchToggle(req.Preset, req.Enabled, req.Codes); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "preset": req.Preset, "enabled": req.Enabled}) } func (s *Server) handleGeoCountrySubnets(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") subnets := s.fwMgr.GeoIP().GetCountrySubnets(code, 100) writeJSON(w, http.StatusOK, map[string]any{"code": code, "subnets": subnets}) } // ======================== Active Connections ======================== func (s *Server) handleListConnections(w http.ResponseWriter, r *http.Request) { conns, err := firewall.ListActiveConnections() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, conns) } func (s *Server) handleKillConnection(w http.ResponseWriter, r *http.Request) { var req struct { Protocol string `json:"protocol"` Src string `json:"src"` Sport int `json:"sport"` Dst string `json:"dst"` Dport int `json:"dport"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := firewall.KillConnection(req.Protocol, req.Src, req.Sport, req.Dst, req.Dport); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true}) } // ======================== Routes & Policy Routing ======================== func (s *Server) handleListRoutes(w http.ResponseWriter, r *http.Request) { routes := s.routingMgr.ListRoutes() writeJSON(w, http.StatusOK, routes) } func (s *Server) handleSaveRoute(w http.ResponseWriter, r *http.Request) { var route routing.RouteItem if err := json.NewDecoder(r.Body).Decode(&route); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.routingMgr.SaveRoute(route); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": route.ID}) } func (s *Server) handleDeleteRoute(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.routingMgr.DeleteRoute(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleSetRouteState(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req struct { Disabled bool `json:"disabled"` } _ = json.NewDecoder(r.Body).Decode(&req) if err := s.routingMgr.SetRouteState(id, req.Disabled); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id, "disabled": req.Disabled}) } func (s *Server) handleListPolicyRules(w http.ResponseWriter, r *http.Request) { rules := s.routingMgr.ListPolicyRules() writeJSON(w, http.StatusOK, rules) } func (s *Server) handleSavePolicyRule(w http.ResponseWriter, r *http.Request) { var rule routing.PolicyRuleItem if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.routingMgr.SavePolicyRule(rule); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": rule.ID}) } func (s *Server) handleDeletePolicyRule(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.routingMgr.DeletePolicyRule(id); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "id": id}) } func (s *Server) handleListRoutingTables(w http.ResponseWriter, r *http.Request) { tables := s.routingMgr.ListTables() writeJSON(w, http.StatusOK, tables) } func (s *Server) handleSaveRoutingTable(w http.ResponseWriter, r *http.Request) { var table routing.RoutingTableItem if err := json.NewDecoder(r.Body).Decode(&table); err != nil || table.Name == "" { writeError(w, http.StatusBadRequest, "table name is required") return } if err := s.routingMgr.SaveTable(table); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": table.Name}) }