From 3fe3dc8eeb9bec0f94feea1779a8c2c979be3b19 Mon Sep 17 00:00:00 2001 From: sysops Date: Sat, 11 Jul 2026 23:50:47 +0200 Subject: [PATCH] Add per-server settings editor (step 5) New servers previously got a bare ConfigFilePath default with no way to set/edit EndpointAddress, FirewallMark, or Table afterward. Adds GET/POST /servers/:id/settings (view/save ServerSetting, admin-only for writes) and a "Settings" button + modal on each server card in servers.html. CreateServer now seeds FirewallMark/Table with the same defaults the legacy single-server bootstrap uses, instead of leaving them blank. --- handler/routes.go | 39 ++++++++++++++ main.go | 2 + templates/servers.html | 112 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/handler/routes.go b/handler/routes.go index cc8ae7d..261ac71 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -592,6 +592,43 @@ func GetServerClients(db store.IStore) echo.HandlerFunc { } } +// GetServerSettings handler returns a single server's per-server settings +// (endpoint address, table, firewall mark, config file path) as JSON. +func GetServerSettings(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + serverID := c.Param("id") + settings, err := db.GetServerSettings(serverID) + if err != nil { + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server settings not found"}) + } + return c.JSON(http.StatusOK, settings) + } +} + +// SaveServerSettingsHandler updates a single server's per-server settings. +// Admin-only (registered with handler.NeedsAdmin). +func SaveServerSettingsHandler(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + serverID := c.Param("id") + if _, err := db.GetServerByID(serverID); err != nil { + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"}) + } + + var settings model.ServerSetting + if err := c.Bind(&settings); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) + } + settings.UpdatedAt = time.Now().UTC() + + if err := db.SaveServerSettings(serverID, settings); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot save server settings: %v", err)}) + } + log.Infof("Updated settings for server %s", serverID) + + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated server settings successfully"}) + } +} + // CreateServer handler creates a new WireGuard server (step 3 of the // multi-server extension). Admin-only. Generates a fresh key pair, // validates the ID/interface name/subnet, and stores the server plus its @@ -658,6 +695,8 @@ func CreateServer(db store.IStore) echo.HandlerFunc { settings := model.ServerSetting{ ConfigFilePath: fmt.Sprintf("/etc/wireguard/%s.conf", req.Interface), + FirewallMark: util.DefaultFirewallMark, + Table: util.DefaultTable, UpdatedAt: time.Now().UTC(), } if err := db.SaveServerSettings(req.ID, settings); err != nil { diff --git a/main.go b/main.go index 15ec91e..1bfcdcf 100644 --- a/main.go +++ b/main.go @@ -263,6 +263,8 @@ func main() { app.POST(util.BasePath+"/servers/:id/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson, handler.RequireServerAccess(db)) app.GET(util.BasePath+"/servers/:id/download", handler.DownloadClient(db), handler.ValidSession, handler.RequireServerAccess(db)) app.POST(util.BasePath+"/servers/:id/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson, handler.RequireServerAccess(db)) + app.GET(util.BasePath+"/servers/:id/settings", handler.GetServerSettings(db), handler.ValidSession, handler.RequireServerAccess(db)) + app.POST(util.BasePath+"/servers/:id/settings", handler.SaveServerSettingsHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession) diff --git a/templates/servers.html b/templates/servers.html index debeb13..8c5baff 100644 --- a/templates/servers.html +++ b/templates/servers.html @@ -70,6 +70,50 @@ Servers + + + {{end}} {{define "bottom_js"}} @@ -90,6 +134,10 @@ Servers
Manage clients
+
+ +

${safeName} ID: ${obj.id} @@ -181,6 +229,70 @@ Servers }); } + // Server settings modal event: load current settings for the clicked server + $("#modal_server_settings").on('show.bs.modal', function (event) { + const button = $(event.relatedTarget); + const serverId = button.data('serverid'); + const modal = $(this); + modal.find("#_settings_server_id").val(serverId); + modal.find("#_settings_endpoint_address").val(""); + modal.find("#_settings_config_file_path").val(""); + modal.find("#_settings_firewall_mark").val(""); + modal.find("#_settings_table").val(""); + + $.ajax({ + cache: false, + method: 'GET', + url: '{{.basePath}}/servers/' + serverId + '/settings', + dataType: 'json', + contentType: "application/json", + success: function (settings) { + modal.find("#_settings_endpoint_address").val(settings.endpoint_address); + modal.find("#_settings_config_file_path").val(settings.config_file_path); + modal.find("#_settings_firewall_mark").val(settings.firewall_mark); + modal.find("#_settings_table").val(settings.table); + }, + error: function (jqXHR, exception) { + const responseJson = jQuery.parseJSON(jqXHR.responseText); + toastr.error(responseJson['message']); + } + }); + }); + + function submitServerSettings() { + const serverId = $("#_settings_server_id").val(); + const data = { + "endpoint_address": $("#_settings_endpoint_address").val(), + "config_file_path": $("#_settings_config_file_path").val(), + "firewall_mark": $("#_settings_firewall_mark").val(), + "table": $("#_settings_table").val() + }; + + $.ajax({ + cache: false, + method: 'POST', + url: '{{.basePath}}/servers/' + serverId + '/settings', + dataType: 'json', + contentType: "application/json", + data: JSON.stringify(data), + success: function (data) { + $("#modal_server_settings").modal('hide'); + toastr.success("Updated server settings successfully"); + }, + error: function (jqXHR, exception) { + const responseJson = jQuery.parseJSON(jqXHR.responseText); + toastr.error(responseJson['message']); + } + }); + } + + $(document).ready(function () { + $("#frm_server_settings").on('submit', function (e) { + e.preventDefault(); + submitServerSettings(); + }); + }); + $(document).ready(function () { $.validator.setDefaults({ submitHandler: function (form) {