diff --git a/pkg/rest/router.go b/pkg/rest/router.go index 2197538..9c11787 100644 --- a/pkg/rest/router.go +++ b/pkg/rest/router.go @@ -56,6 +56,7 @@ func (c *Context) getRouter(assets fs.FS, indexHtml []byte) *http.ServeMux { mux.Handle("/api/setup/general", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.setupHandler))))) mux.Handle("/api/setup/vpn", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.vpnSetupHandler))))) mux.Handle("/api/setup/templates", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.templateSetupHandler))))) + mux.Handle("/api/setup/restart-vpn", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.restartVPNHandler))))) mux.Handle("/api/scim-setup", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.scimSetupHandler))))) mux.Handle("/api/saml-setup", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.samlSetupHandler))))) mux.Handle("/api/saml-setup/{id}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.samlSetupElementHandler))))) diff --git a/pkg/rest/setup.go b/pkg/rest/setup.go index 3367e13..a754123 100644 --- a/pkg/rest/setup.go +++ b/pkg/rest/setup.go @@ -4,12 +4,14 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" "net" "net/http" "net/netip" "reflect" "strconv" "strings" + "time" "github.com/google/uuid" "github.com/in4it/wireguard-server/pkg/auth/oidc" @@ -371,6 +373,44 @@ func (c *Context) templateSetupHandler(w http.ResponseWriter, r *http.Request) { } } +func (c *Context) restartVPNHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + c.returnError(w, fmt.Errorf("unsupported method"), http.StatusBadRequest) + return + } + client := http.Client{ + Timeout: 10 * time.Second, + } + req, err := http.NewRequest(r.Method, "http://"+wireguard.CONFIGMANAGER_URI+"/restart-vpn", nil) + if err != nil { + c.returnError(w, fmt.Errorf("restart request error: %s", err), http.StatusBadRequest) + return + } + resp, err := client.Do(req) + if err != nil { + c.returnError(w, fmt.Errorf("restart error: %s", err), http.StatusBadRequest) + return + } + if resp.StatusCode != http.StatusAccepted { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + c.returnError(w, fmt.Errorf("restart error: got status code: %d. Response: %s", resp.StatusCode, bodyBytes), http.StatusBadRequest) + return + } + c.returnError(w, fmt.Errorf("restart error: got status code: %d. Couldn't get response", resp.StatusCode), http.StatusBadRequest) + return + } + + defer resp.Body.Close() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + c.returnError(w, fmt.Errorf("body read error: %s", err), http.StatusBadRequest) + return + } + + c.write(w, bodyBytes) +} + func (c *Context) scimSetupHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: diff --git a/webapp/src/Routes/Setup/Restart.tsx b/webapp/src/Routes/Setup/Restart.tsx new file mode 100644 index 0000000..01f69d9 --- /dev/null +++ b/webapp/src/Routes/Setup/Restart.tsx @@ -0,0 +1,52 @@ +import { Container, Button, Alert, Space } from "@mantine/core"; +import { useState } from "react"; +import { IconInfoCircle } from "@tabler/icons-react"; +import { AppSettings } from "../../Constants/Constants"; +import { useMutation } from "@tanstack/react-query"; +import { useAuthContext } from "../../Auth/Auth"; +import axios, { AxiosError } from "axios"; + +type RestartError = { + error: string; +} + +export function Restart() { + const [saved, setSaved] = useState(false) + const [saveError, setSaveError] = useState("") + const {authInfo} = useAuthContext(); + const alertIcon = ; + const setupMutation = useMutation({ + mutationFn: () => { + return axios.post(AppSettings.url + '/setup/restart-vpn', {}, { + headers: { + "Authorization": "Bearer " + authInfo.token + }, + }) + }, + onSuccess: () => { + setSaved(true) + setSaveError("") + }, + onError: (error:AxiosError) => { + const errorMessage = error.response?.data as RestartError + if(errorMessage?.error === undefined) { + setSaveError("Error: "+ error.message) + } else { + setSaveError("Error: "+ errorMessage.error) + } + } + }) + + return ( + + This button will reload the WireGuard® Configuration. VPN Clients will be disconnected during the reload. If the configuration has changed, clients might have to download new configuration files (for example if the port or address range has changed). The VPN Server admin UI will not be restarted. + + {saved && saveError === "" ? VPN Restarted! : null} + {saveError !== "" ? {saveError} : null} + + + + ) +} \ No newline at end of file diff --git a/webapp/src/Routes/Setup/Setup.tsx b/webapp/src/Routes/Setup/Setup.tsx index f97e5e3..d21bd88 100644 --- a/webapp/src/Routes/Setup/Setup.tsx +++ b/webapp/src/Routes/Setup/Setup.tsx @@ -1,9 +1,10 @@ import { Container, Tabs, Title, rem } from "@mantine/core"; import classes from './Setup.module.css'; -import { IconFile, IconNetwork, IconSettings } from "@tabler/icons-react"; +import { IconFile, IconNetwork, IconRestore, IconRewindBackward10, IconSettings } from "@tabler/icons-react"; import { GeneralSetup } from "./GeneralSetup"; import { VPNSetup } from "./VPNSetup"; import { TemplateSetup } from "./TemplateSetup"; +import { Restart } from "./Restart"; export function Setup() { const iconStyle = { width: rem(12), height: rem(12) }; @@ -24,6 +25,9 @@ export function Setup() { }> Templates + }> + Restart + @@ -34,6 +38,9 @@ export function Setup() { + + +