package api import ( "encoding/json" "net/http" "strings" "proxmox-router/internal/bridge" "proxmox-router/internal/dhcp" "proxmox-router/internal/bwtest" "proxmox-router/internal/dynamicrouting" "proxmox-router/internal/firewall" "proxmox-router/internal/network" "proxmox-router/internal/routing" "proxmox-router/internal/system" "proxmox-router/internal/tools" "proxmox-router/internal/vpn" "proxmox-router/internal/vrrp" ) type Server struct { netMgr *network.Manager persistMgr *network.PersistenceManager metaMgr *network.MetadataManager vpnMgr *vpn.Manager fwMgr *firewall.FirewallManager routingMgr *routing.RoutingManager dynRoutingMgr *dynamicrouting.Manager bridgeMgr *bridge.BridgeManager toolsMgr *tools.Manager dhcpMgr *dhcp.Manager dhcpServerMgr *dhcp.ServerManager vrrpMgr *vrrp.Manager bwtestMgr *bwtest.Manager mux *http.ServeMux fsHandler http.Handler } func NewServer(netMgr *network.Manager, persistMgr *network.PersistenceManager, metaMgr *network.MetadataManager, vpnMgr *vpn.Manager, fwMgr *firewall.FirewallManager, routingMgr *routing.RoutingManager, dynRoutingMgr *dynamicrouting.Manager, bridgeMgr *bridge.BridgeManager, toolsMgr *tools.Manager, dhcpMgr *dhcp.Manager, dhcpServerMgr *dhcp.ServerManager, vrrpMgr *vrrp.Manager, bwtestMgr *bwtest.Manager, fsHandler http.Handler) *Server { s := &Server{ netMgr: netMgr, persistMgr: persistMgr, metaMgr: metaMgr, vpnMgr: vpnMgr, fwMgr: fwMgr, routingMgr: routingMgr, dynRoutingMgr: dynRoutingMgr, bridgeMgr: bridgeMgr, toolsMgr: toolsMgr, dhcpMgr: dhcpMgr, dhcpServerMgr: dhcpServerMgr, vrrpMgr: vrrpMgr, bwtestMgr: bwtestMgr, mux: http.NewServeMux(), fsHandler: fsHandler, } s.registerRoutes() return s } func (s *Server) registerRoutes() { // API Endpoints s.mux.HandleFunc("GET /api/system/status", s.handleSystemStatus) // Routing & Policy Rules (/ip route & /ip route rule) s.mux.HandleFunc("GET /api/routes", s.handleListRoutes) s.mux.HandleFunc("POST /api/routes", s.handleSaveRoute) s.mux.HandleFunc("DELETE /api/routes/{id}", s.handleDeleteRoute) s.mux.HandleFunc("POST /api/routes/{id}/state", s.handleSetRouteState) s.mux.HandleFunc("GET /api/routing/rules", s.handleListPolicyRules) s.mux.HandleFunc("POST /api/routing/rules", s.handleSavePolicyRule) s.mux.HandleFunc("DELETE /api/routing/rules/{id}", s.handleDeletePolicyRule) s.mux.HandleFunc("GET /api/routing/tables", s.handleListRoutingTables) s.mux.HandleFunc("POST /api/routing/tables", s.handleSaveRoutingTable) // Firewall Endpoints (/ip firewall) s.mux.HandleFunc("GET /api/firewall/summary", s.handleFirewallSummary) s.mux.HandleFunc("POST /api/firewall/reset-counters", s.handleResetFirewallCounters) // Filter Rules s.mux.HandleFunc("GET /api/firewall/filter", s.handleListFilterRules) s.mux.HandleFunc("POST /api/firewall/filter", s.handleSaveFilterRule) s.mux.HandleFunc("DELETE /api/firewall/filter/{id}", s.handleDeleteFilterRule) s.mux.HandleFunc("POST /api/firewall/filter/{id}/move", s.handleMoveFilterRule) s.mux.HandleFunc("POST /api/firewall/filter/{id}/state", s.handleSetFilterRuleState) // NAT Rules s.mux.HandleFunc("GET /api/firewall/nat", s.handleListNATRules) s.mux.HandleFunc("POST /api/firewall/nat", s.handleSaveNATRule) s.mux.HandleFunc("DELETE /api/firewall/nat/{id}", s.handleDeleteNATRule) s.mux.HandleFunc("POST /api/firewall/nat/{id}/move", s.handleMoveNATRule) s.mux.HandleFunc("POST /api/firewall/nat/{id}/state", s.handleSetNATRuleState) // Mangle Rules s.mux.HandleFunc("GET /api/firewall/mangle", s.handleListMangleRules) s.mux.HandleFunc("POST /api/firewall/mangle", s.handleSaveMangleRule) s.mux.HandleFunc("DELETE /api/firewall/mangle/{id}", s.handleDeleteMangleRule) s.mux.HandleFunc("POST /api/firewall/mangle/{id}/move", s.handleMoveMangleRule) s.mux.HandleFunc("POST /api/firewall/mangle/{id}/state", s.handleSetMangleRuleState) // Address Lists s.mux.HandleFunc("GET /api/firewall/address-lists", s.handleListAddressLists) s.mux.HandleFunc("POST /api/firewall/address-lists", s.handleSaveAddressListEntry) s.mux.HandleFunc("DELETE /api/firewall/address-lists/{id}", s.handleDeleteAddressListEntry) s.mux.HandleFunc("POST /api/firewall/address-lists/{id}/state", s.handleSetAddressListEntryState) // Geo-Location s.mux.HandleFunc("GET /api/firewall/geoip/status", s.handleGeoIPStatus) s.mux.HandleFunc("POST /api/firewall/geoip/config", s.handleGeoIPConfig) s.mux.HandleFunc("GET /api/firewall/geoip/countries", s.handleListGeoCountries) s.mux.HandleFunc("POST /api/firewall/geoip/countries/{code}/toggle", s.handleToggleGeoCountry) s.mux.HandleFunc("POST /api/firewall/geoip/countries/{code}/update", s.handleUpdateGeoCountry) s.mux.HandleFunc("POST /api/firewall/geoip/update-now", s.handleGeoIPUpdateNow) s.mux.HandleFunc("POST /api/firewall/geoip/batch-toggle", s.handleGeoIPBatchToggle) s.mux.HandleFunc("GET /api/firewall/geoip/countries/{code}/subnets", s.handleGeoCountrySubnets) // Active Connections s.mux.HandleFunc("GET /api/firewall/connections", s.handleListConnections) s.mux.HandleFunc("POST /api/firewall/connections/kill", s.handleKillConnection) s.mux.HandleFunc("GET /api/interfaces", s.handleListInterfaces) s.mux.HandleFunc("GET /api/interfaces/{name}", s.handleGetInterface) s.mux.HandleFunc("POST /api/interfaces/{name}/state", s.handleSetInterfaceState) s.mux.HandleFunc("POST /api/interfaces/{name}/mtu", s.handleSetInterfaceMTU) s.mux.HandleFunc("POST /api/interfaces/{name}/ip", s.handleAddInterfaceIP) s.mux.HandleFunc("DELETE /api/interfaces/{name}/ip", s.handleDeleteInterfaceIP) s.mux.HandleFunc("POST /api/interfaces/{name}/metadata", s.handleSetInterfaceMetadata) s.mux.HandleFunc("GET /api/vlans", s.handleListVLANs) s.mux.HandleFunc("POST /api/vlans", s.handleCreateVLAN) s.mux.HandleFunc("DELETE /api/vlans/{name}", s.handleDeleteVLAN) // Bridge Management (/interface bridge) s.mux.HandleFunc("GET /api/bridge/summary", s.handleBridgeSummary) s.mux.HandleFunc("GET /api/bridge/bridges", s.handleListBridges) s.mux.HandleFunc("POST /api/bridge/bridges", s.handleSaveBridge) s.mux.HandleFunc("DELETE /api/bridge/bridges/{id}", s.handleDeleteBridge) s.mux.HandleFunc("POST /api/bridge/bridges/{id}/state", s.handleSetBridgeState) s.mux.HandleFunc("POST /api/bridge/bridges/{id}/ip", s.handleAddBridgeIP) s.mux.HandleFunc("DELETE /api/bridge/bridges/{id}/ip", s.handleDeleteBridgeIP) s.mux.HandleFunc("GET /api/bridge/ports", s.handleListBridgePorts) s.mux.HandleFunc("POST /api/bridge/ports", s.handleSaveBridgePort) s.mux.HandleFunc("DELETE /api/bridge/ports/{id}", s.handleDeleteBridgePort) s.mux.HandleFunc("POST /api/bridge/ports/{id}/state", s.handleSetBridgePortState) s.mux.HandleFunc("GET /api/bridge/vlans", s.handleListBridgeVLANs) s.mux.HandleFunc("POST /api/bridge/vlans", s.handleSaveBridgeVLAN) s.mux.HandleFunc("DELETE /api/bridge/vlans/{id}", s.handleDeleteBridgeVLAN) s.mux.HandleFunc("POST /api/bridge/vlans/{id}/state", s.handleSetBridgeVLANState) s.mux.HandleFunc("GET /api/bridge/hosts", s.handleListBridgeHosts) s.mux.HandleFunc("POST /api/bridge/hosts/flush", s.handleFlushBridgeHosts) s.mux.HandleFunc("GET /api/debian/config", s.handleGetDebianConfig) s.mux.HandleFunc("POST /api/debian/apply", s.handleSaveDebianConfig) // VPN & WAN Endpoints s.mux.HandleFunc("GET /api/vpn/status", s.handleOverallVPNStatus) // PPPoE Multi-Instance s.mux.HandleFunc("GET /api/vpn/pppoe/clients", s.handleListPPPoE) s.mux.HandleFunc("POST /api/vpn/pppoe/clients", s.handleSavePPPoE) s.mux.HandleFunc("DELETE /api/vpn/pppoe/clients/{name}", s.handleDeletePPPoE) s.mux.HandleFunc("POST /api/vpn/pppoe/clients/{name}/state", s.handleSetPPPoEState) s.mux.HandleFunc("POST /api/vpn/pppoe/clients/{name}/connect", s.handleConnectPPPoE) s.mux.HandleFunc("POST /api/vpn/pppoe/clients/{name}/disconnect", s.handleDisconnectPPPoE) // WireGuard Multi-Interface & Multi-Peer s.mux.HandleFunc("GET /api/vpn/wireguard/interfaces", s.handleListWireGuardInterfaces) s.mux.HandleFunc("POST /api/vpn/wireguard/interfaces", s.handleSaveWireGuardInterface) s.mux.HandleFunc("DELETE /api/vpn/wireguard/interfaces/{name}", s.handleDeleteWireGuardInterface) s.mux.HandleFunc("POST /api/vpn/wireguard/interfaces/{name}/state", s.handleSetWireGuardInterfaceState) s.mux.HandleFunc("POST /api/vpn/wireguard/interfaces/{name}/start", s.handleStartWireGuardInterface) s.mux.HandleFunc("POST /api/vpn/wireguard/interfaces/{name}/stop", s.handleStopWireGuardInterface) s.mux.HandleFunc("GET /api/vpn/wireguard/peers", s.handleListWireGuardPeers) s.mux.HandleFunc("POST /api/vpn/wireguard/peers", s.handleSaveWireGuardPeer) s.mux.HandleFunc("POST /api/vpn/wireguard/peers/state", s.handleSetWireGuardPeerState) s.mux.HandleFunc("POST /api/vpn/wireguard/peers/delete", s.handleDeleteWireGuardPeer) s.mux.HandleFunc("DELETE /api/vpn/wireguard/peers", s.handleDeleteWireGuardPeer) s.mux.HandleFunc("DELETE /api/vpn/wireguard/peers/{iface}/{pubkey...}", s.handleDeleteWireGuardPeer) s.mux.HandleFunc("POST /api/vpn/wireguard/genkeys", s.handleWireGuardGenKeys) s.mux.HandleFunc("POST /api/vpn/wireguard/calc-pubkey", s.handleWireGuardCalcPubKey) // PPTP Multi-Instance s.mux.HandleFunc("GET /api/vpn/pptp/clients", s.handleListPPTP) s.mux.HandleFunc("POST /api/vpn/pptp/clients", s.handleSavePPTP) s.mux.HandleFunc("DELETE /api/vpn/pptp/clients/{name}", s.handleDeletePPTP) s.mux.HandleFunc("POST /api/vpn/pptp/clients/{name}/state", s.handleSetPPTPState) s.mux.HandleFunc("POST /api/vpn/pptp/clients/{name}/connect", s.handleConnectPPTP) s.mux.HandleFunc("POST /api/vpn/pptp/clients/{name}/disconnect", s.handleDisconnectPPTP) // L2TP Multi-Instance s.mux.HandleFunc("GET /api/vpn/l2tp/clients", s.handleListL2TP) s.mux.HandleFunc("POST /api/vpn/l2tp/clients", s.handleSaveL2TP) s.mux.HandleFunc("DELETE /api/vpn/l2tp/clients/{name}", s.handleDeleteL2TP) s.mux.HandleFunc("POST /api/vpn/l2tp/clients/{name}/state", s.handleSetL2TPState) s.mux.HandleFunc("POST /api/vpn/l2tp/clients/{name}/connect", s.handleConnectL2TP) s.mux.HandleFunc("POST /api/vpn/l2tp/clients/{name}/disconnect", s.handleDisconnectL2TP) // IPSec Multi-Tunnel s.mux.HandleFunc("GET /api/vpn/ipsec/tunnels", s.handleListIPSec) s.mux.HandleFunc("POST /api/vpn/ipsec/tunnels", s.handleSaveIPSecTunnel) s.mux.HandleFunc("DELETE /api/vpn/ipsec/tunnels/{name}", s.handleDeleteIPSecTunnel) s.mux.HandleFunc("POST /api/vpn/ipsec/tunnels/{name}/state", s.handleSetIPSecState) s.mux.HandleFunc("POST /api/vpn/ipsec/tunnels/{name}/start", s.handleInitiateIPSecTunnel) s.mux.HandleFunc("POST /api/vpn/ipsec/tunnels/{name}/stop", s.handleTerminateIPSecTunnel) // EoIP (Ethernet over IP / GRETAP) s.mux.HandleFunc("GET /api/vpn/eoip", s.handleListEoIP) s.mux.HandleFunc("POST /api/vpn/eoip", s.handleSaveEoIP) s.mux.HandleFunc("DELETE /api/vpn/eoip/{id}", s.handleDeleteEoIP) s.mux.HandleFunc("POST /api/vpn/eoip/{id}/state", s.handleSetEoIPState) // AeroTUN (High-speed ChaCha20-Poly1305 + LZ4 Encrypted UDP Tunnel) s.mux.HandleFunc("GET /api/vpn/aerotun/tunnels", s.handleListAeroTUNTunnels) s.mux.HandleFunc("POST /api/vpn/aerotun/tunnels", s.handleSaveAeroTUNTunnel) s.mux.HandleFunc("DELETE /api/vpn/aerotun/tunnels/{id}", s.handleDeleteAeroTUNTunnel) s.mux.HandleFunc("POST /api/vpn/aerotun/tunnels/{id}/state", s.handleSetAeroTUNTunnelState) s.mux.HandleFunc("POST /api/vpn/aerotun/tunnels/{id}/start", s.handleStartAeroTUNTunnel) s.mux.HandleFunc("POST /api/vpn/aerotun/tunnels/{id}/stop", s.handleStopAeroTUNTunnel) s.mux.HandleFunc("GET /api/vpn/aerotun/genkey", s.handleGenerateAeroTUNKey) s.mux.HandleFunc("GET /api/vpn/aerotun/stats", s.handleGetAeroTUNStats) // PPP Profiles (/ppp profile) s.mux.HandleFunc("GET /api/vpn/ppp/profiles", s.handleListPPPProfiles) s.mux.HandleFunc("POST /api/vpn/ppp/profiles", s.handleSavePPPProfile) s.mux.HandleFunc("DELETE /api/vpn/ppp/profiles/{name}", s.handleDeletePPPProfile) // PPP Secrets (/ppp secret) s.mux.HandleFunc("GET /api/vpn/ppp/secrets", s.handleListPPPSecrets) s.mux.HandleFunc("POST /api/vpn/ppp/secrets", s.handleSavePPPSecret) s.mux.HandleFunc("DELETE /api/vpn/ppp/secrets/{id}", s.handleDeletePPPSecret) s.mux.HandleFunc("POST /api/vpn/ppp/secrets/{id}/state", s.handleSetPPPSecretState) // Active PPP Sessions (/ppp active) s.mux.HandleFunc("GET /api/vpn/ppp/active", s.handleListPPPSessions) s.mux.HandleFunc("DELETE /api/vpn/ppp/active/{iface}", s.handleDisconnectPPPSession) // VPN Server Settings s.mux.HandleFunc("GET /api/vpn/servers/pppoe", s.handleGetPPPoEServer) s.mux.HandleFunc("POST /api/vpn/servers/pppoe", s.handleSetPPPoEServer) s.mux.HandleFunc("GET /api/vpn/servers/pptp", s.handleGetPPTPServer) s.mux.HandleFunc("POST /api/vpn/servers/pptp", s.handleSetPPTPServer) s.mux.HandleFunc("GET /api/vpn/servers/l2tp", s.handleGetL2TPServer) s.mux.HandleFunc("POST /api/vpn/servers/l2tp", s.handleSetL2TPServer) s.mux.HandleFunc("GET /api/vpn/servers/ipsec", s.handleGetIPSecServer) s.mux.HandleFunc("POST /api/vpn/servers/ipsec", s.handleSetIPSecServer) // WireGuard Client Config Generator s.mux.HandleFunc("POST /api/vpn/wireguard/client-config", s.handleGenerateWGClientConfig) // Dynamic Routing (BGP & OSPF) s.mux.HandleFunc("GET /api/routing/dynamic/summary", s.handleDynamicRoutingSummary) // BGP s.mux.HandleFunc("GET /api/routing/bgp/instance", s.handleGetBGPInstance) s.mux.HandleFunc("POST /api/routing/bgp/instance", s.handleUpdateBGPInstance) s.mux.HandleFunc("GET /api/routing/bgp/peers", s.handleListBGPPeers) s.mux.HandleFunc("POST /api/routing/bgp/peers", s.handleSaveBGPPeer) s.mux.HandleFunc("DELETE /api/routing/bgp/peers/{id}", s.handleDeleteBGPPeer) s.mux.HandleFunc("POST /api/routing/bgp/peers/{id}/state", s.handleSetBGPPeerState) s.mux.HandleFunc("GET /api/routing/bgp/networks", s.handleListBGPNetworks) s.mux.HandleFunc("POST /api/routing/bgp/networks", s.handleSaveBGPNetwork) s.mux.HandleFunc("DELETE /api/routing/bgp/networks/{id}", s.handleDeleteBGPNetwork) s.mux.HandleFunc("GET /api/routing/bgp/routes", s.handleGetBGPRoutes) // OSPF (RouterOS v7 Multi-Instance OSPF) s.mux.HandleFunc("GET /api/routing/ospf/instances", s.handleListOSPFInstances) s.mux.HandleFunc("POST /api/routing/ospf/instances", s.handleSaveOSPFInstance) s.mux.HandleFunc("DELETE /api/routing/ospf/instances/{id}", s.handleDeleteOSPFInstance) s.mux.HandleFunc("POST /api/routing/ospf/instances/{id}/state", s.handleSetOSPFInstanceState) s.mux.HandleFunc("GET /api/routing/ospf/instance", s.handleGetOSPFInstance) s.mux.HandleFunc("POST /api/routing/ospf/instance", s.handleUpdateOSPFInstance) s.mux.HandleFunc("GET /api/routing/ospf/areas", s.handleListOSPFAreas) s.mux.HandleFunc("POST /api/routing/ospf/areas", s.handleSaveOSPFArea) s.mux.HandleFunc("DELETE /api/routing/ospf/areas/{id}", s.handleDeleteOSPFArea) s.mux.HandleFunc("POST /api/routing/ospf/areas/{id}/state", s.handleSetOSPFAreaState) s.mux.HandleFunc("GET /api/routing/ospf/area-ranges", s.handleListOSPFAreaRanges) s.mux.HandleFunc("POST /api/routing/ospf/area-ranges", s.handleSaveOSPFAreaRange) s.mux.HandleFunc("DELETE /api/routing/ospf/area-ranges/{id}", s.handleDeleteOSPFAreaRange) s.mux.HandleFunc("GET /api/routing/ospf/templates", s.handleListOSPFInterfaces) s.mux.HandleFunc("POST /api/routing/ospf/templates", s.handleSaveOSPFInterface) s.mux.HandleFunc("DELETE /api/routing/ospf/templates/{id}", s.handleDeleteOSPFInterface) s.mux.HandleFunc("POST /api/routing/ospf/templates/{id}/state", s.handleSetOSPFInterfaceState) s.mux.HandleFunc("GET /api/routing/ospf/interfaces", s.handleListOSPFInterfaces) s.mux.HandleFunc("POST /api/routing/ospf/interfaces", s.handleSaveOSPFInterface) s.mux.HandleFunc("DELETE /api/routing/ospf/interfaces/{id}", s.handleDeleteOSPFInterface) s.mux.HandleFunc("POST /api/routing/ospf/interfaces/{id}/state", s.handleSetOSPFInterfaceState) s.mux.HandleFunc("GET /api/routing/ospf/interfaces-live", s.handleGetOSPFInterfacesLive) s.mux.HandleFunc("GET /api/routing/ospf/static-neighbors", s.handleListOSPFStaticNeighbors) s.mux.HandleFunc("POST /api/routing/ospf/static-neighbors", s.handleSaveOSPFStaticNeighbor) s.mux.HandleFunc("DELETE /api/routing/ospf/static-neighbors/{id}", s.handleDeleteOSPFStaticNeighbor) s.mux.HandleFunc("GET /api/routing/ospf/neighbors", s.handleGetOSPFNeighbors) s.mux.HandleFunc("GET /api/routing/ospf/lsa", s.handleGetOSPFLSA) s.mux.HandleFunc("GET /api/routing/ospf/routes", s.handleGetOSPFRoutes) // Routing Filters (/routing filter) s.mux.HandleFunc("GET /api/routing/filters", s.handleListRoutingFilters) s.mux.HandleFunc("POST /api/routing/filters", s.handleSaveRoutingFilter) s.mux.HandleFunc("DELETE /api/routing/filters/{id}", s.handleDeleteRoutingFilter) s.mux.HandleFunc("POST /api/routing/filters/{id}/state", s.handleSetRoutingFilterState) s.mux.HandleFunc("GET /api/routing/filters/rules", s.handleListRoutingFilters) s.mux.HandleFunc("POST /api/routing/filters/rules", s.handleSaveRoutingFilter) s.mux.HandleFunc("DELETE /api/routing/filters/rules/{id}", s.handleDeleteRoutingFilter) s.mux.HandleFunc("POST /api/routing/filters/rules/{id}/state", s.handleSetRoutingFilterState) s.mux.HandleFunc("GET /api/routing/filters/select-rules", s.handleListSelectRules) s.mux.HandleFunc("POST /api/routing/filters/select-rules", s.handleSaveSelectRule) s.mux.HandleFunc("DELETE /api/routing/filters/select-rules/{id}", s.handleDeleteSelectRule) s.mux.HandleFunc("POST /api/routing/filters/select-rules/{id}/state", s.handleSetSelectRuleState) s.mux.HandleFunc("GET /api/routing/filters/community-lists", s.handleListCommunityLists) s.mux.HandleFunc("POST /api/routing/filters/community-lists", s.handleSaveCommunityList) s.mux.HandleFunc("DELETE /api/routing/filters/community-lists/{id}", s.handleDeleteCommunityList) s.mux.HandleFunc("POST /api/routing/filters/community-lists/{id}/state", s.handleSetCommunityListState) s.mux.HandleFunc("GET /api/routing/filters/as-lists", s.handleListASLists) s.mux.HandleFunc("POST /api/routing/filters/as-lists", s.handleSaveASList) s.mux.HandleFunc("DELETE /api/routing/filters/as-lists/{id}", s.handleDeleteASList) s.mux.HandleFunc("POST /api/routing/filters/as-lists/{id}/state", s.handleSetASListState) // Troubleshooting Tools (/tool ping, traceroute, mtr, dns, port-probe) s.mux.HandleFunc("POST /api/tools/ping", s.handleToolsPing) s.mux.HandleFunc("POST /api/tools/ping/stream", s.handleToolsPingStream) s.mux.HandleFunc("GET /api/tools/ping/stream", s.handleToolsPingStream) s.mux.HandleFunc("POST /api/tools/traceroute", s.handleToolsTraceroute) s.mux.HandleFunc("POST /api/tools/traceroute/stream", s.handleToolsTracerouteStream) s.mux.HandleFunc("GET /api/tools/traceroute/stream", s.handleToolsTracerouteStream) s.mux.HandleFunc("POST /api/tools/dns", s.handleToolsDNS) s.mux.HandleFunc("POST /api/tools/port-probe", s.handleToolsPortProbe) s.mux.HandleFunc("POST /api/tools/mtr", s.handleToolsMTR) // Bandwidth Test Tool (/tool bandwidth-test / BwTest) s.mux.HandleFunc("GET /api/tools/bwtest/server", s.handleGetBwTestServer) s.mux.HandleFunc("POST /api/tools/bwtest/server", s.handleUpdateBwTestServer) s.mux.HandleFunc("POST /api/tools/bwtest/start", s.handleStartBwTest) s.mux.HandleFunc("POST /api/tools/bwtest/stop", s.handleStopBwTest) s.mux.HandleFunc("GET /api/tools/bwtest/status", s.handleGetBwTestStatus) s.mux.HandleFunc("GET /api/tools/bwtest/stream", s.handleStreamBwTest) s.mux.HandleFunc("GET /api/tools/bwtest/history", s.handleGetBwTestHistory) // DHCP Client (/ip dhcp-client) s.mux.HandleFunc("GET /api/dhcp/clients", s.handleListDHCPClients) s.mux.HandleFunc("GET /api/dhcp/clients/summary", s.handleGetDHCPSummary) s.mux.HandleFunc("POST /api/dhcp/clients", s.handleCreateDHCPClient) s.mux.HandleFunc("PUT /api/dhcp/clients/{id}", s.handleUpdateDHCPClient) s.mux.HandleFunc("DELETE /api/dhcp/clients/{id}", s.handleDeleteDHCPClient) s.mux.HandleFunc("POST /api/dhcp/clients/{id}/state", s.handleToggleDHCPClientState) s.mux.HandleFunc("POST /api/dhcp/clients/{id}/renew", s.handleRenewDHCPClient) s.mux.HandleFunc("POST /api/dhcp/clients/{id}/release", s.handleReleaseDHCPClient) // DHCP Server (/ip dhcp-server) s.mux.HandleFunc("GET /api/dhcp-server/summary", s.handleGetDHCPServerSummary) s.mux.HandleFunc("GET /api/dhcp-server/servers", s.handleListDHCPServers) s.mux.HandleFunc("POST /api/dhcp-server/servers", s.handleSaveDHCPServer) s.mux.HandleFunc("DELETE /api/dhcp-server/servers/{id}", s.handleDeleteDHCPServer) s.mux.HandleFunc("POST /api/dhcp-server/servers/{id}/state", s.handleSetDHCPServerState) s.mux.HandleFunc("GET /api/dhcp-server/networks", s.handleListDHCPNetworks) s.mux.HandleFunc("POST /api/dhcp-server/networks", s.handleSaveDHCPNetwork) s.mux.HandleFunc("DELETE /api/dhcp-server/networks/{id}", s.handleDeleteDHCPNetwork) s.mux.HandleFunc("GET /api/dhcp-server/leases", s.handleListDHCPLeases) s.mux.HandleFunc("POST /api/dhcp-server/leases/static", s.handleSaveDHCPStaticLease) s.mux.HandleFunc("DELETE /api/dhcp-server/leases/{id}", s.handleDeleteDHCPStaticLease) s.mux.HandleFunc("POST /api/dhcp-server/leases/make-static", s.handleMakeDHCPStaticLease) s.mux.HandleFunc("GET /api/dhcp-server/options", s.handleListDHCPOptions) s.mux.HandleFunc("POST /api/dhcp-server/options", s.handleSaveDHCPOption) s.mux.HandleFunc("DELETE /api/dhcp-server/options/{id}", s.handleDeleteDHCPOption) s.mux.HandleFunc("POST /api/dhcp-server/setup", s.handleRunDHCPSetup) // VRRP Redundancy & High Availability s.mux.HandleFunc("GET /api/vrrp/summary", s.handleGetVRRPSummary) s.mux.HandleFunc("GET /api/vrrp/instances", s.handleListVRRPInstances) s.mux.HandleFunc("POST /api/vrrp/instances", s.handleSaveVRRPInstance) s.mux.HandleFunc("DELETE /api/vrrp/instances/{id}", s.handleDeleteVRRPInstance) s.mux.HandleFunc("POST /api/vrrp/instances/{id}/state", s.handleToggleVRRPState) // Web UI Static Files if s.fsHandler != nil { s.mux.Handle("/", s.fsHandler) } } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Enable CORS for local development/testing w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS, PUT") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } s.mux.ServeHTTP(w, r) } func (s *Server) handleSystemStatus(w http.ResponseWriter, r *http.Request) { status, err := system.GetStatus() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } ifaces, err := s.netMgr.ListInterfaces() if err == nil { status.TotalIfaces = len(ifaces) for _, iface := range ifaces { if iface.State == "UP" { status.UpIfaces++ } else { status.DownIfaces++ } if iface.IsVLAN { status.VlanIfaces++ } } } writeJSON(w, http.StatusOK, status) } func (s *Server) handleRoutes(w http.ResponseWriter, r *http.Request) { routes, err := system.GetRoutingTable() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, routes) } func (s *Server) handleListInterfaces(w http.ResponseWriter, r *http.Request) { ifaces, err := s.netMgr.ListInterfaces() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, ifaces) } func (s *Server) handleGetInterface(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") iface, err := s.netMgr.GetInterface(name) if err != nil { writeError(w, http.StatusNotFound, err.Error()) return } writeJSON(w, http.StatusOK, iface) } func (s *Server) handleSetInterfaceState(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req struct { State string `json:"state"` // "up" or "down" } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } up := strings.ToLower(req.State) == "up" if err := s.netMgr.SetInterfaceState(name, up); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name, "state": req.State}) } func (s *Server) handleSetInterfaceMTU(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req struct { MTU int `json:"mtu"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.netMgr.SetInterfaceMTU(name, req.MTU); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name, "mtu": req.MTU}) } func (s *Server) handleAddInterfaceIP(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req network.AddIPRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.IP == "" { writeError(w, http.StatusBadRequest, "missing or invalid 'ip' parameter") return } if err := s.netMgr.AddInterfaceIP(name, req.IP); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } // If this interface is a bridge, also sync to BridgeManager persistent config if s.bridgeMgr != nil { _ = s.bridgeMgr.AddBridgeIP(name, req.IP) } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name, "ip": req.IP}) } func (s *Server) handleDeleteInterfaceIP(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req network.AddIPRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.IP == "" { writeError(w, http.StatusBadRequest, "missing or invalid 'ip' parameter") return } if err := s.netMgr.DeleteInterfaceIP(name, req.IP); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } // If this interface is a bridge, also delete from BridgeManager persistent config if s.bridgeMgr != nil { _ = s.bridgeMgr.DeleteBridgeIP(name, req.IP) } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name, "ip": req.IP}) } func (s *Server) handleSetInterfaceMetadata(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req struct { Role string `json:"role"` Description string `json:"description"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := s.metaMgr.Set(name, req.Role, req.Description); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name, "role": req.Role, "description": req.Description}) } func (s *Server) handleListVLANs(w http.ResponseWriter, r *http.Request) { vlans, err := s.netMgr.ListVLANs() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, vlans) } func (s *Server) handleCreateVLAN(w http.ResponseWriter, r *http.Request) { var req network.CreateVLANRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.ParentName == "" || req.VlanID <= 0 { writeError(w, http.StatusBadRequest, "parent_name and valid vlan_id (1-4094) are required") return } iface, err := s.netMgr.CreateVLAN(req) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusCreated, iface) } func (s *Server) handleDeleteVLAN(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") if err := s.netMgr.DeleteVLAN(name); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"success": true, "name": name}) } func (s *Server) handleGetDebianConfig(w http.ResponseWriter, r *http.Request) { cfg, err := s.persistMgr.GenerateDebianConfig() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{ "path": network.DebianVlansCfgPath, "content": cfg, }) } func (s *Server) handleSaveDebianConfig(w http.ResponseWriter, r *http.Request) { cfg, err := s.persistMgr.SaveDebianConfig() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{ "success": true, "path": network.DebianVlansCfgPath, "content": cfg, "message": "Configuration saved to " + network.DebianVlansCfgPath, }) } func writeJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(data) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]any{ "error": true, "message": message, }) }