Remove legacy default-server page, unify on multi-server registry
Deletes /wg-server (route, handlers, template) entirely and adds generic /servers/:id/interface and /servers/:id/keypair endpoints + UI in the All Servers page, so every server (including the default one) is managed through the same per-server registry. Drops the write-through dual-write hacks that kept the old single-server collection in sync - the registry is now the single source of truth. One-time legacy-install migration path is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2212141ba3
commit
58db6839c3
@@ -1453,3 +1453,143 @@ Keine Commits in dieser Session.
|
|||||||
- templates/global_settings.html | 93 ++++++++++++++++++++++++++++++++++++++++++
|
- templates/global_settings.html | 93 ++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 2026-07-12 12:54 – 13:03 (9m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
- 2212141 Clarify server nav labels (Default Server Interface vs All Servers)
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:13 – 13:24 (11m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:25 – 13:25 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:26 – 13:27 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:29 – 13:29 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:30 – 13:30 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:33 – 13:35 (1m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 13:38 – 13:38 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 +-
|
||||||
|
- templates/server.html | 6 +--
|
||||||
|
- templates/servers.html | 4 +-
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 16:20 – 16:20 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 ++--
|
||||||
|
- templates/server.html | 6 +++---
|
||||||
|
- templates/servers.html | 4 ++--
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-07-12 16:20 – 16:21 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** wireguard-ui-multi
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
- templates/base.html | 4 ++--
|
||||||
|
- templates/server.html | 6 +++---
|
||||||
|
- templates/servers.html | 4 ++--
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
+36
-72
@@ -35,8 +35,8 @@ var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$")
|
|||||||
|
|
||||||
// resolveServerID returns the server ID a request targets: the :id route
|
// resolveServerID returns the server ID a request targets: the :id route
|
||||||
// param for /servers/:id/... routes, or util.DefaultServerID for the
|
// param for /servers/:id/... routes, or util.DefaultServerID for the
|
||||||
// legacy bare routes (/, /new-client, /wg-server, ...) which always
|
// legacy bare routes (/, /new-client, ...) which always operate on the
|
||||||
// operate on the migrated default server.
|
// migrated default server.
|
||||||
func resolveServerID(c echo.Context) string {
|
func resolveServerID(c echo.Context) string {
|
||||||
if id := c.Param("id"); id != "" {
|
if id := c.Param("id"); id != "" {
|
||||||
return id
|
return id
|
||||||
@@ -1168,27 +1168,22 @@ func RemoveClient(db store.IStore) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireGuardServer handler
|
// UpdateServerInterfaceHandler updates the WireGuard interface settings
|
||||||
func WireGuardServer(db store.IStore) echo.HandlerFunc {
|
// (addresses, listen port, up/down scripts) for a single server, identified
|
||||||
|
// by :id. This is the multi-server-aware handler surfaced from the "All
|
||||||
|
// Servers" page; the per-server registry (servers/<id>.json) is the single
|
||||||
|
// source of truth for every server, including the default one.
|
||||||
|
func UpdateServerInterfaceHandler(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
server, err := db.GetServer()
|
serverID := c.Param("id")
|
||||||
if err != nil {
|
if _, err := db.GetServerByID(serverID); err != nil {
|
||||||
log.Error("Cannot get server config: ", err)
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "server.html", map[string]interface{}{
|
|
||||||
"baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
|
||||||
"serverInterface": server.Interface,
|
|
||||||
"serverKeyPair": server.KeyPair,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WireGuardServerInterfaces handler
|
|
||||||
func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
var serverInterface model.ServerInterface
|
var serverInterface model.ServerInterface
|
||||||
c.Bind(&serverInterface)
|
if err := c.Bind(&serverInterface); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||||
|
}
|
||||||
|
|
||||||
// validate the input addresses
|
// validate the input addresses
|
||||||
if util.ValidateServerAddresses(serverInterface.Addresses) == false {
|
if util.ValidateServerAddresses(serverInterface.Addresses) == false {
|
||||||
@@ -1198,31 +1193,27 @@ func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc {
|
|||||||
|
|
||||||
serverInterface.UpdatedAt = time.Now().UTC()
|
serverInterface.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
// write config to the database (legacy single-server collection)
|
if err := db.UpdateServerInterface(serverID, serverInterface); err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot update server interface: %v", err)})
|
||||||
if err := db.SaveServerInterface(serverInterface); err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Interface IP address must be in CIDR format"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the new per-server registry record for the default server in
|
log.Infof("Updated wireguard server interface settings for server %s: %v", serverID, serverInterface)
|
||||||
// sync, since this legacy route is the only way to edit it today
|
|
||||||
if serverInterface.Name == "" {
|
|
||||||
serverInterface.Name = util.DefaultServerID
|
|
||||||
}
|
|
||||||
if err := db.UpdateServerInterface(util.DefaultServerID, serverInterface); err != nil {
|
|
||||||
log.Warnf("Could not sync default server registry entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Updated wireguard server interfaces settings: %v", serverInterface)
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"})
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireGuardServerKeyPair handler to generate private and public keys
|
// UpdateServerKeyPairHandler generates a fresh WireGuard key pair for a
|
||||||
func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc {
|
// single server, identified by :id. The per-server registry
|
||||||
|
// (servers/<id>.json) is the single source of truth for every server,
|
||||||
|
// including the default one.
|
||||||
|
func UpdateServerKeyPairHandler(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
// gen Wireguard key pair
|
serverID := c.Param("id")
|
||||||
|
if _, err := db.GetServerByID(serverID); err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"})
|
||||||
|
}
|
||||||
|
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot generate wireguard key pair: ", err)
|
log.Error("Cannot generate wireguard key pair: ", err)
|
||||||
@@ -1234,17 +1225,11 @@ func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc {
|
|||||||
serverKeyPair.PublicKey = key.PublicKey().String()
|
serverKeyPair.PublicKey = key.PublicKey().String()
|
||||||
serverKeyPair.UpdatedAt = time.Now().UTC()
|
serverKeyPair.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
if err := db.SaveServerKeyPair(serverKeyPair); err != nil {
|
if err := db.UpdateServerKeyPair(serverID, serverKeyPair); err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot update server key pair: %v", err)})
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the new per-server registry record for the default server in
|
log.Infof("Updated wireguard server key pair for server %s", serverID)
|
||||||
// sync, since this legacy route is the only way to edit it today
|
|
||||||
if err := db.UpdateServerKeyPair(util.DefaultServerID, serverKeyPair); err != nil {
|
|
||||||
log.Warnf("Could not sync default server registry entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Updated wireguard server interfaces settings: %v", serverKeyPair)
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, serverKeyPair)
|
return c.JSON(http.StatusOK, serverKeyPair)
|
||||||
}
|
}
|
||||||
@@ -1379,26 +1364,14 @@ func GlobalSettingSubmit(db store.IStore) echo.HandlerFunc {
|
|||||||
|
|
||||||
globalSettings.UpdatedAt = time.Now().UTC()
|
globalSettings.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
// write config to the database (legacy global collection)
|
// write the app-wide settings (DNS/MTU/PersistentKeepalive); per-server
|
||||||
|
// concerns (EndpointAddress/Table/FirewallMark/ConfigFilePath) live
|
||||||
|
// solely in the per-server registry's ServerSetting record, edited via
|
||||||
|
// the per-server settings editor, and are not duplicated here.
|
||||||
if err := db.SaveGlobalSettings(globalSettings); err != nil {
|
if err := db.SaveGlobalSettings(globalSettings); err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the new per-server registry's ServerSetting record for the
|
|
||||||
// default server in sync (EndpointAddress/Table/FirewallMark/
|
|
||||||
// ConfigFilePath are per-server concerns in the new model), since
|
|
||||||
// this legacy route is the only way to edit it today
|
|
||||||
serverSettings := model.ServerSetting{
|
|
||||||
EndpointAddress: globalSettings.EndpointAddress,
|
|
||||||
FirewallMark: globalSettings.FirewallMark,
|
|
||||||
Table: globalSettings.Table,
|
|
||||||
ConfigFilePath: globalSettings.ConfigFilePath,
|
|
||||||
UpdatedAt: globalSettings.UpdatedAt,
|
|
||||||
}
|
|
||||||
if err := db.SaveServerSettings(util.DefaultServerID, serverSettings); err != nil {
|
|
||||||
log.Warnf("Could not sync default server settings registry entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Updated global settings: %v", globalSettings)
|
log.Infof("Updated global settings: %v", globalSettings)
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated global settings successfully"})
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated global settings successfully"})
|
||||||
@@ -1438,7 +1411,7 @@ func GetOrderedSubnetRanges() echo.HandlerFunc {
|
|||||||
// SuggestIPAllocation handler to get the list of ip address for client
|
// SuggestIPAllocation handler to get the list of ip address for client
|
||||||
func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
server, err := db.GetServer()
|
server, err := db.GetServerByID(util.DefaultServerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot fetch server config from database: ", err)
|
log.Error("Cannot fetch server config from database: ", err)
|
||||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, err.Error()})
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, err.Error()})
|
||||||
@@ -1506,16 +1479,7 @@ func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc {
|
|||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
serverID := resolveServerID(c)
|
serverID := resolveServerID(c)
|
||||||
|
|
||||||
var server model.Server
|
server, err := db.GetServerByID(serverID)
|
||||||
var err error
|
|
||||||
if serverID == util.DefaultServerID {
|
|
||||||
// legacy bare route: keep reading from the legacy single-server
|
|
||||||
// collection, which is still the source of truth an operator
|
|
||||||
// may have just edited via /wg-server
|
|
||||||
server, err = db.GetServer()
|
|
||||||
} else {
|
|
||||||
server, err = db.GetServerByID(serverID)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot get server config: ", err)
|
log.Error("Cannot get server config: ", err)
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get server config"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get server config"})
|
||||||
|
|||||||
@@ -245,9 +245,6 @@ func main() {
|
|||||||
app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession)
|
app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin)
|
|
||||||
app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
|
||||||
app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
|
||||||
app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin)
|
app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin)
|
||||||
app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession, handler.RefreshSession)
|
app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession, handler.RefreshSession)
|
||||||
@@ -265,6 +262,8 @@ func main() {
|
|||||||
app.POST(util.BasePath+"/servers/:id/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson, 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.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.POST(util.BasePath+"/servers/:id/settings", handler.SaveServerSettingsHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
|
app.POST(util.BasePath+"/servers/:id/interface", handler.UpdateServerInterfaceHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
|
app.POST(util.BasePath+"/servers/:id/keypair", handler.UpdateServerKeyPairHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.POST(util.BasePath+"/backup/download", handler.DownloadBackup(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
app.POST(util.BasePath+"/backup/download", handler.DownloadBackup(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
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/client/:id", handler.GetClient(db), handler.ValidSession)
|
||||||
@@ -321,7 +320,7 @@ func initServerConfig(db store.IStore, tmplDir fs.FS) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := db.GetServer()
|
server, err := db.GetServerByID(util.DefaultServerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Cannot get server config: %v", err)
|
log.Fatalf("Cannot get server config: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,11 +81,6 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplServerString, err := util.StringFromEmbedFile(tmplDir, "server.html")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmplGlobalSettingsString, err := util.StringFromEmbedFile(tmplDir, "global_settings.html")
|
tmplGlobalSettingsString, err := util.StringFromEmbedFile(tmplDir, "global_settings.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -129,7 +124,6 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo
|
|||||||
templates["login.html"] = template.Must(template.New("login").Funcs(funcs).Parse(tmplLoginString))
|
templates["login.html"] = template.Must(template.New("login").Funcs(funcs).Parse(tmplLoginString))
|
||||||
templates["profile.html"] = template.Must(template.New("profile").Funcs(funcs).Parse(tmplBaseString + tmplProfileString))
|
templates["profile.html"] = template.Must(template.New("profile").Funcs(funcs).Parse(tmplBaseString + tmplProfileString))
|
||||||
templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString))
|
templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString))
|
||||||
templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString))
|
|
||||||
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
|
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
|
||||||
templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString))
|
templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString))
|
||||||
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
|
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
|
||||||
|
|||||||
+9
-48
@@ -325,11 +325,13 @@ func (o *JsonDB) migrateLegacyServer() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the legacy "server" directory is intentionally left in place
|
// NOTE: the legacy "server" directory is intentionally left in place
|
||||||
// (not renamed/removed) at this stage. Existing store methods (GetServer,
|
// (not renamed/removed). GetGlobalSettings/SaveGlobalSettings still read
|
||||||
// GetGlobalSettings, SaveServerInterface, ...) still read/write it
|
// and write the app-wide settings record there (DNS/MTU/
|
||||||
// directly until they are switched over to the new per-server layout;
|
// PersistentKeepalive are not yet per-server concerns); every other
|
||||||
// removing it now would break the running app. The rename-to-backup
|
// server-scoped read/write now goes exclusively through the per-server
|
||||||
// step happens once those methods are migrated.
|
// registry (servers/<id>.json via GetServerByID/UpdateServerInterface/
|
||||||
|
// UpdateServerKeyPair/GetServerSettings), so this directory is kept only
|
||||||
|
// as a landing spot for one-time migration and for global settings.
|
||||||
log.Printf("Migrated legacy single-server DB to multi-server layout (server id: %s); legacy db/server/ kept in place for now", serverID)
|
log.Printf("Migrated legacy single-server DB to multi-server layout (server id: %s); legacy db/server/ kept in place for now", serverID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -387,27 +389,6 @@ func (o *JsonDB) GetGlobalSettings() (model.GlobalSetting, error) {
|
|||||||
return settings, o.conn.Read("server", "global_settings", &settings)
|
return settings, o.conn.Read("server", "global_settings", &settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServer func to query Server settings from the database
|
|
||||||
func (o *JsonDB) GetServer() (model.Server, error) {
|
|
||||||
server := model.Server{}
|
|
||||||
// read server interface information
|
|
||||||
serverInterface := model.ServerInterface{}
|
|
||||||
if err := o.conn.Read("server", "interfaces", &serverInterface); err != nil {
|
|
||||||
return server, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// read server key pair information
|
|
||||||
serverKeyPair := model.ServerKeypair{}
|
|
||||||
if err := o.conn.Read("server", "keypair", &serverKeyPair); err != nil {
|
|
||||||
return server, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create Server object and return
|
|
||||||
server.Interface = &serverInterface
|
|
||||||
server.KeyPair = &serverKeyPair
|
|
||||||
return server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) {
|
func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) {
|
||||||
var clients []model.ClientData
|
var clients []model.ClientData
|
||||||
|
|
||||||
@@ -429,7 +410,7 @@ func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) {
|
|||||||
|
|
||||||
// generate client qrcode image in base64
|
// generate client qrcode image in base64
|
||||||
if hasQRCode && client.PrivateKey != "" {
|
if hasQRCode && client.PrivateKey != "" {
|
||||||
server, _ := o.GetServer()
|
server, _ := o.GetServerByID(util.DefaultServerID)
|
||||||
globalSettings, _ := o.GetGlobalSettings()
|
globalSettings, _ := o.GetGlobalSettings()
|
||||||
|
|
||||||
png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256)
|
png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256)
|
||||||
@@ -459,7 +440,7 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti
|
|||||||
|
|
||||||
// generate client qrcode image in base64
|
// generate client qrcode image in base64
|
||||||
if qrCodeSettings.Enabled && client.PrivateKey != "" {
|
if qrCodeSettings.Enabled && client.PrivateKey != "" {
|
||||||
server, _ := o.GetServer()
|
server, _ := o.GetServerByID(util.DefaultServerID)
|
||||||
globalSettings, _ := o.GetGlobalSettings()
|
globalSettings, _ := o.GetGlobalSettings()
|
||||||
client := client
|
client := client
|
||||||
if !qrCodeSettings.IncludeDNS {
|
if !qrCodeSettings.IncludeDNS {
|
||||||
@@ -508,26 +489,6 @@ func (o *JsonDB) DeleteClient(clientID string) error {
|
|||||||
return o.conn.Delete("clients", clientID)
|
return o.conn.Delete("clients", clientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error {
|
|
||||||
serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json")
|
|
||||||
output := o.conn.Write("server", "interfaces", serverInterface)
|
|
||||||
err := util.ManagePerms(serverInterfacePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error {
|
|
||||||
serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json")
|
|
||||||
output := o.conn.Write("server", "keypair", serverKeyPair)
|
|
||||||
err := util.ManagePerms(serverKeyPairPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
|
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
|
||||||
globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json")
|
globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json")
|
||||||
output := o.conn.Write("server", "global_settings", globalSettings)
|
output := o.conn.Write("server", "global_settings", globalSettings)
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ type IStore interface {
|
|||||||
SaveUser(user model.User) error
|
SaveUser(user model.User) error
|
||||||
DeleteUser(username string) error
|
DeleteUser(username string) error
|
||||||
GetGlobalSettings() (model.GlobalSetting, error)
|
GetGlobalSettings() (model.GlobalSetting, error)
|
||||||
GetServer() (model.Server, error)
|
|
||||||
GetClients(hasQRCode bool) ([]model.ClientData, error)
|
GetClients(hasQRCode bool) ([]model.ClientData, error)
|
||||||
GetClientByID(clientID string, qrCode model.QRCodeSettings) (model.ClientData, error)
|
GetClientByID(clientID string, qrCode model.QRCodeSettings) (model.ClientData, error)
|
||||||
SaveClient(client model.Client) error
|
SaveClient(client model.Client) error
|
||||||
DeleteClient(clientID string) error
|
DeleteClient(clientID string) error
|
||||||
SaveServerInterface(serverInterface model.ServerInterface) error
|
|
||||||
SaveServerKeyPair(serverKeyPair model.ServerKeypair) error
|
|
||||||
SaveGlobalSettings(globalSettings model.GlobalSetting) error
|
SaveGlobalSettings(globalSettings model.GlobalSetting) error
|
||||||
GetWakeOnLanHosts() ([]model.WakeOnLanHost, error)
|
GetWakeOnLanHosts() ([]model.WakeOnLanHost, error)
|
||||||
GetWakeOnLanHost(macAddress string) (*model.WakeOnLanHost, error)
|
GetWakeOnLanHost(macAddress string) (*model.WakeOnLanHost, error)
|
||||||
|
|||||||
@@ -128,16 +128,6 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{if .baseData.Admin}}
|
{{if .baseData.Admin}}
|
||||||
<li class="nav-item">
|
|
||||||
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
|
|
||||||
<i class="nav-icon fas fa-server"></i>
|
|
||||||
<p>
|
|
||||||
Default Server Interface
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
<li class="nav-header">SETTINGS</li>
|
<li class="nav-header">SETTINGS</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{.basePath}}/servers-settings" class="nav-link {{if eq .baseData.Active "servers" }}active{{end}}">
|
<a href="{{.basePath}}/servers-settings" class="nav-link {{if eq .baseData.Active "servers" }}active{{end}}">
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
{{define "title"}}
|
|
||||||
Default Server Interface
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "top_css"}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "username"}}
|
|
||||||
{{ .username }}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "page_title"}}
|
|
||||||
Default Server Interface Settings
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "page_content"}}
|
|
||||||
<section class="content">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<!-- <h5 class="mt-4 mb-2">Default Server Interface</h5> -->
|
|
||||||
<div class="row">
|
|
||||||
<!-- left column -->
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card card-success">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Interface</h3>
|
|
||||||
</div>
|
|
||||||
<!-- /.card-header -->
|
|
||||||
<!-- form start -->
|
|
||||||
<form role="form" id="frm_server_interface" name="frm_server_interface">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="addresses" class="control-label">Server Interface Addresses</label>
|
|
||||||
<input type="text" data-role="tagsinput" class="form-control" id="addresses" value="">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="listen_port">Listen Port</label>
|
|
||||||
<input type="text" class="form-control" id="listen_port" name="listen_port"
|
|
||||||
placeholder="Listen Port" value="{{ .serverInterface.ListenPort }}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="post_up">Post Up Script</label>
|
|
||||||
<input type="text" class="form-control" id="post_up" name="post_up"
|
|
||||||
placeholder="Post Up Script" value="{{ .serverInterface.PostUp }}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="pre_down">Pre Down Script</label>
|
|
||||||
<input type="text" class="form-control" id="pre_down" name="pre_down"
|
|
||||||
placeholder="Pre Down Script" value="{{ .serverInterface.PreDown }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="post_down">Post Down Script</label>
|
|
||||||
<input type="text" class="form-control" id="post_down" name="post_down"
|
|
||||||
placeholder="Post Down Script" value="{{ .serverInterface.PostDown }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.card-body -->
|
|
||||||
|
|
||||||
<div class="card-footer">
|
|
||||||
<button type="submit" class="btn btn-success">Save</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- /.card -->
|
|
||||||
</div>
|
|
||||||
<!-- right column -->
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card card-danger">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Key Pair</h3>
|
|
||||||
</div>
|
|
||||||
<!-- /.card-header -->
|
|
||||||
<!-- form start -->
|
|
||||||
<form role="form">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="private_key">Private Key</label>
|
|
||||||
<div class="input-group input-group">
|
|
||||||
<input type="password" class="form-control" id="private_key" placeholder="Private Key"
|
|
||||||
value="{{ .serverKeyPair.PrivateKey }}" disabled>
|
|
||||||
<span class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-danger btn-flat"
|
|
||||||
id="btn_show_private_key">Show</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="public_key">Public Key</label>
|
|
||||||
<input type="text" class="form-control" id="public_key" placeholder="Public Key"
|
|
||||||
value="{{ .serverKeyPair.PublicKey }}" disabled>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.card-body -->
|
|
||||||
|
|
||||||
<div class="card-footer">
|
|
||||||
<button type="button" class="btn btn-danger" data-toggle="modal"
|
|
||||||
data-target="#modal_keypair_confirmation">Generate</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- /.card -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.row -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="modal fade" id="modal_keypair_confirmation">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content bg-warning">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">KeyPair Generation</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure to generate a new key pair for the Wireguard server?<br/>
|
|
||||||
The existing Client's peer public key need to be updated to keep the connection working.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer justify-content-between">
|
|
||||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="button" class="btn btn-outline-dark" id="btn_generate_confirm">Generate</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.modal-content -->
|
|
||||||
</div>
|
|
||||||
<!-- /.modal-dialog -->
|
|
||||||
</div>
|
|
||||||
<!-- /.modal -->
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "bottom_js"}}
|
|
||||||
<script>
|
|
||||||
function submitServerInterfaceSetting() {
|
|
||||||
const addresses = $("#addresses").val().split(",");
|
|
||||||
const listen_port = $("#listen_port").val();
|
|
||||||
const post_up = $("#post_up").val();
|
|
||||||
const pre_down = $("#pre_down").val();
|
|
||||||
const post_down = $("#post_down").val();
|
|
||||||
const data = {"addresses": addresses, "listen_port": listen_port, "post_up": post_up, "pre_down": pre_down, "post_down": post_down};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
cache: false,
|
|
||||||
method: 'POST',
|
|
||||||
url: '{{.basePath}}/wg-server/interfaces',
|
|
||||||
dataType: 'json',
|
|
||||||
contentType: "application/json",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
success: function(data) {
|
|
||||||
$("#modal_new_client").modal('hide');
|
|
||||||
toastr.success('Updated Wireguard server interface addresses successfully');
|
|
||||||
},
|
|
||||||
error: function(jqXHR, exception) {
|
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
|
||||||
toastr.error(responseJson['message']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
// Wireguard Interface Addresses tag input
|
|
||||||
$("#addresses").tagsInput({
|
|
||||||
'width': '100%',
|
|
||||||
// 'height': '75%',
|
|
||||||
'interactive': true,
|
|
||||||
'defaultText': 'Add More',
|
|
||||||
'removeWithBackspace': true,
|
|
||||||
'minChars': 0,
|
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load server addresses to the form
|
|
||||||
{{range .serverInterface.Addresses}}
|
|
||||||
$("#addresses").removeTag('{{.}}');
|
|
||||||
$("#addresses").addTag('{{.}}');
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// Wireguard Interface Addresses form validation
|
|
||||||
$(document).ready(function () {
|
|
||||||
$.validator.setDefaults({
|
|
||||||
submitHandler: function () {
|
|
||||||
submitServerInterfaceSetting();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#frm_server_interface").validate({
|
|
||||||
rules: {
|
|
||||||
listen_port: {
|
|
||||||
required: true,
|
|
||||||
digits: true,
|
|
||||||
range: [1, 65535]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
listen_port: {
|
|
||||||
required: "Please enter a port",
|
|
||||||
digits: "Port must be an integer",
|
|
||||||
range: "Port must be in range 1..65535"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
errorElement: 'span',
|
|
||||||
errorPlacement: function (error, element) {
|
|
||||||
error.addClass('invalid-feedback');
|
|
||||||
element.closest('.form-group').append(error);
|
|
||||||
},
|
|
||||||
highlight: function (element, errorClass, validClass) {
|
|
||||||
$(element).addClass('is-invalid');
|
|
||||||
},
|
|
||||||
unhighlight: function (element, errorClass, validClass) {
|
|
||||||
$(element).removeClass('is-invalid');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wireguard Key Pair generation confirmation button
|
|
||||||
$(document).ready(function () {
|
|
||||||
$("#btn_generate_confirm").click(function () {
|
|
||||||
$.ajax({
|
|
||||||
cache: false,
|
|
||||||
method: 'POST',
|
|
||||||
url: '{{.basePath}}/wg-server/keypair',
|
|
||||||
dataType: 'json',
|
|
||||||
contentType: "application/json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#modal_keypair_confirmation").modal('hide');
|
|
||||||
toastr.success('Generate new key pair successfully');
|
|
||||||
// update the UI
|
|
||||||
$("#private_key").val(data['private_key']);
|
|
||||||
$("#public_key").val(data['public_key']);
|
|
||||||
},
|
|
||||||
error: function(jqXHR, exception) {
|
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
|
||||||
toastr.error(responseJson['message']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show private key button event
|
|
||||||
$(document).ready(function () {
|
|
||||||
$("#btn_show_private_key").click(function () {
|
|
||||||
const privateElement = document.getElementById("private_key");
|
|
||||||
const btnElement = document.getElementById("btn_show_private_key");
|
|
||||||
if (privateElement.type === 'password') {
|
|
||||||
privateElement.type = 'text';
|
|
||||||
btnElement.innerText = 'Hide';
|
|
||||||
} else {
|
|
||||||
privateElement.type = 'password';
|
|
||||||
btnElement.innerText = 'Show';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -114,12 +114,73 @@ All Servers
|
|||||||
<!-- /.modal-dialog -->
|
<!-- /.modal-dialog -->
|
||||||
</div>
|
</div>
|
||||||
<!-- /.modal -->
|
<!-- /.modal -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="modal_server_interface">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Server Interface</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form name="frm_server_interface" id="frm_server_interface">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="_iface_server_id" name="_iface_server_id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_iface_addresses" class="control-label">Server Interface Addresses</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_addresses"
|
||||||
|
placeholder="e.g. 10.20.22.1/24, fd00::1/64">
|
||||||
|
<small class="form-text text-muted">Comma-separated list of address ranges.</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_iface_listen_port" class="control-label">Listen Port</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_listen_port" name="_iface_listen_port"
|
||||||
|
placeholder="e.g. 51822">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_iface_post_up" class="control-label">Post Up Script</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_post_up" placeholder="Post Up Script">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_iface_pre_down" class="control-label">Pre Down Script</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_pre_down" placeholder="Pre Down Script">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_iface_post_down" class="control-label">Post Down Script</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_post_down" placeholder="Post Down Script">
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">Public Key</label>
|
||||||
|
<input type="text" class="form-control" id="_iface_public_key" disabled>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm" id="btn_regenerate_keypair">
|
||||||
|
Regenerate Keypair
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-between">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-success">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-content -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-dialog -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal -->
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "bottom_js"}}
|
{{define "bottom_js"}}
|
||||||
<script>
|
<script>
|
||||||
|
// cache of the last-fetched server list, keyed by id, so the Interface
|
||||||
|
// modal can prefill fields without a second round trip
|
||||||
|
var serversById = {};
|
||||||
|
|
||||||
function renderServersList(data) {
|
function renderServersList(data) {
|
||||||
$.each(data, function (index, obj) {
|
$.each(data, function (index, obj) {
|
||||||
|
serversById[obj.id] = obj;
|
||||||
const addresses = (obj.Interface && obj.Interface.addresses) ? obj.Interface.addresses.join(", ") : "";
|
const addresses = (obj.Interface && obj.Interface.addresses) ? obj.Interface.addresses.join(", ") : "";
|
||||||
const listenPort = obj.Interface ? obj.Interface.listen_port : "";
|
const listenPort = obj.Interface ? obj.Interface.listen_port : "";
|
||||||
const interfaceName = obj.Interface ? obj.Interface.name : "";
|
const interfaceName = obj.Interface ? obj.Interface.name : "";
|
||||||
@@ -138,6 +199,10 @@ All Servers
|
|||||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal"
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal"
|
||||||
data-target="#modal_server_settings" data-serverid="${obj.id}">Settings</button>
|
data-target="#modal_server_settings" data-serverid="${obj.id}">Settings</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal"
|
||||||
|
data-target="#modal_server_interface" data-serverid="${obj.id}">Interface</button>
|
||||||
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<span class="info-box-text"><i class="fas fa-server"></i> ${safeName}</span>
|
<span class="info-box-text"><i class="fas fa-server"></i> ${safeName}</span>
|
||||||
<span class="info-box-text"><i class="fas fa-fingerprint"></i> ID: ${obj.id}</span>
|
<span class="info-box-text"><i class="fas fa-fingerprint"></i> ID: ${obj.id}</span>
|
||||||
@@ -293,6 +358,118 @@ All Servers
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Server interface modal event: prefill from the cached server list data
|
||||||
|
$("#modal_server_interface").on('show.bs.modal', function (event) {
|
||||||
|
const button = $(event.relatedTarget);
|
||||||
|
const serverId = button.data('serverid');
|
||||||
|
const modal = $(this);
|
||||||
|
const obj = serversById[serverId] || {};
|
||||||
|
|
||||||
|
modal.find("#_iface_server_id").val(serverId);
|
||||||
|
modal.find("#_iface_addresses").val((obj.Interface && obj.Interface.addresses) ? obj.Interface.addresses.join(", ") : "");
|
||||||
|
modal.find("#_iface_listen_port").val(obj.Interface ? obj.Interface.listen_port : "");
|
||||||
|
modal.find("#_iface_post_up").val(obj.Interface ? obj.Interface.post_up : "");
|
||||||
|
modal.find("#_iface_pre_down").val(obj.Interface ? obj.Interface.pre_down : "");
|
||||||
|
modal.find("#_iface_post_down").val(obj.Interface ? obj.Interface.post_down : "");
|
||||||
|
modal.find("#_iface_public_key").val(obj.KeyPair ? obj.KeyPair.public_key : "");
|
||||||
|
});
|
||||||
|
|
||||||
|
function submitServerInterface() {
|
||||||
|
const serverId = $("#_iface_server_id").val();
|
||||||
|
const addresses = $("#_iface_addresses").val().split(",").map(function (a) {
|
||||||
|
return a.trim();
|
||||||
|
}).filter(function (a) {
|
||||||
|
return a !== "";
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
"addresses": addresses,
|
||||||
|
"listen_port": $("#_iface_listen_port").val(),
|
||||||
|
"post_up": $("#_iface_post_up").val(),
|
||||||
|
"pre_down": $("#_iface_pre_down").val(),
|
||||||
|
"post_down": $("#_iface_post_down").val()
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/servers/' + serverId + '/interface',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
$("#modal_server_interface").modal('hide');
|
||||||
|
toastr.success("Updated server interface successfully");
|
||||||
|
$('#servers-list').empty();
|
||||||
|
populateServersList();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#frm_server_interface").validate({
|
||||||
|
rules: {
|
||||||
|
_iface_listen_port: {
|
||||||
|
required: true,
|
||||||
|
digits: true,
|
||||||
|
range: [1, 65535]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
_iface_listen_port: {
|
||||||
|
required: "Please enter a port",
|
||||||
|
digits: "Port must be an integer",
|
||||||
|
range: "Port must be in range 1..65535"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorElement: 'span',
|
||||||
|
errorPlacement: function (error, element) {
|
||||||
|
error.addClass('invalid-feedback');
|
||||||
|
element.closest('.form-group').append(error);
|
||||||
|
},
|
||||||
|
highlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).addClass('is-invalid');
|
||||||
|
},
|
||||||
|
unhighlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).removeClass('is-invalid');
|
||||||
|
},
|
||||||
|
submitHandler: function (form) {
|
||||||
|
submitServerInterface();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regenerate keypair button: confirm, then POST and refresh the field
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#btn_regenerate_keypair").click(function () {
|
||||||
|
const serverId = $("#_iface_server_id").val();
|
||||||
|
if (!confirm("Are you sure you want to generate a new key pair for this server?\n" +
|
||||||
|
"All existing peers will lose connectivity until their configuration is updated with the new public key.")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/servers/' + serverId + '/keypair',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function (data) {
|
||||||
|
toastr.success("Generated new key pair successfully");
|
||||||
|
$("#_iface_public_key").val(data['public_key']);
|
||||||
|
$('#servers-list').empty();
|
||||||
|
populateServersList();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$.validator.setDefaults({
|
$.validator.setDefaults({
|
||||||
submitHandler: function (form) {
|
submitHandler: function (form) {
|
||||||
|
|||||||
+2
-2
@@ -491,7 +491,7 @@ func ValidateAndFixSubnetRanges(db store.IStore) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := db.GetServer()
|
server, err := db.GetServerByID(DefaultServerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -639,7 +639,7 @@ func SendRequestedConfigsToTelegram(db store.IStore, userid int64) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build config
|
// build config
|
||||||
server, _ := db.GetServer()
|
server, _ := db.GetServerByID(DefaultServerID)
|
||||||
globalSettings, _ := db.GetGlobalSettings()
|
globalSettings, _ := db.GetGlobalSettings()
|
||||||
config := BuildClientConfig(*clientData.Client, server, globalSettings)
|
config := BuildClientConfig(*clientData.Client, server, globalSettings)
|
||||||
configData := []byte(config)
|
configData := []byte(config)
|
||||||
|
|||||||
Reference in New Issue
Block a user