From 504dceedf34694f358c0a4142f17f27d54a4663f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Wed, 29 Apr 2026 01:34:35 +0200 Subject: [PATCH] [client] Add Wails3 + React desktop UI scaffold Stage 1 of the client/ui (Fyne) replacement. Adds a new client/ui-wails module that runs on Linux/macOS/Windows from a single React + Vite + Tailwind frontend driven by a thin gRPC services layer in Go. - Single-module integration (no submodule): merge Wails3 into root go.mod with build tags !android !ios !freebsd !js so cross-compiles on those targets exclude the package automatically. - Seven gRPC-bound services: Connection, Settings, Networks, Profiles, Debug, Update, Peers. Peers bridges Status polling and SubscribeEvents to the Wails event bus (netbird:status, netbird:event). - Tray + window shell mirrors the Fyne menu 1:1 with hide-on-close, SIGUSR1 / Windows named-event for external "show window" triggers. - React pages cover functional parity for Status, Settings (3 tabs), Networks (3 tabs), Profiles, Debug, Update, QuickActions, LoginUrl. - SVG-sourced tray icons (12 source SVGs incl. macOS template variants) rasterized to PNG via task common:generate:tray:icons. - Linux launcher sets WEBKIT_DISABLE_DMABUF_RENDERER=1 in the .desktop Exec= line and in task linux:run so the app renders correctly under RDP, VirtualBox, KVM, and bare WMs (Fluxbox/dwm) without DRM access. --- client/ui-wails/.gitignore | 7 + client/ui-wails/README.md | 85 + client/ui-wails/Taskfile.yml | 58 + .../netbird-systemtray-connected-dark.png | Bin 0 -> 2618 bytes .../netbird-systemtray-connected-macos.png | Bin 0 -> 1700 bytes .../assets/netbird-systemtray-connected.ico | Bin 0 -> 17542 bytes .../assets/netbird-systemtray-connected.png | Bin 0 -> 2618 bytes .../netbird-systemtray-connecting-macos.png | Bin 0 -> 1895 bytes .../assets/netbird-systemtray-connecting.ico | Bin 0 -> 17542 bytes .../assets/netbird-systemtray-connecting.png | Bin 0 -> 2705 bytes .../netbird-systemtray-disconnected-macos.png | Bin 0 -> 2084 bytes .../netbird-systemtray-disconnected.ico | Bin 0 -> 17542 bytes .../netbird-systemtray-disconnected.png | Bin 0 -> 2751 bytes .../assets/netbird-systemtray-error-macos.png | Bin 0 -> 1891 bytes .../assets/netbird-systemtray-error.ico | Bin 0 -> 17542 bytes .../assets/netbird-systemtray-error.png | Bin 0 -> 2563 bytes ...bird-systemtray-update-connected-macos.png | Bin 0 -> 1747 bytes .../netbird-systemtray-update-connected.ico | Bin 0 -> 17542 bytes .../netbird-systemtray-update-connected.png | Bin 0 -> 2588 bytes ...d-systemtray-update-disconnected-macos.png | Bin 0 -> 1992 bytes ...netbird-systemtray-update-disconnected.ico | Bin 0 -> 17542 bytes ...netbird-systemtray-update-disconnected.png | Bin 0 -> 2626 bytes client/ui-wails/assets/svg/_base.svg | 14 + client/ui-wails/assets/svg/appicon.svg | 17 + .../ui-wails/assets/svg/connected-macos.svg | 10 + client/ui-wails/assets/svg/connected.svg | 14 + .../ui-wails/assets/svg/connecting-macos.svg | 9 + client/ui-wails/assets/svg/connecting.svg | 9 + .../assets/svg/disconnected-macos.svg | 10 + client/ui-wails/assets/svg/disconnected.svg | 10 + client/ui-wails/assets/svg/error-macos.svg | 11 + client/ui-wails/assets/svg/error.svg | 11 + .../assets/svg/update-connected-macos.svg | 10 + .../ui-wails/assets/svg/update-connected.svg | 10 + .../assets/svg/update-disconnected-macos.svg | 10 + .../assets/svg/update-disconnected.svg | 10 + client/ui-wails/build/Taskfile.yml | 291 +++ .../appicon.icon/Assets/wails_icon_vector.svg | 11 + client/ui-wails/build/appicon.icon/icon.json | 51 + client/ui-wails/build/appicon.png | Bin 0 -> 32751 bytes client/ui-wails/build/config.yml | 78 + client/ui-wails/build/darwin/Info.dev.plist | 34 + client/ui-wails/build/darwin/Info.plist | 29 + client/ui-wails/build/darwin/Taskfile.yml | 208 ++ client/ui-wails/build/darwin/icons.icns | Bin 0 -> 98385 bytes client/ui-wails/build/docker/Dockerfile.cross | 203 ++ .../ui-wails/build/docker/Dockerfile.server | 41 + client/ui-wails/build/linux/Taskfile.yml | 235 +++ client/ui-wails/build/linux/appimage/build.sh | 35 + client/ui-wails/build/linux/desktop | 13 + .../ui-wails/build/linux/netbird-ui.desktop | 10 + client/ui-wails/build/linux/nfpm/nfpm.yaml | 67 + .../build/linux/nfpm/scripts/postinstall.sh | 21 + .../build/linux/nfpm/scripts/postremove.sh | 1 + .../build/linux/nfpm/scripts/preinstall.sh | 1 + .../build/linux/nfpm/scripts/preremove.sh | 1 + client/ui-wails/build/windows/Taskfile.yml | 192 ++ client/ui-wails/build/windows/icon.ico | Bin 0 -> 18097 bytes client/ui-wails/build/windows/info.json | 15 + .../build/windows/msix/app_manifest.xml | 55 + .../ui-wails/build/windows/msix/template.xml | 54 + .../ui-wails/build/windows/nsis/project.nsi | 114 ++ .../build/windows/nsis/wails_tools.nsh | 236 +++ .../ui-wails/build/windows/wails.exe.manifest | 22 + .../ui-wails/frontend/Inter Font License.txt | 93 + client/ui-wails/frontend/index.html | 12 + client/ui-wails/frontend/package.json | 33 + client/ui-wails/frontend/pnpm-lock.yaml | 1758 +++++++++++++++++ client/ui-wails/frontend/postcss.config.js | 6 + .../ui-wails/frontend/public/Inter-Medium.ttf | Bin 0 -> 315132 bytes client/ui-wails/frontend/public/react.svg | 1 + client/ui-wails/frontend/public/style.css | 157 ++ client/ui-wails/frontend/public/wails.png | Bin 0 -> 9057 bytes client/ui-wails/frontend/src/App.tsx | 32 + client/ui-wails/frontend/src/Layout.tsx | 45 + .../frontend/src/components/Button.tsx | 42 + .../ui-wails/frontend/src/components/Card.tsx | 14 + .../frontend/src/components/Input.tsx | 33 + .../frontend/src/components/Switch.tsx | 42 + .../ui-wails/frontend/src/components/Tabs.tsx | 40 + .../ui-wails/frontend/src/hooks/useStatus.ts | 36 + client/ui-wails/frontend/src/index.css | 17 + client/ui-wails/frontend/src/lib/cn.ts | 6 + client/ui-wails/frontend/src/main.tsx | 10 + client/ui-wails/frontend/src/pages/Debug.tsx | 105 + .../ui-wails/frontend/src/pages/LoginUrl.tsx | 34 + .../ui-wails/frontend/src/pages/Networks.tsx | 159 ++ client/ui-wails/frontend/src/pages/Peers.tsx | 211 ++ .../ui-wails/frontend/src/pages/Profiles.tsx | 173 ++ .../frontend/src/pages/QuickActions.tsx | 40 + .../ui-wails/frontend/src/pages/Settings.tsx | 240 +++ client/ui-wails/frontend/src/pages/Status.tsx | 161 ++ client/ui-wails/frontend/src/pages/Update.tsx | 61 + client/ui-wails/frontend/src/vite-env.d.ts | 1 + client/ui-wails/frontend/tailwind.config.ts | 44 + client/ui-wails/frontend/tsconfig.json | 25 + client/ui-wails/frontend/vite.config.ts | 12 + client/ui-wails/grpc.go | 57 + client/ui-wails/icons.go | 49 + client/ui-wails/icons_windows.go | 29 + client/ui-wails/main.go | 101 + client/ui-wails/services/conn.go | 13 + client/ui-wails/services/connection.go | 146 ++ client/ui-wails/services/debug.go | 88 + client/ui-wails/services/network.go | 92 + client/ui-wails/services/peers.go | 328 +++ client/ui-wails/services/profile.go | 118 ++ client/ui-wails/services/settings.go | 192 ++ client/ui-wails/services/update.go | 55 + client/ui-wails/signal_unix.go | 33 + client/ui-wails/signal_windows.go | 81 + client/ui-wails/tray.go | 565 ++++++ client/ui-wails/tray_icon_other.go | 8 + client/ui-wails/tray_icon_windows.go | 27 + go.mod | 56 +- go.sum | 142 +- 116 files changed, 8100 insertions(+), 46 deletions(-) create mode 100644 client/ui-wails/.gitignore create mode 100644 client/ui-wails/README.md create mode 100644 client/ui-wails/Taskfile.yml create mode 100644 client/ui-wails/assets/netbird-systemtray-connected-dark.png create mode 100644 client/ui-wails/assets/netbird-systemtray-connected-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-connected.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-connected.png create mode 100644 client/ui-wails/assets/netbird-systemtray-connecting-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-connecting.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-connecting.png create mode 100644 client/ui-wails/assets/netbird-systemtray-disconnected-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-disconnected.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-disconnected.png create mode 100644 client/ui-wails/assets/netbird-systemtray-error-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-error.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-error.png create mode 100644 client/ui-wails/assets/netbird-systemtray-update-connected-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-update-connected.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-update-connected.png create mode 100644 client/ui-wails/assets/netbird-systemtray-update-disconnected-macos.png create mode 100644 client/ui-wails/assets/netbird-systemtray-update-disconnected.ico create mode 100644 client/ui-wails/assets/netbird-systemtray-update-disconnected.png create mode 100644 client/ui-wails/assets/svg/_base.svg create mode 100644 client/ui-wails/assets/svg/appicon.svg create mode 100644 client/ui-wails/assets/svg/connected-macos.svg create mode 100644 client/ui-wails/assets/svg/connected.svg create mode 100644 client/ui-wails/assets/svg/connecting-macos.svg create mode 100644 client/ui-wails/assets/svg/connecting.svg create mode 100644 client/ui-wails/assets/svg/disconnected-macos.svg create mode 100644 client/ui-wails/assets/svg/disconnected.svg create mode 100644 client/ui-wails/assets/svg/error-macos.svg create mode 100644 client/ui-wails/assets/svg/error.svg create mode 100644 client/ui-wails/assets/svg/update-connected-macos.svg create mode 100644 client/ui-wails/assets/svg/update-connected.svg create mode 100644 client/ui-wails/assets/svg/update-disconnected-macos.svg create mode 100644 client/ui-wails/assets/svg/update-disconnected.svg create mode 100644 client/ui-wails/build/Taskfile.yml create mode 100644 client/ui-wails/build/appicon.icon/Assets/wails_icon_vector.svg create mode 100644 client/ui-wails/build/appicon.icon/icon.json create mode 100644 client/ui-wails/build/appicon.png create mode 100644 client/ui-wails/build/config.yml create mode 100644 client/ui-wails/build/darwin/Info.dev.plist create mode 100644 client/ui-wails/build/darwin/Info.plist create mode 100644 client/ui-wails/build/darwin/Taskfile.yml create mode 100644 client/ui-wails/build/darwin/icons.icns create mode 100644 client/ui-wails/build/docker/Dockerfile.cross create mode 100644 client/ui-wails/build/docker/Dockerfile.server create mode 100644 client/ui-wails/build/linux/Taskfile.yml create mode 100644 client/ui-wails/build/linux/appimage/build.sh create mode 100644 client/ui-wails/build/linux/desktop create mode 100755 client/ui-wails/build/linux/netbird-ui.desktop create mode 100644 client/ui-wails/build/linux/nfpm/nfpm.yaml create mode 100644 client/ui-wails/build/linux/nfpm/scripts/postinstall.sh create mode 100644 client/ui-wails/build/linux/nfpm/scripts/postremove.sh create mode 100644 client/ui-wails/build/linux/nfpm/scripts/preinstall.sh create mode 100644 client/ui-wails/build/linux/nfpm/scripts/preremove.sh create mode 100644 client/ui-wails/build/windows/Taskfile.yml create mode 100644 client/ui-wails/build/windows/icon.ico create mode 100644 client/ui-wails/build/windows/info.json create mode 100644 client/ui-wails/build/windows/msix/app_manifest.xml create mode 100644 client/ui-wails/build/windows/msix/template.xml create mode 100644 client/ui-wails/build/windows/nsis/project.nsi create mode 100644 client/ui-wails/build/windows/nsis/wails_tools.nsh create mode 100644 client/ui-wails/build/windows/wails.exe.manifest create mode 100644 client/ui-wails/frontend/Inter Font License.txt create mode 100644 client/ui-wails/frontend/index.html create mode 100644 client/ui-wails/frontend/package.json create mode 100644 client/ui-wails/frontend/pnpm-lock.yaml create mode 100644 client/ui-wails/frontend/postcss.config.js create mode 100644 client/ui-wails/frontend/public/Inter-Medium.ttf create mode 100644 client/ui-wails/frontend/public/react.svg create mode 100644 client/ui-wails/frontend/public/style.css create mode 100644 client/ui-wails/frontend/public/wails.png create mode 100644 client/ui-wails/frontend/src/App.tsx create mode 100644 client/ui-wails/frontend/src/Layout.tsx create mode 100644 client/ui-wails/frontend/src/components/Button.tsx create mode 100644 client/ui-wails/frontend/src/components/Card.tsx create mode 100644 client/ui-wails/frontend/src/components/Input.tsx create mode 100644 client/ui-wails/frontend/src/components/Switch.tsx create mode 100644 client/ui-wails/frontend/src/components/Tabs.tsx create mode 100644 client/ui-wails/frontend/src/hooks/useStatus.ts create mode 100644 client/ui-wails/frontend/src/index.css create mode 100644 client/ui-wails/frontend/src/lib/cn.ts create mode 100644 client/ui-wails/frontend/src/main.tsx create mode 100644 client/ui-wails/frontend/src/pages/Debug.tsx create mode 100644 client/ui-wails/frontend/src/pages/LoginUrl.tsx create mode 100644 client/ui-wails/frontend/src/pages/Networks.tsx create mode 100644 client/ui-wails/frontend/src/pages/Peers.tsx create mode 100644 client/ui-wails/frontend/src/pages/Profiles.tsx create mode 100644 client/ui-wails/frontend/src/pages/QuickActions.tsx create mode 100644 client/ui-wails/frontend/src/pages/Settings.tsx create mode 100644 client/ui-wails/frontend/src/pages/Status.tsx create mode 100644 client/ui-wails/frontend/src/pages/Update.tsx create mode 100644 client/ui-wails/frontend/src/vite-env.d.ts create mode 100644 client/ui-wails/frontend/tailwind.config.ts create mode 100644 client/ui-wails/frontend/tsconfig.json create mode 100644 client/ui-wails/frontend/vite.config.ts create mode 100644 client/ui-wails/grpc.go create mode 100644 client/ui-wails/icons.go create mode 100644 client/ui-wails/icons_windows.go create mode 100644 client/ui-wails/main.go create mode 100644 client/ui-wails/services/conn.go create mode 100644 client/ui-wails/services/connection.go create mode 100644 client/ui-wails/services/debug.go create mode 100644 client/ui-wails/services/network.go create mode 100644 client/ui-wails/services/peers.go create mode 100644 client/ui-wails/services/profile.go create mode 100644 client/ui-wails/services/settings.go create mode 100644 client/ui-wails/services/update.go create mode 100644 client/ui-wails/signal_unix.go create mode 100644 client/ui-wails/signal_windows.go create mode 100644 client/ui-wails/tray.go create mode 100644 client/ui-wails/tray_icon_other.go create mode 100644 client/ui-wails/tray_icon_windows.go diff --git a/client/ui-wails/.gitignore b/client/ui-wails/.gitignore new file mode 100644 index 000000000..d779b3d07 --- /dev/null +++ b/client/ui-wails/.gitignore @@ -0,0 +1,7 @@ +.task +bin +frontend/dist +frontend/node_modules +frontend/bindings +build/linux/appimage/build +build/windows/nsis/MicrosoftEdgeWebview2Setup.exe diff --git a/client/ui-wails/README.md b/client/ui-wails/README.md new file mode 100644 index 000000000..ec1e96028 --- /dev/null +++ b/client/ui-wails/README.md @@ -0,0 +1,85 @@ +# NetBird desktop UI (Wails3 + React) + +Replaces `client/ui` (Fyne). One binary on Windows / macOS / Linux, +talks to the NetBird daemon over gRPC, renders a React frontend in a +WebView. + +## Prerequisites + +- Go ≥ 1.25, Node ≥ 20, **pnpm** (`corepack enable && corepack prepare pnpm@latest --activate`) +- `wails3` CLI: `go install github.com/wailsapp/wails/v3/cmd/wails3@latest` +- `task`: `go install github.com/go-task/task/v3/cmd/task@latest` +- A running NetBird daemon (default: `unix:///var/run/netbird.sock`, + Windows `tcp://127.0.0.1:41731`) +- Linux only: `libwebkit2gtk-4.1-dev`, `libgtk-3-dev`, + `libayatana-appindicator3-dev` + +## Develop without rebuilding + +```bash +cd client/ui-wails +task dev +``` + +`task dev` runs Vite (port 9245) + the Go binary + a `*.go` watcher. +Frontend edits hot-reload instantly. Go edits trigger a rebuild and +relaunch. Pass daemon flags after `--`: + +```bash +task dev -- --daemon-addr=tcp://127.0.0.1:41731 +``` + +For pure UI work (no native window, fastest loop): + +```bash +cd frontend && pnpm dev +``` + +## Production build + +```bash +task build +``` + +Output in `bin/`. Frontend assets are embedded into the binary. + +### Cross-compile Windows from Linux + +Install the mingw-w64 toolchain once: + +```bash +sudo apt install gcc-mingw-w64-x86-64 # Debian/Ubuntu +sudo dnf install mingw64-gcc # Fedora +sudo pacman -S mingw-w64-gcc # Arch +``` + +Then: + +```bash +CGO_ENABLED=1 task windows:build +``` + +Produces `bin/netbird-ui.exe`. macOS cross-compile from Linux is not +supported (signing and notarization need a real Mac). + +## Regenerating bindings + +When a Go service signature changes: + +```bash +wails3 generate bindings +``` + +`task dev` does this automatically on `*.go` save. + +## Tray icons + +Source SVGs live in `assets/svg/` (state.svg + state-macos.svg). After editing +any SVG, rasterize to the PNGs the Go side embeds: + +```bash +task common:generate:tray:icons +``` + +Requires Inkscape. Commit the resulting `assets/*.png` files alongside the +SVG change so CI doesn't need Inkscape installed. diff --git a/client/ui-wails/Taskfile.yml b/client/ui-wails/Taskfile.yml new file mode 100644 index 000000000..2d0af9018 --- /dev/null +++ b/client/ui-wails/Taskfile.yml @@ -0,0 +1,58 @@ +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "netbird-ui" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + + setup:docker: + summary: Builds Docker image for cross-compilation (~800MB download) + cmds: + - task: common:setup:docker + + build:server: + summary: Builds the application in server mode (no GUI, HTTP server only) + cmds: + - task: common:build:server + + run:server: + summary: Runs the application in server mode + cmds: + - task: common:run:server + + build:docker: + summary: Builds a Docker image for server mode deployment + cmds: + - task: common:build:docker + + run:docker: + summary: Builds and runs the Docker image + cmds: + - task: common:run:docker diff --git a/client/ui-wails/assets/netbird-systemtray-connected-dark.png b/client/ui-wails/assets/netbird-systemtray-connected-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..be38eb0a6bf7432e60e6806681de6b1ebf4bc88c GIT binary patch literal 2618 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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-connecting.ico b/client/ui-wails/assets/netbird-systemtray-connecting.ico new file mode 100644 index 0000000000000000000000000000000000000000..a62d16e838b426219b79960b561205066b94b107 GIT binary patch literal 17542 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 LNkvXXu0mjf;u$m0 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-disconnected-macos.png b/client/ui-wails/assets/netbird-systemtray-disconnected-macos.png new file mode 100644 index 0000000000000000000000000000000000000000..58e32e69092123ef16f1436e1dd8abf799901680 GIT binary patch literal 2084 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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-disconnected.png b/client/ui-wails/assets/netbird-systemtray-disconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..a79fd1bedfc5d7a0a71bcc59f54b51b1ebad225a GIT binary patch literal 2751 zcmV;w3PAOVP)&K+OO(Kqv(d005^{5x{*Rg?QBh8Rr~hL518lG+np@Bsp)ty{VWp zJPD9F^F{`MGWjH8oc#&s9KknhD>-O6CrS|oDP){TIq8`oL>M2ILHZ~|m#(R`KeG8o z^D=D-H9p4)$cfi>}I6ESkp-f~!cfjcox$@PV zSQ7y@qK7g*>6D6F57t=@Hf+dwKNBJ06!y1-63NW+c-4Wn{}6(NtYrhnC|I8l<}6#Q~_mPCUEDcF!Hp^V~@z`8BSX46kOYu7iO8%RkuZVTsJqc}g|;8!y@ zB^s>X3?+quDdQ64o{Xo%ghtE1PE|OuuQhR|`^I?`1UE9ZW(sUv4aKcbSzZ~@XA~GY z=V$S|w((TulTWroT#<_C2ZVE1+SWvX^_bKCCe&vJ^QXQ<5-4unDrajteWVs`_P2z3 zjou@KlfiV9Pk{{^p!nnM#S(ppSt~{&1i?;XFIUp(BIR&vd$B91W#m21*=RZ)fKh~8 zsSjc%6ptRvox;(C@LTz>Urak*rMKQRwfKDAz+n!4H@Wa6n9B&r*)3~qDA{p=Fbb53O4s*_u1{xL=5gN|^tfP!eVB_kz?S#Q;XlfbGM0aLCO_RgTM0$kZ z%Ll{*>o;Y!o=52#j-4yv;DKR-?rf%dGxp8AS6Xj#jp_)1n8!Khk5JFa4 zOD(^xUSS)ov#)+hQmYREi1|zB)C0V4*`EU9!|3JZ{L$O8b3dAt}J9)Zt4dwa6EzQ+I9Bb)YUMDciJeQpS7ZWMij`{y%ncGyib zLL-@zAne*zdCXX{|5Der@I4^`u%@aK8*A5~ysQ+IL|-8?&T*^Pg>#N}w0Cwz`F&O8 zGWMh0JHDqCr?W*lA;AAe-DVIp#A=goTA+-izabK*FzHO$6A%&0ug@}|mbsw&fR`*^UH7NdIA^wWrr zE7wW@)MiY8_pNo+l#-I;}eTKP3#1&KPl3pjFyevJA6QROd8jd5JbM*B0Kc3N6 zI4~I6M2L{_VV`PbsBC=*b(hUbmw9CtcK_m{LQp~=D+<0o?}U5IlTm-hd;_*@utO9C zSj;Be=$U+`;Pv?}=@alyeLg3&E+b1KKqVQff9R z$~fr*03aZEWK$g$KbG&~Y6>mgk`{3!HPb=Zt`Jj_GHD<%^49F`Eu9h=-Ch z;HX|<3-5&5JG;>9%IHcm3O)S;=(y4yF0Wo;8vxL@5DDOQ_SG*H78oZB0RHj8zcB90 zIQ0*IAb@v|9S?^uv&o3MnyMFp1@csOCM31`(AI|a-%gd?V;=ndpC84{^dy7NAHd(< z{Sc9dtF4>sFH36m5g^Ao!5m}hN1uJwapppsZmOcdxCl@0-T`ZAaY_n$`Ui0M=)W;K zHk}aJ@}S+nbK91+0ImWHn*ao4x$(WDpB}&5bv^8<1VHsF8#dOiMTIqXO6+D&Kh8PY z(Q&0aYSms{xt#6Yz5P*96u$`TTgU_eKtPrsKJmrbPa-{G3OY#(i`j%l_mZM_6C_0& zHrV~!AKti26ve2=pbMUWdsyad@BH}WnF}lLjJnhM&}=ed>*o5)_Ue^S0JGmZeOCwo z0OFsR*xTCH@#l^!*GhU_gRw3~CItyW`Ql=%USS)ktEu|6q*fn}tMofWKqP91hDV;g z(c8Dv@1H0d_xTC{K(Et{N|O5aQft}A7PILf0B15q{{>dJ@#-Ps(!u}$002ovPDHLk FV1nAZC|>{o literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-error-macos.png b/client/ui-wails/assets/netbird-systemtray-error-macos.png new file mode 100644 index 0000000000000000000000000000000000000000..ce9dc6d1da6b1812137602182d83afb1de626779 GIT binary patch literal 1891 zcmV-p2b}ncP)Ad71TduF@aqaLNlFqYiY@v)~qPguYbIG zby}wL^Sznb$TzvkoA=H+_nhy$_nvppdt)T={|u|r4pbTs38=IKmBvE?D(yg}@sNN@ zJ5XsnB%sm`R2sfYJCF^=m`8wipaa+p_<^-0n!OKvOVSKfDFMcq%|H+E3{c~;`G^xd zR2c!rnDxL{fiAq6p8o)TnSP>b2r$Mx0el;1@UWYoNcv~`iK-yL7_%Ka_(weK<`(eE z%99xhFvipY9|f9#`c&J9Js;Nr9|G0@3n`WR*u!fUcn_EcZUYmN7PEO}jQKd;2QT2X zGVJ|*DJMPyYz3OJ1Fv|3AmB&gfZq(;3(b(5Add>Ynd*jkAN@Y{=!oO zY)Q3VV9pqG8F*FFP2d~AM_lf6CH@V(wAw}E+z?|-C-6QsAddG z`cEMWc|ZE5wvo?aKfzTau1fk-p-Kfk`%8Kc_zp0WOSa5N0)xdU7QFe9G>c6_-V03` zFeYid7{#J?f0EwCUK8ev(RUg1z(}#mMQ?voj6a8+cPoiuNk!j?lJ@_S{t7&gz2~k3 zCV=0RVxZ&#h@`&&-^VVCl^dreB}*~jN(CsDetrOCt^c`3x$gqo^0MdVM^eU^R_qOw>y;j330KS4)J$v@lkBp2w z>GSzaG#Vuw4zsYZkXtbgwY9Yj4-XRz2Ek6(Um&f}PBtj$-p{1qef$Q`0^Yr%iGB7Xz_6wMofsZ-YOCABnn0nwxl>Xj6 z-6q$rU3)qZ2yBc-ql7{s78f&T*hnP8?%lfqh(@FI_V#9!vn-29B;t&O%fM5B+!z60 z!^PkM4jnqw6bJ-1CK3t4;qa=#1L+Ihvg4IlTwG+|zI{whO#yrwd(@UT1&lFmz-LRb zk-;WMj~?9uaQyi3g6DiT%+Jqr;=~E(=krhk0mc{ud>xl_erIQAo!{?YACJcw8X9ub zGyrGMo^{e@8_-e82V{V_?tcT&b=YB$G*Ev6%C-rzir9F>8UaYhI;c z$BrGF04`kc?!C;|*qC#;v)B>vWnhy#?kq<`L&I8tn>TNIVIF{6x0Y(Vj~7gUG3GHK z=&DC)sIRZD0hpei_NogeCztBFg%bem$HOl=%JB~t%UTM1u^eZ`D z1YEgtWmP%LvUKj;IY;z&sXnTf03I8pJ($4fgDji@x?I}rrm!z4Q867@+ zSU#UGXW*7)DI5-`&-qzNpI>R;)v~yUXaRnPUCAMlNGz4bZ{NPXtSsKz+Dcbf7r|f< zzu)iZ{~p)}j6Yzx3hr>e%}5GKdRzJ9x};~b+5Mo5fR*UL{!h_P><_M;Tb%%=fH!~` d@K!EE{{`fQHlY*eoW}qF002ovPDHLkV1nbtjdcJ3 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-error.ico b/client/ui-wails/assets/netbird-systemtray-error.ico new file mode 100644 index 0000000000000000000000000000000000000000..f3bb85e21e76f98b3febe38c1e9d1c6dda31d0a7 GIT binary patch literal 17542 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 V^wF5XO&PsN{XjJ!X*}iQ{{a!H!S(i&{~*R` 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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-update-connected-macos.png b/client/ui-wails/assets/netbird-systemtray-update-connected-macos.png new file mode 100644 index 0000000000000000000000000000000000000000..84cb1c029ebcd6ce5c77b51ca12ad20f93b651b6 GIT binary patch literal 1747 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 IWvBT604ApYnE(I) literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-update-connected.png b/client/ui-wails/assets/netbird-systemtray-update-connected.png new file mode 100644 index 0000000000000000000000000000000000000000..4b160f888da1ce144665db6e0ba7349f0c317dde GIT binary patch literal 2588 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 ag8l=^Z~sZ{;3&NS00003-@6EiPH*dP9d-gDt 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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/netbird-systemtray-update-disconnected.png b/client/ui-wails/assets/netbird-systemtray-update-disconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9e1c8b2ffec54e6c02b68f87479a08294a8d20 GIT binary patch literal 2626 zcmV-I3cdA-P)o^At68Ct;)#Pt{;IVz@xweLP~TPKuhTw=NU8VP57xh&YAnIJ*6v)8qS!2(1dXMIm}tO64CZ&Qo!cof z_Pf&hFz{gAk#SoY=MdvT)&Jsdi{l7{Is@r!A*A2o{1s`|ImjVJsKu4(vCX!Y8VTGJ2e(3nu{lQVHXABTzz&GMI0ca&8a?`D+g|HHqg zq2AQ)n?qtvKd55?+^%>~y6pkfzA32IvXajejTvun$Ie(LnL!b6t=au=A;`#DF;GgP z8be9fTm`X%I@ha+XUPU?_%j|Ff(nSfG7 zW+H)Bj=41#(X=z!YwEX1^hd>6k_;7*sFqMh8MniS`eYo%JoHPg_5^C)Y0gPD?TFUe zb{qbw(VrG>N;1^pa@-sobGv*v2Mb;fQQNC`R@@^_rw~@Q9ZerNP*k#KfhsD zK6B)jZSL3^J2bj;qtN0=KFa%`TFY>I(mji1x{*N@BNHO1ZQP*Ue5S}b-Bwobk9d`M zPHS~Mp9xTsSX1s!420VeK(*(vH4*yDQL8`5J748jZ)$dScXx)L)##Jih2v07qroYz ztf`i4+XbZ@$H=Md4Mg6SmH&2Mhu`H{tyR`Uqar4;*5da%ZkG?$9@`uA|zvHUcTPbE?Ca zSJ}&oG`h6Qj>-4F)l;b2q84Z1zIU318{Guj891gI#}uP_5L_iu9m&a1Y26l};r(Lt z7QOxV*6jH*it`evM6j!t0?S@DoJ=ObP;8?{7q!Sr(*GE>`h!m;;w7}w#EL6>nV>qn ziSHWrdu!?s6sxmj4Iq1U{eGp4e>u$|D0VlO25!U#A%(nP*(CpcuP+3)0{ei@-p@z9 z*J^=_a?x4RR*c4J#?07%C>TOY;k)_f+1Ao4uNM9@D$?{CJkZmXq$Wfz;Q`ku|?u@ayS_&$)-_bd$owTsMdqkZMTH#_Q!a$?5{ zC&6>gHI@FAD}RI##w+y>cRwDcE0UkRL9gJ^+r2EAV?z9yWhwA_-*`!~RgXMzNvhXc za#|}!DCezt63kh%LjDyiRv?7&_?9Sl{HZ5z=K(syn%n=_!-g$U01xqeka#~@JOQ;A z1c6J^4V%+iFv{|KP!m#$z&-b@z~c$NQt$AKrz5B|zS|a5FY{2n%!8fg%7;3C+_rd|Ft&v zUu*Nkoj$x7NVsc#m?P~P;OoG3P$U7}wHHlt5HqsLNeO0U3x+R}O%x%eu$L_P7QpW| zMbd`7*MbezeyV1kDr8kNP5yR|AHSXYXhCOK^ZOSPHmrqW2oOSOp%>+>Kq}00nlXHX zwhc)zFTZ@U2nH(}k1003o%)%=Yr%#)eN@db28~_%HG}m(@urQ0XLiK;%I5jNyn+en z{MGDPQi=(A-DD}`IgJ>0dJ^n~3ughmQtzZJbqVKZrhkN~nrX5j&2n%RY;H)*zq~L4 z{xaW^u2@*etDboSA?it+LOXYn3{LueVqkmU_(&%J;Bw$OU|d^==Fyj< zJh?TBlS5PV+65L5UT-rl{@~LF+(7)jK>}X|0sy81PZ96r&~#k)w#6L|wkEg5r&dU= znr*UVj@g&(A-?8UK#H?YTI;XMM`*3D*IIw1bH`q-^>vx_epMo1Am#zfiD&Xjz~tWR khk(Pt8^9)@A)BKA0$`PCs1Z<6F#rGn07*qoM6N<$g3TEm{Qv*} literal 0 HcmV?d00001 diff --git a/client/ui-wails/assets/svg/_base.svg b/client/ui-wails/assets/svg/_base.svg new file mode 100644 index 000000000..9b2498ae8 --- /dev/null +++ b/client/ui-wails/assets/svg/_base.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/client/ui-wails/assets/svg/appicon.svg b/client/ui-wails/assets/svg/appicon.svg new file mode 100644 index 000000000..773ad3417 --- /dev/null +++ b/client/ui-wails/assets/svg/appicon.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/client/ui-wails/assets/svg/connected-macos.svg b/client/ui-wails/assets/svg/connected-macos.svg new file mode 100644 index 000000000..d1e2ce18c --- /dev/null +++ b/client/ui-wails/assets/svg/connected-macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/connected.svg b/client/ui-wails/assets/svg/connected.svg new file mode 100644 index 000000000..687d8e2e5 --- /dev/null +++ b/client/ui-wails/assets/svg/connected.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/connecting-macos.svg b/client/ui-wails/assets/svg/connecting-macos.svg new file mode 100644 index 000000000..04d666c5f --- /dev/null +++ b/client/ui-wails/assets/svg/connecting-macos.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/ui-wails/assets/svg/connecting.svg b/client/ui-wails/assets/svg/connecting.svg new file mode 100644 index 000000000..d3818055a --- /dev/null +++ b/client/ui-wails/assets/svg/connecting.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/ui-wails/assets/svg/disconnected-macos.svg b/client/ui-wails/assets/svg/disconnected-macos.svg new file mode 100644 index 000000000..06802c9d4 --- /dev/null +++ b/client/ui-wails/assets/svg/disconnected-macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/disconnected.svg b/client/ui-wails/assets/svg/disconnected.svg new file mode 100644 index 000000000..31eab7970 --- /dev/null +++ b/client/ui-wails/assets/svg/disconnected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/error-macos.svg b/client/ui-wails/assets/svg/error-macos.svg new file mode 100644 index 000000000..4c6d4e76d --- /dev/null +++ b/client/ui-wails/assets/svg/error-macos.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/error.svg b/client/ui-wails/assets/svg/error.svg new file mode 100644 index 000000000..46ac0d762 --- /dev/null +++ b/client/ui-wails/assets/svg/error.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/update-connected-macos.svg b/client/ui-wails/assets/svg/update-connected-macos.svg new file mode 100644 index 000000000..774e631e6 --- /dev/null +++ b/client/ui-wails/assets/svg/update-connected-macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/update-connected.svg b/client/ui-wails/assets/svg/update-connected.svg new file mode 100644 index 000000000..45e22693b --- /dev/null +++ b/client/ui-wails/assets/svg/update-connected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/update-disconnected-macos.svg b/client/ui-wails/assets/svg/update-disconnected-macos.svg new file mode 100644 index 000000000..fe161cc44 --- /dev/null +++ b/client/ui-wails/assets/svg/update-disconnected-macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/assets/svg/update-disconnected.svg b/client/ui-wails/assets/svg/update-disconnected.svg new file mode 100644 index 000000000..657974005 --- /dev/null +++ b/client/ui-wails/assets/svg/update-disconnected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/ui-wails/build/Taskfile.yml b/client/ui-wails/build/Taskfile.yml new file mode 100644 index 000000000..a8262fc49 --- /dev/null +++ b/client/ui-wails/build/Taskfile.yml @@ -0,0 +1,291 @@ +version: '3' + +tasks: + go:mod:tidy: + summary: Runs `go mod tidy` + internal: true + cmds: + - go mod tidy + + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - pnpm-lock.yaml + generates: + - node_modules + preconditions: + - sh: pnpm --version + msg: "Looks like pnpm isn't installed. Install with: corepack enable && corepack prepare pnpm@latest --activate" + cmds: + - pnpm install + + build:frontend: + label: build:frontend (DEV={{.DEV}}) + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + - exclude: node_modules/**/* + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - pnpm run {{.BUILD_COMMAND}} + env: + PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}' + vars: + BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}' + + + frontend:vendor:puppertino: + summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling + sources: + - frontend/public/puppertino/puppertino.css + generates: + - frontend/public/puppertino/puppertino.css + cmds: + - | + set -euo pipefail + mkdir -p frontend/public/puppertino + # If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error. + if [ ! -f frontend/public/puppertino/puppertino.css ]; then + echo "No bundled Puppertino found. Attempting to fetch from GitHub..." + if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then + curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true + echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css" + else + echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it." + fi + else + echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css" + fi + # Ensure index.html includes Puppertino CSS and button classes + INDEX_HTML=frontend/index.html + if [ -f "$INDEX_HTML" ]; then + if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then + # Insert Puppertino link tag after style.css link + awk ' + /href="\/style.css"\/?/ && !x { print; print " "; x=1; next }1 + ' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML" + fi + # Replace default .btn with Puppertino primary button classes if present + sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true + fi + + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: Generates bindings for the frontend + deps: + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts + + generate:icons: + summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms). + dir: build + sources: + - "appicon.png" + - "appicon.icon" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin + + generate:tray:icons: + summary: Rasterize the SVG tray sources to PNG/ICO embedded by the Go side. + desc: | + Reads assets/svg/*.svg and writes assets/.png (Linux/macOS + tray) plus assets/.ico (Windows tray). The .ico packs 16/24/32/48 + px frames so Shell_NotifyIcon picks the size matching the user's DPI + instead of poorly downscaling a single large PNG. + Run after editing any SVG; CI runs this before build. + dir: assets + sources: + - "svg/*.svg" + generates: + - "netbird-systemtray-*.png" + - "netbird-systemtray-*.ico" + preconditions: + - sh: command -v inkscape >/dev/null 2>&1 + msg: "inkscape is required to rasterize tray SVGs (apt install inkscape)" + - sh: command -v icotool >/dev/null 2>&1 + msg: "icotool is required to pack tray .ico files (apt install icoutils)" + cmds: + - | + set -euo pipefail + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + for state in connected disconnected connecting error update-connected update-disconnected; do + # 64px PNG for Linux tray + macOS-template PNG (per-state) + inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-$state.png" "svg/$state.svg" >/dev/null + inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-$state-macos.png" "svg/$state-macos.svg" >/dev/null + # multi-resolution .ico for Windows tray + for sz in 16 24 32 48; do + inkscape --export-type=png --export-width=$sz --export-filename="$tmp/$state-$sz.png" "svg/$state.svg" >/dev/null + done + icotool -c -o "netbird-systemtray-$state.ico" \ + "$tmp/$state-16.png" "$tmp/$state-24.png" "$tmp/$state-32.png" "$tmp/$state-48.png" + done + # Linux dark-mode variant currently shares the connected artwork. + inkscape --export-type=png --export-width=64 --export-filename="netbird-systemtray-connected-dark.png" "svg/connected.svg" >/dev/null + + dev:frontend: + summary: Runs the frontend in development mode + dir: frontend + deps: + - task: install:frontend:deps + cmds: + - pnpm exec vite --port {{.VITE_PORT}} --strictPort + + update:build-assets: + summary: Updates the build assets + dir: build + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . + + build:server: + summary: Builds the application in server mode (no GUI, HTTP server only) + desc: | + Builds the application with the server build tag enabled. + Server mode runs as a pure HTTP server without native GUI dependencies. + Usage: task build:server + deps: + - task: build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}} + vars: + BUILD_FLAGS: "{{.BUILD_FLAGS}}" + + run:server: + summary: Builds and runs the application in server mode + deps: + - task: build:server + cmds: + - ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}} + + build:docker: + summary: Builds a Docker image for server mode deployment + desc: | + Creates a minimal Docker image containing the server mode binary. + The image is based on distroless for security and small size. + Usage: task build:docker [TAG=myapp:latest] + cmds: + - docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server . + vars: + TAG: "{{.TAG}}" + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required. Please install Docker first." + - sh: test -f build/docker/Dockerfile.server + msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it." + + run:docker: + summary: Builds and runs the Docker image + desc: | + Builds the Docker image and runs it, exposing port 8080. + Usage: task run:docker [TAG=myapp:latest] [PORT=8080] + Note: The internal container port is always 8080. The PORT variable + only changes the host port mapping. Ensure your app uses port 8080 + or modify the Dockerfile to match your ServerOptions.Port setting. + deps: + - task: build:docker + vars: + TAG: + ref: .TAG + cmds: + - docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}} + vars: + TAG: "{{.TAG}}" + PORT: "{{.PORT}}" + + setup:docker: + summary: Builds Docker image for cross-compilation (~800MB download) + desc: | + Builds the Docker image needed for cross-compiling to any platform. + Run this once to enable cross-platform builds from any OS. + cmds: + - docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/ + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required. Please install Docker first." + + ios:device:list: + summary: Lists connected iOS devices (UDIDs) + cmds: + - xcrun xcdevice list + + ios:run:device: + summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl) + vars: + PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/.xcodeproj + SCHEME: '{{.SCHEME}}' # e.g., ios.dev + CONFIG: '{{.CONFIG | default "Debug"}}' + DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}' + UDID: '{{.UDID}}' # from `task ios:device:list` + BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev + TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing + preconditions: + - sh: xcrun -f xcodebuild + msg: "xcodebuild not found. Please install Xcode." + - sh: xcrun -f devicectl + msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)." + - sh: test -n '{{.PROJECT}}' + msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)." + - sh: test -n '{{.SCHEME}}' + msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)." + - sh: test -n '{{.UDID}}' + msg: "Set UDID to your device UDID (see: task ios:device:list)." + - sh: test -n '{{.BUNDLE_ID}}' + msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)." + cmds: + - | + set -euo pipefail + echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}" + XCB_ARGS=( + -project "{{.PROJECT}}" + -scheme "{{.SCHEME}}" + -configuration "{{.CONFIG}}" + -destination "id={{.UDID}}" + -derivedDataPath "{{.DERIVED}}" + -allowProvisioningUpdates + -allowProvisioningDeviceRegistration + ) + # Optionally inject signing identifiers if provided + if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi + if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi + xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true + # If xcpretty isn't installed, run without it + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + xcodebuild "${XCB_ARGS[@]}" build + fi + # Find built .app + APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1) + if [ -z "$APP_PATH" ]; then + echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2 + exit 1 + fi + echo "Installing: $APP_PATH" + xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH" + echo "Launching: {{.BUNDLE_ID}}" + xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}" diff --git a/client/ui-wails/build/appicon.icon/Assets/wails_icon_vector.svg b/client/ui-wails/build/appicon.icon/Assets/wails_icon_vector.svg new file mode 100644 index 000000000..588f46bda --- /dev/null +++ b/client/ui-wails/build/appicon.icon/Assets/wails_icon_vector.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/client/ui-wails/build/appicon.icon/icon.json b/client/ui-wails/build/appicon.icon/icon.json new file mode 100644 index 000000000..ecf18497c --- /dev/null +++ b/client/ui-wails/build/appicon.icon/icon.json @@ -0,0 +1,51 @@ +{ + "fill" : { + "automatic-gradient" : "extended-gray:1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "fill-specializations" : [ + { + "appearance" : "dark", + "value" : { + "solid" : "srgb:0.92143,0.92145,0.92144,1.00000" + } + }, + { + "appearance" : "tinted", + "value" : { + "solid" : "srgb:0.83742,0.83744,0.83743,1.00000" + } + } + ], + "image-name" : "wails_icon_vector.svg", + "name" : "wails_icon_vector", + "position" : { + "scale" : 1.25, + "translation-in-points" : [ + 36.890625, + 4.96875 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "specular" : true, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/client/ui-wails/build/appicon.png b/client/ui-wails/build/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a34fdf3a252a1c490b0d0bf14eada7f6f30dd3eb GIT binary patch literal 32751 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 + + + CFBundlePackageType + APPL + CFBundleName + NetBird + CFBundleExecutable + netbird-ui + CFBundleIdentifier + io.netbird.client + CFBundleVersion + 0.0.1 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.0.1 + CFBundleIconFile + icons + CFBundleIconName + appicon + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © 2026, My Company + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/client/ui-wails/build/darwin/Info.plist b/client/ui-wails/build/darwin/Info.plist new file mode 100644 index 000000000..7449c69ad --- /dev/null +++ b/client/ui-wails/build/darwin/Info.plist @@ -0,0 +1,29 @@ + + + + CFBundlePackageType + APPL + CFBundleName + NetBird + CFBundleExecutable + netbird-ui + CFBundleIdentifier + io.netbird.client + CFBundleVersion + 0.0.1 + CFBundleGetInfoString + This is a comment + CFBundleShortVersionString + 0.0.1 + CFBundleIconFile + icons + CFBundleIconName + appicon + LSMinimumSystemVersion + 10.15.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + © 2026, My Company + + \ No newline at end of file diff --git a/client/ui-wails/build/darwin/Taskfile.yml b/client/ui-wails/build/darwin/Taskfile.yml new file mode 100644 index 000000000..e4a2d03a9 --- /dev/null +++ b/client/ui-wails/build/darwin/Taskfile.yml @@ -0,0 +1,208 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)" + # KEYCHAIN_PROFILE: "my-notarize-profile" + # ENTITLEMENTS: "build/darwin/entitlements.plist" + + # Docker image for cross-compilation (used when building on non-macOS) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application + cmds: + - task: '{{if eq OS "darwin"}}build:native{{else}}build:docker{{end}}' + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + OUTPUT: '{{.OUTPUT}}' + EXTRA_TAGS: '{{.EXTRA_TAGS}}' + vars: + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + + build:native: + summary: Builds the application natively on macOS + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + CGO_CFLAGS: "-mmacosx-version-min=10.15" + CGO_LDFLAGS: "-mmacosx-version-min=10.15" + MACOSX_DEPLOYMENT_TARGET: "10.15" + + build:docker: + summary: Cross-compiles for macOS using Docker (for Linux/Windows hosts) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for cross-compilation. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - mkdir -p {{.BIN_DIR}} + - mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" + vars: + DOCKER_ARCH: '{{if eq .ARCH "arm64"}}arm64{{else if eq .ARCH "amd64"}}amd64{{else}}arm64{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + # Handles both relative (=> ../) and absolute (=> /) paths + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + build:universal: + summary: Builds darwin universal binary (arm64 + amd64) + deps: + - task: build + vars: + ARCH: amd64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" + - task: build + vars: + ARCH: arm64 + OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + cmds: + - task: '{{if eq OS "darwin"}}build:universal:lipo:native{{else}}build:universal:lipo:go{{end}}' + + build:universal:lipo:native: + summary: Creates universal binary using native lipo (macOS) + internal: true + cmds: + - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + build:universal:lipo:go: + summary: Creates universal binary using wails3 tool lipo (Linux/Windows) + internal: true + cmds: + - wails3 tool lipo -output "{{.BIN_DIR}}/{{.APP_NAME}}" -input "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" -input "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + - rm -f "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64" + + package: + summary: Packages the application into a `.app` bundle + deps: + - task: build + cmds: + - task: create:app:bundle + + package:universal: + summary: Packages darwin universal binary (arm64 + amd64) + deps: + - task: build:universal + cmds: + - task: create:app:bundle + + + create:app:bundle: + summary: Creates an `.app` bundle + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + - | + if [ -f build/darwin/Assets.car ]; then + cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources" + fi + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS" + - cp build/darwin/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents" + - task: '{{if eq OS "darwin"}}codesign:adhoc{{else}}codesign:skip{{end}}' + + codesign:adhoc: + summary: Ad-hoc signs the app bundle (macOS only) + internal: true + cmds: + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app" + + codesign:skip: + summary: Skips codesigning when cross-compiling + internal: true + cmds: + - 'echo "Skipping codesign (not available on {{OS}}). Sign the .app on macOS before distribution."' + + run: + cmds: + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + - | + if [ -f build/darwin/Assets.car ]; then + cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources" + fi + - cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS" + - cp "build/darwin/Info.dev.plist" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist" + - codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}' + + sign: + summary: Signs the application bundle with Developer ID + desc: | + Signs the .app bundle for distribution. + Configure SIGN_IDENTITY in the vars section at the top of this file. + deps: + - task: package + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' + msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" + + sign:notarize: + summary: Signs and notarizes the application bundle + desc: | + Signs the .app bundle and submits it for notarization. + Configure SIGN_IDENTITY and KEYCHAIN_PROFILE in the vars section at the top of this file. + + Setup (one-time): + wails3 signing credentials --apple-id "you@email.com" --team-id "TEAMID" --password "app-specific-password" --profile "my-profile" + deps: + - task: package + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} --notarize --keychain-profile {{.KEYCHAIN_PROFILE}} + preconditions: + - sh: '[ -n "{{.SIGN_IDENTITY}}" ]' + msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" + - sh: '[ -n "{{.KEYCHAIN_PROFILE}}" ]' + msg: "KEYCHAIN_PROFILE is required. Set it in the vars section at the top of build/darwin/Taskfile.yml" diff --git a/client/ui-wails/build/darwin/icons.icns b/client/ui-wails/build/darwin/icons.icns new file mode 100644 index 0000000000000000000000000000000000000000..07ec0589309205c5be51165318af72c4cb1f3cc8 GIT binary patch literal 98385 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 UA(Z?3gsKHQCf7gv|7tn@7hn&vuK)l5 literal 0 HcmV?d00001 diff --git a/client/ui-wails/build/docker/Dockerfile.cross b/client/ui-wails/build/docker/Dockerfile.cross new file mode 100644 index 000000000..a3c01f2da --- /dev/null +++ b/client/ui-wails/build/docker/Dockerfile.cross @@ -0,0 +1,203 @@ +# Cross-compile Wails v3 apps to any platform +# +# Darwin: Zig + macOS SDK +# Linux: Native GCC when host matches target, Zig for cross-arch +# Windows: Zig + bundled mingw +# +# Usage: +# docker build -t wails-cross -f Dockerfile.cross . +# docker run --rm -v $(pwd):/app wails-cross darwin arm64 +# docker run --rm -v $(pwd):/app wails-cross darwin amd64 +# docker run --rm -v $(pwd):/app wails-cross linux amd64 +# docker run --rm -v $(pwd):/app wails-cross linux arm64 +# docker run --rm -v $(pwd):/app wails-cross windows amd64 +# docker run --rm -v $(pwd):/app wails-cross windows arm64 + +FROM golang:1.25-bookworm + +ARG TARGETARCH + +# Install base tools, GCC, and GTK/WebKit dev packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl xz-utils nodejs npm pkg-config gcc libc6-dev \ + libgtk-3-dev libwebkit2gtk-4.1-dev \ + libgtk-4-dev libwebkitgtk-6.0-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Zig - automatically selects correct binary for host architecture +ARG ZIG_VERSION=0.14.0 +RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ + curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \ + | tar -xJ -C /opt \ + && ln -s /opt/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig + +# Download macOS SDK (required for darwin targets) +ARG MACOS_SDK_VERSION=14.5 +RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \ + | tar -xJ -C /opt \ + && mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk + +ENV MACOS_SDK_PATH=/opt/macos-sdk + +# Create Zig CC wrappers for cross-compilation targets +# Darwin and Windows use Zig; Linux uses native GCC (run with --platform for cross-arch) + +# Darwin arm64 +COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -mmacosx-version-min=*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-darwin-arm64 + +# Darwin amd64 +COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -mmacosx-version-min=*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-darwin-amd64 + +# Windows amd64 - uses Zig's bundled mingw +COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -Wl,*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -target x86_64-windows-gnu $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-windows-amd64 + +# Windows arm64 - uses Zig's bundled mingw +COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64 +#!/bin/sh +ARGS="" +SKIP_NEXT=0 +for arg in "$@"; do + if [ $SKIP_NEXT -eq 1 ]; then + SKIP_NEXT=0 + continue + fi + case "$arg" in + -target) SKIP_NEXT=1 ;; + -Wl,*) ;; + *) ARGS="$ARGS $arg" ;; + esac +done +exec zig cc -target aarch64-windows-gnu $ARGS +ZIGWRAP +RUN chmod +x /usr/local/bin/zcc-windows-arm64 + +# Build script +COPY <<'SCRIPT' /usr/local/bin/build.sh +#!/bin/sh +set -e + +OS=${1:-darwin} +ARCH=${2:-arm64} + +case "${OS}-${ARCH}" in + darwin-arm64|darwin-aarch64) + export CC=zcc-darwin-arm64 + export GOARCH=arm64 + export GOOS=darwin + ;; + darwin-amd64|darwin-x86_64) + export CC=zcc-darwin-amd64 + export GOARCH=amd64 + export GOOS=darwin + ;; + linux-arm64|linux-aarch64) + export CC=gcc + export GOARCH=arm64 + export GOOS=linux + ;; + linux-amd64|linux-x86_64) + export CC=gcc + export GOARCH=amd64 + export GOOS=linux + ;; + windows-arm64|windows-aarch64) + export CC=zcc-windows-arm64 + export GOARCH=arm64 + export GOOS=windows + ;; + windows-amd64|windows-x86_64) + export CC=zcc-windows-amd64 + export GOARCH=amd64 + export GOOS=windows + ;; + *) + echo "Usage: " + echo " os: darwin, linux, windows" + echo " arch: amd64, arm64" + exit 1 + ;; +esac + +export CGO_ENABLED=1 +export CGO_CFLAGS="-w" + +# Build frontend if exists and not already built (host may have built it) +if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then + (cd frontend && npm install --silent && npm run build --silent) +fi + +# Build +APP=${APP_NAME:-$(basename $(pwd))} +mkdir -p bin + +EXT="" +LDFLAGS="-s -w" +if [ "$GOOS" = "windows" ]; then + EXT=".exe" + LDFLAGS="-s -w -H windowsgui" +fi + +TAGS="production" +if [ -n "$EXTRA_TAGS" ]; then + TAGS="${TAGS},${EXTRA_TAGS}" +fi + +go build -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} . +echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}" +SCRIPT +RUN chmod +x /usr/local/bin/build.sh + +WORKDIR /app +ENTRYPOINT ["/usr/local/bin/build.sh"] +CMD ["darwin", "arm64"] diff --git a/client/ui-wails/build/docker/Dockerfile.server b/client/ui-wails/build/docker/Dockerfile.server new file mode 100644 index 000000000..58fb64f76 --- /dev/null +++ b/client/ui-wails/build/docker/Dockerfile.server @@ -0,0 +1,41 @@ +# Wails Server Mode Dockerfile +# Multi-stage build for minimal image size + +# Build stage +FROM golang:alpine AS builder + +WORKDIR /app + +# Install build dependencies +RUN apk add --no-cache git + +# Copy source code +COPY . . + +# Remove local replace directive if present (for production builds) +RUN sed -i '/^replace/d' go.mod || true + +# Download dependencies +RUN go mod tidy + +# Build the server binary +RUN go build -tags server -ldflags="-s -w" -o server . + +# Runtime stage - minimal image +FROM gcr.io/distroless/static-debian12 + +# Copy the binary +COPY --from=builder /app/server /server + +# Copy frontend assets +COPY --from=builder /app/frontend/dist /frontend/dist + +# Expose the default port +EXPOSE 8080 + +# Bind to all interfaces (required for Docker) +# Can be overridden at runtime with -e WAILS_SERVER_HOST=... +ENV WAILS_SERVER_HOST=0.0.0.0 + +# Run the server +ENTRYPOINT ["/server"] diff --git a/client/ui-wails/build/linux/Taskfile.yml b/client/ui-wails/build/linux/Taskfile.yml new file mode 100644 index 000000000..68a795fc1 --- /dev/null +++ b/client/ui-wails/build/linux/Taskfile.yml @@ -0,0 +1,235 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # PGP_KEY: "path/to/signing-key.asc" + # SIGN_ROLE: "builder" # Options: origin, maint, archive, builder + # + # Password is stored securely in system keychain. Run: wails3 setup signing + + # Docker image for cross-compilation (used when building on non-Linux or no CC available) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application for Linux + cmds: + # Linux requires CGO - use Docker when: + # 1. Cross-compiling from non-Linux, OR + # 2. No C compiler is available, OR + # 3. Target architecture differs from host architecture (cross-arch compilation) + - task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}' + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + OUTPUT: '{{.OUTPUT}}' + EXTRA_TAGS: '{{.EXTRA_TAGS}}' + vars: + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Determine target architecture (defaults to host ARCH if not specified) + TARGET_ARCH: '{{.ARCH | default ARCH}}' + # Check if a C compiler is available (gcc or clang) + HAS_CC: + sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"' + + build:native: + summary: Builds the application natively on Linux + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + - task: generate:dotdesktop + cmds: + - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}} + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default ARCH}}' + + build:docker: + summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + - task: generate:dotdesktop + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for cross-compilation to Linux. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - mkdir -p {{.BIN_DIR}} + - mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}" + vars: + DOCKER_ARCH: '{{.ARCH | default "amd64"}}' + DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}' + OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + package: + summary: Packages the application for Linux + deps: + - task: build + cmds: + - task: create:appimage + - task: create:deb + - task: create:rpm + - task: create:aur + + create:appimage: + summary: Creates an AppImage + dir: build/linux/appimage + deps: + - task: build + - task: generate:dotdesktop + cmds: + - cp "{{.APP_BINARY}}" "{{.APP_NAME}}" + - cp ../../appicon.png "{{.APP_NAME}}.png" + - wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build + vars: + APP_NAME: '{{.APP_NAME}}' + APP_BINARY: '../../../bin/{{.APP_NAME}}' + ICON: '{{.APP_NAME}}.png' + DESKTOP_FILE: '../{{.APP_NAME}}.desktop' + OUTPUT_DIR: '../../../bin' + + create:deb: + summary: Creates a deb package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:deb + + create:rpm: + summary: Creates a rpm package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:rpm + + create:aur: + summary: Creates a arch linux packager package + deps: + - task: build + cmds: + - task: generate:dotdesktop + - task: generate:aur + + generate:deb: + summary: Creates a deb package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:rpm: + summary: Creates a rpm package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:aur: + summary: Creates a arch linux packager package + cmds: + - wails3 tool package -name "{{.APP_NAME}}" -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin + + generate:dotdesktop: + summary: Generates a `.desktop` file + dir: build + cmds: + - mkdir -p {{.ROOT_DIR}}/build/linux/appimage + - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}" + # Wrap Exec= with `env WEBKIT_DISABLE_DMABUF_RENDERER=1 ...` so launches + # from any desktop environment use the working renderer. See build/linux/Taskfile.yml :run for the matching dev-mode env block. + - sed -i -E 's|^Exec=([^ ]+)(.*)$|Exec=env WEBKIT_DISABLE_DMABUF_RENDERER=1 \1\2|' {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop + vars: + APP_NAME: '{{.APP_NAME}}' + EXEC: '{{.APP_NAME}}' + ICON: '{{.APP_NAME}}' + CATEGORIES: 'Development;' + OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop' + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}' + env: + # WebKitGTK 2.50's default DMA-BUF renderer fails on RDP, VirtualBox/QEMU, + # and some bare WMs (Fluxbox, dwm) where DRM dumb-buffer access is + # restricted. Disabling it falls back to the GLES2/cairo path which works + # everywhere. Production launchers must set this too. + WEBKIT_DISABLE_DMABUF_RENDERER: "1" + + sign:deb: + summary: Signs the DEB package + desc: | + Signs the .deb package with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:deb + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}} + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" + + sign:rpm: + summary: Signs the RPM package + desc: | + Signs the .rpm package with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:rpm + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}} + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" + + sign:packages: + summary: Signs all Linux packages (DEB and RPM) + desc: | + Signs both .deb and .rpm packages with a PGP key. + Configure PGP_KEY in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + cmds: + - task: sign:deb + - task: sign:rpm + preconditions: + - sh: '[ -n "{{.PGP_KEY}}" ]' + msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml" diff --git a/client/ui-wails/build/linux/appimage/build.sh b/client/ui-wails/build/linux/appimage/build.sh new file mode 100644 index 000000000..85901c34e --- /dev/null +++ b/client/ui-wails/build/linux/appimage/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Copyright (c) 2018-Present Lea Anthony +# SPDX-License-Identifier: MIT + +# Fail script on any error +set -euxo pipefail + +# Define variables +APP_DIR="${APP_NAME}.AppDir" + +# Create AppDir structure +mkdir -p "${APP_DIR}/usr/bin" +cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/" +cp "${ICON_PATH}" "${APP_DIR}/" +cp "${DESKTOP_FILE}" "${APP_DIR}/" + +if [[ $(uname -m) == *x86_64* ]]; then + # Download linuxdeploy and make it executable + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + + # Run linuxdeploy to bundle the application + ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage +else + # Download linuxdeploy and make it executable (arm64) + wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage + chmod +x linuxdeploy-aarch64.AppImage + + # Run linuxdeploy to bundle the application (arm64) + ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage +fi + +# Rename the generated AppImage +mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" + diff --git a/client/ui-wails/build/linux/desktop b/client/ui-wails/build/linux/desktop new file mode 100644 index 000000000..deadfe9f4 --- /dev/null +++ b/client/ui-wails/build/linux/desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Name=NetBird +Comment=NetBird desktop client +# The Exec line includes %u to pass the URL to the application +Exec=/usr/local/bin/netbird-ui %u +Terminal=false +Type=Application +Icon=netbird-ui +Categories=Utility; +StartupWMClass=netbird-ui + + diff --git a/client/ui-wails/build/linux/netbird-ui.desktop b/client/ui-wails/build/linux/netbird-ui.desktop new file mode 100755 index 000000000..a46e530c1 --- /dev/null +++ b/client/ui-wails/build/linux/netbird-ui.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=netbird-ui +Exec=netbird-ui +Icon=netbird-ui +Categories=Development; +Terminal=false +Keywords=wails +Version=1.0 +StartupNotify=false diff --git a/client/ui-wails/build/linux/nfpm/nfpm.yaml b/client/ui-wails/build/linux/nfpm/nfpm.yaml new file mode 100644 index 000000000..a6e468bfa --- /dev/null +++ b/client/ui-wails/build/linux/nfpm/nfpm.yaml @@ -0,0 +1,67 @@ +# Feel free to remove those if you don't want/need to use them. +# Make sure to check the documentation at https://nfpm.goreleaser.com +# +# The lines below are called `modelines`. See `:help modeline` + +name: "netbird-ui" +arch: ${GOARCH} +platform: "linux" +version: "0.0.1" +section: "default" +priority: "extra" +maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +description: "NetBird desktop client" +vendor: "NetBird" +homepage: "https://wails.io" +license: "MIT" +release: "1" + +contents: + - src: "./bin/netbird-ui" + dst: "/usr/local/bin/netbird-ui" + - src: "./build/appicon.png" + dst: "/usr/share/icons/hicolor/128x128/apps/netbird-ui.png" + - src: "./build/linux/netbird-ui.desktop" + dst: "/usr/share/applications/netbird-ui.desktop" + +# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1 +depends: + - libgtk-3-0 + - libwebkit2gtk-4.1-0 + +# Distribution-specific overrides for different package formats and WebKit versions +overrides: + # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) + rpm: + depends: + - gtk3 + - webkit2gtk4.1 + + # Arch Linux packages (WebKit 4.1) + archlinux: + depends: + - gtk3 + - webkit2gtk-4.1 + +# scripts section to ensure desktop database is updated after install +scripts: + postinstall: "./build/linux/nfpm/scripts/postinstall.sh" + # You can also add preremove, postremove if needed + # preremove: "./build/linux/nfpm/scripts/preremove.sh" + # postremove: "./build/linux/nfpm/scripts/postremove.sh" + +# replaces: +# - foobar +# provides: +# - bar +# depends: +# - gtk3 +# - libwebkit2gtk +# recommends: +# - whatever +# suggests: +# - something-else +# conflicts: +# - not-foo +# - not-bar +# changelog: "changelog.yaml" diff --git a/client/ui-wails/build/linux/nfpm/scripts/postinstall.sh b/client/ui-wails/build/linux/nfpm/scripts/postinstall.sh new file mode 100644 index 000000000..4bbb815a3 --- /dev/null +++ b/client/ui-wails/build/linux/nfpm/scripts/postinstall.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Update desktop database for .desktop file changes +# This makes the application appear in application menus and registers its capabilities. +if command -v update-desktop-database >/dev/null 2>&1; then + echo "Updating desktop database..." + update-desktop-database -q /usr/share/applications +else + echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2 +fi + +# Update MIME database for custom URL schemes (x-scheme-handler) +# This ensures the system knows how to handle your custom protocols. +if command -v update-mime-database >/dev/null 2>&1; then + echo "Updating MIME database..." + update-mime-database -n /usr/share/mime +else + echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2 +fi + +exit 0 diff --git a/client/ui-wails/build/linux/nfpm/scripts/postremove.sh b/client/ui-wails/build/linux/nfpm/scripts/postremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/client/ui-wails/build/linux/nfpm/scripts/postremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/client/ui-wails/build/linux/nfpm/scripts/preinstall.sh b/client/ui-wails/build/linux/nfpm/scripts/preinstall.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/client/ui-wails/build/linux/nfpm/scripts/preinstall.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/client/ui-wails/build/linux/nfpm/scripts/preremove.sh b/client/ui-wails/build/linux/nfpm/scripts/preremove.sh new file mode 100644 index 000000000..a9bf588e2 --- /dev/null +++ b/client/ui-wails/build/linux/nfpm/scripts/preremove.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/client/ui-wails/build/windows/Taskfile.yml b/client/ui-wails/build/windows/Taskfile.yml new file mode 100644 index 000000000..5aaf2db90 --- /dev/null +++ b/client/ui-wails/build/windows/Taskfile.yml @@ -0,0 +1,192 @@ +version: '3' + +includes: + common: ../Taskfile.yml + +vars: + # Signing configuration - edit these values for your project + # SIGN_CERTIFICATE: "path/to/certificate.pfx" + # SIGN_THUMBPRINT: "certificate-thumbprint" # Alternative to SIGN_CERTIFICATE + # TIMESTAMP_SERVER: "http://timestamp.digicert.com" + # + # Password is stored securely in system keychain. Run: wails3 setup signing + + # Docker image for cross-compilation with CGO (used when CGO_ENABLED=1 on non-Windows) + CROSS_IMAGE: wails-cross + +tasks: + build: + summary: Builds the application for Windows + cmds: + # CGO Windows builds from Linux use mingw-w64 (lighter than docker). + # Docker is only needed if mingw-w64 is unavailable. + - task: build:native + vars: + ARCH: '{{.ARCH}}' + DEV: '{{.DEV}}' + EXTRA_TAGS: '{{.EXTRA_TAGS}}' + vars: + CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}' + + build:native: + summary: Builds for Windows natively, or cross-compiles from Linux/macOS via mingw-w64. + internal: true + deps: + - task: common:go:mod:tidy + - task: common:build:frontend + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + DEV: + ref: .DEV + - task: common:generate:icons + preconditions: + # When cross-compiling with CGO from a non-Windows host, the mingw-w64 + # cross-gcc must be present. Native Windows builds skip this check. + - sh: '[ "{{OS}}" = "windows" ] || [ "{{.CGO_ENABLED}}" != "1" ] || command -v {{.CC}}' + msg: "{{.CC}} not found. Install with: sudo apt-get install gcc-mingw-w64-x86-64 (Debian/Ubuntu) / sudo dnf install mingw64-gcc (Fedora)" + cmds: + - task: generate:syso + - go build {{.BUILD_FLAGS}} -o "{{.BIN_DIR}}/{{.APP_NAME}}.exe" + - cmd: powershell Remove-item *.syso + platforms: [windows] + - cmd: rm -f *.syso + platforms: [linux, darwin] + vars: + BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}' + CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}' + CC: '{{.CC | default "x86_64-w64-mingw32-gcc"}}' + env: + GOOS: windows + CGO_ENABLED: '{{.CGO_ENABLED}}' + GOARCH: '{{.ARCH | default ARCH}}' + CC: '{{.CC}}' + + build:docker: + summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows) + internal: true + deps: + - task: common:build:frontend + - task: common:generate:icons + preconditions: + - sh: docker info > /dev/null 2>&1 + msg: "Docker is required for CGO cross-compilation. Please install Docker." + - sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1 + msg: | + Docker image '{{.CROSS_IMAGE}}' not found. + Build it first: wails3 task setup:docker + cmds: + - task: generate:syso + - docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}} + - docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin + - rm -f *.syso + vars: + DOCKER_ARCH: '{{.ARCH | default "amd64"}}' + # Mount Go module cache for faster builds + GO_CACHE_MOUNT: + sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"' + # Extract replace directives from go.mod and create -v mounts for each + REPLACE_MOUNTS: + sh: | + grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do + path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r') + # Convert relative paths to absolute + if [ "${path#/}" = "$path" ]; then + path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")" + fi + # Only mount if directory exists + if [ -d "$path" ]; then + echo "-v $path:$path:ro" + fi + done | tr '\n' ' ' + + package: + summary: Packages the application + cmds: + - task: '{{if eq (.FORMAT | default "nsis") "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}' + vars: + FORMAT: '{{.FORMAT | default "nsis"}}' + + generate:syso: + summary: Generates Windows `.syso` file + dir: build + cmds: + - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso + vars: + ARCH: '{{.ARCH | default ARCH}}' + + create:nsis:installer: + summary: Creates an NSIS installer + dir: build/windows/nsis + deps: + - task: build + cmds: + # Create the Microsoft WebView2 bootstrapper if it doesn't exist + - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis" + - | + {{if eq OS "windows"}} + makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi + {{else}} + makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi + {{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}' + + create:msix:package: + summary: Creates an MSIX package + deps: + - task: build + cmds: + - |- + wails3 tool msix \ + --config "{{.ROOT_DIR}}/wails.json" \ + --name "{{.APP_NAME}}" \ + --executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \ + --arch "{{.ARCH}}" \ + --out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \ + {{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \ + {{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \ + {{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}} + vars: + ARCH: '{{.ARCH | default ARCH}}' + CERT_PATH: '{{.CERT_PATH | default ""}}' + PUBLISHER: '{{.PUBLISHER | default ""}}' + USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}' + + install:msix:tools: + summary: Installs tools required for MSIX packaging + cmds: + - wails3 tool msix-install-tools + + run: + cmds: + - '{{.BIN_DIR}}/{{.APP_NAME}}.exe' + + sign: + summary: Signs the Windows executable + desc: | + Signs the .exe with an Authenticode certificate. + Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: build + cmds: + - wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' + msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" + + sign:installer: + summary: Signs the NSIS installer + desc: | + Creates and signs the NSIS installer. + Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file. + Password is retrieved from system keychain (run: wails3 setup signing) + deps: + - task: create:nsis:installer + cmds: + - wails3 tool sign --input "build/windows/nsis/{{.APP_NAME}}-installer.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}} + preconditions: + - sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]' + msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml" diff --git a/client/ui-wails/build/windows/icon.ico b/client/ui-wails/build/windows/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c3dad889d82909e1c8c03a0085c170c60caf1a22 GIT binary patch literal 18097 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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/build/windows/info.json b/client/ui-wails/build/windows/info.json new file mode 100644 index 000000000..432caf331 --- /dev/null +++ b/client/ui-wails/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "0.0.1" + }, + "info": { + "0000": { + "ProductVersion": "0.0.1", + "CompanyName": "NetBird", + "FileDescription": "NetBird desktop client", + "LegalCopyright": "© 2026, My Company", + "ProductName": "NetBird", + "Comments": "This is a comment" + } + } +} \ No newline at end of file diff --git a/client/ui-wails/build/windows/msix/app_manifest.xml b/client/ui-wails/build/windows/msix/app_manifest.xml new file mode 100644 index 000000000..0ae55ce77 --- /dev/null +++ b/client/ui-wails/build/windows/msix/app_manifest.xml @@ -0,0 +1,55 @@ + + + + + + + NetBird + NetBird + NetBird desktop client + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ui-wails/build/windows/msix/template.xml b/client/ui-wails/build/windows/msix/template.xml new file mode 100644 index 000000000..437a68097 --- /dev/null +++ b/client/ui-wails/build/windows/msix/template.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + false + NetBird + NetBird + NetBird desktop client + Assets\AppIcon.png + + + + + + + diff --git a/client/ui-wails/build/windows/nsis/project.nsi b/client/ui-wails/build/windows/nsis/project.nsi new file mode 100644 index 000000000..8d2530972 --- /dev/null +++ b/client/ui-wails/build/windows/nsis/project.nsi @@ -0,0 +1,114 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the wails_tools.nsh file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "my-project" # Default "netbird-ui" +## !define INFO_COMPANYNAME "My Company" # Default "NetBird" +## !define INFO_PRODUCTNAME "My Product Name" # Default "NetBird" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.0.1" +## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© 2026, My Company" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/client/ui-wails/build/windows/nsis/wails_tools.nsh b/client/ui-wails/build/windows/nsis/wails_tools.nsh new file mode 100644 index 000000000..fefa4004d --- /dev/null +++ b/client/ui-wails/build/windows/nsis/wails_tools.nsh @@ -0,0 +1,236 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "netbird-ui" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "NetBird" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "NetBird" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "0.0.1" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "© 2026, My Company" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + +!macroend \ No newline at end of file diff --git a/client/ui-wails/build/windows/wails.exe.manifest b/client/ui-wails/build/windows/wails.exe.manifest new file mode 100644 index 000000000..bfcd1c6ed --- /dev/null +++ b/client/ui-wails/build/windows/wails.exe.manifest @@ -0,0 +1,22 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + + + + + + + \ No newline at end of file diff --git a/client/ui-wails/frontend/Inter Font License.txt b/client/ui-wails/frontend/Inter Font License.txt new file mode 100644 index 000000000..b525cbf3a --- /dev/null +++ b/client/ui-wails/frontend/Inter Font License.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/client/ui-wails/frontend/index.html b/client/ui-wails/frontend/index.html new file mode 100644 index 000000000..95d7870be --- /dev/null +++ b/client/ui-wails/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + NetBird + + +

+ + + diff --git a/client/ui-wails/frontend/package.json b/client/ui-wails/frontend/package.json new file mode 100644 index 000000000..d2f52125d --- /dev/null +++ b/client/ui-wails/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "netbird-ui", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build:dev": "tsc && vite build --minify false --mode development", + "build": "tsc && vite build --mode production", + "preview": "vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@wailsio/runtime": "latest", + "clsx": "^2.1.1", + "lucide-react": "^0.469.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.3", + "tailwind-merge": "^2.6.0" + }, + "devDependencies": { + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.7.3", + "vite": "^6.0.7" + } +} diff --git a/client/ui-wails/frontend/pnpm-lock.yaml b/client/ui-wails/frontend/pnpm-lock.yaml new file mode 100644 index 000000000..58f7576a8 --- /dev/null +++ b/client/ui-wails/frontend/pnpm-lock.yaml @@ -0,0 +1,1758 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@wailsio/runtime': + specifier: latest + version: 3.0.0-alpha.79 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.469.0 + version: 0.469.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^7.1.3 + version: 7.14.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.1 + devDependencies: + '@types/react': + specifier: ^18.3.18 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.2(jiti@1.21.7)) + autoprefixer: + specifier: ^10.4.20 + version: 10.5.0(postcss@8.5.12) + postcss: + specifier: ^8.5.1 + version: 8.5.12 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vite: + specifier: ^6.0.7 + version: 6.4.2(jiti@1.21.7) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@wailsio/runtime@3.0.0-alpha.79': + resolution: {integrity: sha512-NITzxKmJsMEruc39L166lbPJVECxzcbdqpHVqOOF7Cu/7Zqk/e3B/gNpkUjhNyo5rVb3V1wpS8oEgLUmpu1cwA==} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + baseline-browser-mapping@2.10.24: + resolution: {integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001791: + resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} + engines: {node: ^10 || ^12 || >=14} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.14.2: + resolution: {integrity: sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.14.2: + resolution: {integrity: sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwind-merge@2.6.1: + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/estree@1.0.8': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@1.21.7))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + '@wailsio/runtime@3.0.0-alpha.79': {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + autoprefixer@10.5.0(postcss@8.5.12): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001791 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.12 + postcss-value-parser: 4.2.0 + + baseline-browser-mapping@2.10.24: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.24 + caniuse-lite: 1.0.30001791 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001791: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + clsx@2.1.1: {} + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + electron-to-chromium@1.5.344: {} + + es-errors@1.3.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fraction.js@5.3.4: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.3 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.469.0(react@18.3.1): + dependencies: + react: 18.3.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + node-releases@2.0.38: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.12): + dependencies: + postcss: 8.5.12 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.12): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.12 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.12): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.12 + + postcss-nested@6.2.0(postcss@8.5.12): + dependencies: + postcss: 8.5.12 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.12: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react-router-dom@7.14.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.14.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.14.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.1.1 + react: 18.3.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + set-cookie-parser@2.7.2: {} + + source-map-js@1.2.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwind-merge@2.6.1: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.12 + postcss-import: 15.1.0(postcss@8.5.12) + postcss-js: 4.1.0(postcss@8.5.12) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.12) + postcss-nested: 6.2.0(postcss@8.5.12) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + typescript@5.9.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + vite@6.4.2(jiti@1.21.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.12 + rollup: 4.60.2 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 + jiti: 1.21.7 + + yallist@3.1.1: {} diff --git a/client/ui-wails/frontend/postcss.config.js b/client/ui-wails/frontend/postcss.config.js new file mode 100644 index 000000000..2aa7205d4 --- /dev/null +++ b/client/ui-wails/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/client/ui-wails/frontend/public/Inter-Medium.ttf b/client/ui-wails/frontend/public/Inter-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a01f3777a6fc284b7a720c0f8248a27066389ef9 GIT binary patch literal 315132 zcmd>{3Ajzw|M0(S?X!n-?sds<&AMdHJY`De%rlit*Gxz*k|ZQaMWm8sCL|H5Orgje zLX;sYN}^G@hWmcjK4)L_tKaY+-sgFr_ul8T*4k_QuJ8KpwfA0opS?wiNG6<+$lZ18 z)h}DV>>3eWoroNFw`kt_=)7a6M2)O0s^ph46$l{1e>k6gWyQwEyWNY9Gz_3HOX&vyAf z)S{Q>5MAJ8?;gqBOFo}`FX>%Kuhg4}e4BH=#y0Xu!QTA`56g7y>@Ol2dy5p=(eJ@- z$>AZbE{NPwf$hEhlZOqA#4CsVv?oaelKb~){O#aW5vPqvIP<^B5&SN zzrXqG{ycsElt;op`!QDN*v#5b&hDJC`frju&adR?&uep=z_GS14*mY-l@WRR#%E^J z5gn_;)b6n*lGS$$ihj6P@4+ggNJ_r}gHSL1OfsF!hHQL}O9s)M+nsb6q^Qx|dn(6vPA zI=TUFBi#nKoleH>rm-LWq<#hWRsAaNOuY_wquz}Bk=}y4L+`}htv|#4+^Q|gs%JIC zZDKuyJJ=e6`-n9Tcf7Rn$I0&?vs26| zhFiiZfm_XC?3_kUBivR_Pu$*4Z`=o*2XF^C18^U9hKc1o;ygllxHE$A80QJxxz0jS zuIEyxThJ|tTgUBy+u7}m`?&iA?o;klxKF!J<38g~!kywy!JX<(#hvD&2lrK%QFa%* zi*es@m*OsSm*cK3;-OD(xHp{e)}Vk zj}jgoW^}?YgkQj&9-fZ-es~?J8^W6i?+WiGygz&h_el5{?w4U|4u2c|7WaG@4Tpb< z&3F~`P%|^Jmxw&?#K*!)a@fDd!depS)3LB6rR?`&;m{3vY^h|U1HT@J_`b2QD{bt= zSlAQKPKbrWk`y{03&#=O6AQ=R5YC{ItYxwI1gT)5Z@>Ob60rux!na7g)jbx@d_A7& zF41ZhjaN$YSmk12Emid&v9Kle_1m#?vid~S~a&&R@fB%ch5h4XS)iH$F^BublD{H+{eYRAG!9CZrB!ugRWM=V@`kdUjN zBuh7WfLsk_FyVgEhwba}^<)_FgGi|@50cvbPdWcF{jc){v8|mem2h13VpUJrSMT9Ty>3?!}(7+ojH-SDu#-J4JfsYA$L->-WxHJI`PNg0eR zrk)%OnM*bDYd9+u%CMXc}>W)X;S;MZTi-o7KTVyUt%M{ zpqpyy=F2mb5@zI$)P2a=gfb8NwcIE}lbcEgd7JyvnwADob^xWW$D|N5?1t^0zAS_O zQtAi&vUMquf)xFc*ywzS->W{9Gc5#~G(9rbkmBnx1>HsEAMVR+MkEQ{8BLp>rLbir zGrCS9&XgYD_t!`p9oeqL4kG72YyWz`jLr6f!IZesh8y~=80{Nt9gJqGN@@O6kj9Lc zv5gcd1xCxI{5E@$-W-c)!v1aS(MVgL6eDkvv?kP((8K7#*oWzf$r0U;dyvc6hZ(zQ zpG@0PYchFTv)7wZyDs_ln$4cljk3`iN|37^TSfn*nUt7Tjja8l6mAc+*oxXs%n;lp+B0Ju?NN%a&mjNx zQA%QOYv@ZAwTBy1qIx#6N9RXVm)WNV`Ln4R6=O?*1lLQJyvZW|tkd#HTL!IG;ff$? zK_%HvmWsrckxH~t1`Xbrs}-Zz6sjSqu?f?oo7-qboY_;%TtDcBTuC=cQU@IjWS%m1 z7|f>SXw48EVPi4Z+enfY7y8oNVG@2$=8*Bb|Yth z#wE~FX<80?P)sDQpt&yPN^GUn|6ii|e_rpkJ?_f1l28#`I0oDP{d)d+`M8MGzY({b zoVDP42V>h{^2j=f$(R9Lo&d7Uiy12EF<#m^3% zk;mAc3tNaQ3?C4lt$)Av9sZ_hKH0z`#ŹD@)<7ltOtn$SSm;LMTrPEA?sR43d= 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�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 literal 0 HcmV?d00001 diff --git a/client/ui-wails/frontend/public/react.svg b/client/ui-wails/frontend/public/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/client/ui-wails/frontend/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/ui-wails/frontend/public/style.css b/client/ui-wails/frontend/public/style.css new file mode 100644 index 000000000..0ba9cf5cc --- /dev/null +++ b/client/ui-wails/frontend/public/style.css @@ -0,0 +1,157 @@ +:root { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: rgba(27, 38, 54, 1); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + src: local(""), + url("./Inter-Medium.ttf") format("truetype"); +} + +h3 { + font-size: 3em; + line-height: 1.1; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +button { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.result { + height: 20px; + line-height: 20px; +} + +body { + margin: 0; + display: flex; + place-items: center; + place-content: center; + min-width: 320px; + min-height: 100vh; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} + +.logo:hover { + filter: drop-shadow(0 0 2em #e80000aa); +} + +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; + text-align: center; +} + +.footer { + margin-top: 1rem; + align-content: center; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + color: black; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} diff --git a/client/ui-wails/frontend/public/wails.png b/client/ui-wails/frontend/public/wails.png new file mode 100644 index 0000000000000000000000000000000000000000..8bdf424833bfb5b6b34a0807224e94533f738f2e GIT binary patch literal 9057 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 + + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} diff --git a/client/ui-wails/frontend/src/Layout.tsx b/client/ui-wails/frontend/src/Layout.tsx new file mode 100644 index 000000000..46349571b --- /dev/null +++ b/client/ui-wails/frontend/src/Layout.tsx @@ -0,0 +1,45 @@ +import { NavLink, Outlet } from "react-router-dom"; +import { Activity, Bug, Network, Settings as SettingsIcon, Share2, Users } from "lucide-react"; +import { cn } from "./lib/cn"; + +const nav = [ + { to: "/", label: "Status", icon: Activity, end: true }, + { to: "/peers", label: "Peers", icon: Share2 }, + { to: "/networks", label: "Networks", icon: Network }, + { to: "/profiles", label: "Profiles", icon: Users }, + { to: "/settings", label: "Settings", icon: SettingsIcon }, + { to: "/debug", label: "Debug", icon: Bug }, +]; + +export default function Layout() { + return ( +
+ +
+ +
+
+ ); +} diff --git a/client/ui-wails/frontend/src/components/Button.tsx b/client/ui-wails/frontend/src/components/Button.tsx new file mode 100644 index 000000000..a1b2867b3 --- /dev/null +++ b/client/ui-wails/frontend/src/components/Button.tsx @@ -0,0 +1,42 @@ +import { ButtonHTMLAttributes, forwardRef } from "react"; +import { cn } from "../lib/cn"; + +type Variant = "primary" | "secondary" | "ghost" | "danger"; +type Size = "sm" | "md"; + +const variants: Record = { + primary: "bg-netbird text-white hover:bg-netbird-500 disabled:bg-nb-gray-300", + secondary: + "bg-nb-gray-100 text-nb-gray-900 hover:bg-nb-gray-200 dark:bg-nb-gray-900 dark:text-nb-gray-50 dark:hover:bg-nb-gray-800", + ghost: + "bg-transparent text-nb-gray-700 hover:bg-nb-gray-100 dark:text-nb-gray-200 dark:hover:bg-nb-gray-900", + danger: "bg-red-600 text-white hover:bg-red-500", +}; + +const sizes: Record = { + sm: "h-7 px-2 text-xs", + md: "h-9 px-3 text-sm", +}; + +interface Props extends ButtonHTMLAttributes { + variant?: Variant; + size?: Size; +} + +export const Button = forwardRef(function Button( + { variant = "primary", size = "md", className, ...rest }, + ref, +) { + return ( + + {(label || description) && ( + + {label && {label}} + {description && ( + {description} + )} + + )} + + ); +} diff --git a/client/ui-wails/frontend/src/components/Tabs.tsx b/client/ui-wails/frontend/src/components/Tabs.tsx new file mode 100644 index 000000000..e82029c4c --- /dev/null +++ b/client/ui-wails/frontend/src/components/Tabs.tsx @@ -0,0 +1,40 @@ +import { ReactNode, useState } from "react"; +import { cn } from "../lib/cn"; + +interface Tab { + value: string; + label: string; + content: ReactNode; +} + +interface Props { + tabs: Tab[]; + initial?: string; +} + +export function Tabs({ tabs, initial }: Props) { + const [active, setActive] = useState(initial ?? tabs[0]?.value); + return ( +
+
+ {tabs.map((t) => ( + + ))} +
+
+ {tabs.find((t) => t.value === active)?.content} +
+
+ ); +} diff --git a/client/ui-wails/frontend/src/hooks/useStatus.ts b/client/ui-wails/frontend/src/hooks/useStatus.ts new file mode 100644 index 000000000..45a006907 --- /dev/null +++ b/client/ui-wails/frontend/src/hooks/useStatus.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from "react"; +import { Events } from "@wailsio/runtime"; +import { Peers } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { Status } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; + +const EVENT_STATUS = "netbird:status"; + +// useStatus loads the current daemon status once and re-renders whenever the +// peers service emits a fresh snapshot over the Wails event bus. +export function useStatus(): { status: Status | null; error: string | null } { + const [status, setStatus] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + Peers.Get() + .then((s) => { + if (!cancelled) setStatus(s); + }) + .catch((e: unknown) => { + if (!cancelled) setError(String(e)); + }); + + const off = Events.On(EVENT_STATUS, (ev: { data: Status }) => { + setStatus(ev.data); + setError(null); + }); + + return () => { + cancelled = true; + off(); + }; + }, []); + + return { status, error }; +} diff --git a/client/ui-wails/frontend/src/index.css b/client/ui-wails/frontend/src/index.css new file mode 100644 index 000000000..d6c7d11bb --- /dev/null +++ b/client/ui-wails/frontend/src/index.css @@ -0,0 +1,17 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body, +#root { + height: 100%; +} + +body { + @apply bg-white text-nb-gray-900 antialiased; +} + +.dark body { + @apply bg-nb-gray-950 text-nb-gray-50; +} diff --git a/client/ui-wails/frontend/src/lib/cn.ts b/client/ui-wails/frontend/src/lib/cn.ts new file mode 100644 index 000000000..a5ef19350 --- /dev/null +++ b/client/ui-wails/frontend/src/lib/cn.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/client/ui-wails/frontend/src/main.tsx b/client/ui-wails/frontend/src/main.tsx new file mode 100644 index 000000000..8b1ddb971 --- /dev/null +++ b/client/ui-wails/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/client/ui-wails/frontend/src/pages/Debug.tsx b/client/ui-wails/frontend/src/pages/Debug.tsx new file mode 100644 index 000000000..929e4325f --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Debug.tsx @@ -0,0 +1,105 @@ +import { useState } from "react"; +import { Debug as DebugSvc } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { DebugBundleResult } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Button } from "../components/Button"; +import { Input } from "../components/Input"; +import { Switch } from "../components/Switch"; +import { Card } from "../components/Card"; + +export default function Debug() { + const [anonymize, setAnonymize] = useState(true); + const [systemInfo, setSystemInfo] = useState(true); + const [upload, setUpload] = useState(false); + const [uploadUrl, setUploadUrl] = useState(""); + const [logFiles, setLogFiles] = useState(0); + + const [running, setRunning] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + const run = async () => { + setRunning(true); + setResult(null); + setError(null); + try { + const r = await DebugSvc.Bundle({ + anonymize, + systemInfo, + uploadUrl: upload ? uploadUrl : "", + logFileCount: logFiles, + }); + setResult(r); + } catch (e) { + setError(String(e)); + } finally { + setRunning(false); + } + }; + + return ( +
+

Debug bundle

+ + + + + + {upload && ( + setUploadUrl(e.target.value)} + /> + )} + setLogFiles(Number(e.target.value))} + /> +
+ +
+
+ + {error &&

{error}

} + + {result && ( + + {result.path && ( +

+ Path:{" "} + {result.path} +

+ )} + {result.uploadedKey && ( +

+ Uploaded key:{" "} + {result.uploadedKey} +

+ )} + {result.uploadFailureReason && ( +

+ Upload failed: {result.uploadFailureReason} +

+ )} +
+ )} +
+ ); +} diff --git a/client/ui-wails/frontend/src/pages/LoginUrl.tsx b/client/ui-wails/frontend/src/pages/LoginUrl.tsx new file mode 100644 index 000000000..71c8e88a1 --- /dev/null +++ b/client/ui-wails/frontend/src/pages/LoginUrl.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { ExternalLink } from "lucide-react"; +import { Button } from "../components/Button"; + +export default function LoginUrl() { + const [url, setUrl] = useState(""); + + useEffect(() => { + const params = new URLSearchParams(window.location.hash.split("?")[1] ?? ""); + setUrl(params.get("url") ?? ""); + }, []); + + if (!url) { + return ( +
+ No login URL provided. +
+ ); + } + + return ( +
+

Continue in your browser

+

+ Open the following URL to finish signing in. +

+ +

{url}

+
+ ); +} diff --git a/client/ui-wails/frontend/src/pages/Networks.tsx b/client/ui-wails/frontend/src/pages/Networks.tsx new file mode 100644 index 000000000..ea3bd055e --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Networks.tsx @@ -0,0 +1,159 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { RefreshCw } from "lucide-react"; +import { Networks as NetworksSvc } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { Network } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Button } from "../components/Button"; +import { Tabs } from "../components/Tabs"; + +export default function Networks() { + const [routes, setRoutes] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const refresh = useCallback(async () => { + setLoading(true); + try { + const list = await NetworksSvc.List(); + setRoutes(list); + setError(null); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + refresh(); + }, [refresh]); + + const toggle = async (id: string, selected: boolean) => { + try { + if (selected) { + await NetworksSvc.Deselect({ networkIds: [id], append: false, all: false }); + } else { + await NetworksSvc.Select({ networkIds: [id], append: true, all: false }); + } + await refresh(); + } catch (e) { + setError(String(e)); + } + }; + + const setAll = async (ids: string[], on: boolean) => { + try { + if (on) { + await NetworksSvc.Select({ networkIds: ids, append: false, all: true }); + } else { + await NetworksSvc.Deselect({ networkIds: ids, append: false, all: true }); + } + await refresh(); + } catch (e) { + setError(String(e)); + } + }; + + const overlapping = useMemo(() => filterOverlapping(routes), [routes]); + const exitNodes = useMemo(() => routes.filter((r) => r.range === "0.0.0.0/0"), [routes]); + + return ( +
+
+

Networks

+ +
+ + {error && ( +

{error}

+ )} + +
+ , + }, + { + value: "overlap", + label: `Overlapping (${overlapping.length})`, + content: , + }, + { + value: "exit", + label: `Exit-node (${exitNodes.length})`, + content: , + }, + ]} + /> +
+
+ ); +} + +function NetworkList({ + routes, + onToggle, + onSetAll, +}: { + routes: Network[]; + onToggle: (id: string, selected: boolean) => void; + onSetAll: (ids: string[], on: boolean) => void; +}) { + if (routes.length === 0) { + return

No networks.

; + } + const ids = routes.map((r) => r.id); + return ( +
+
+ + +
+
    + {routes.map((r) => ( +
  • + onToggle(r.id, r.selected)} + className="mt-1 h-4 w-4 accent-netbird" + /> +
    +

    {r.id}

    +

    {r.range}

    + {r.domains.length > 0 && ( +

    + {r.domains.join(", ")} +

    + )} +
    +
  • + ))} +
+
+ ); +} + +function filterOverlapping(routes: Network[]): Network[] { + const byRange = new Map(); + for (const r of routes) { + if (r.domains.length > 0) continue; + const arr = byRange.get(r.range) ?? []; + arr.push(r); + byRange.set(r.range, arr); + } + const out: Network[] = []; + for (const arr of byRange.values()) { + if (arr.length > 1) out.push(...arr); + } + return out; +} diff --git a/client/ui-wails/frontend/src/pages/Peers.tsx b/client/ui-wails/frontend/src/pages/Peers.tsx new file mode 100644 index 000000000..f1522ca87 --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Peers.tsx @@ -0,0 +1,211 @@ +import { useMemo, useState } from "react"; +import { ChevronDown, ChevronRight, Network, ShieldCheck, Zap } from "lucide-react"; +import { useStatus } from "../hooks/useStatus"; +import type { PeerStatus } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Card } from "../components/Card"; +import { Input } from "../components/Input"; +import { cn } from "../lib/cn"; + +export default function Peers() { + const { status } = useStatus(); + const [filter, setFilter] = useState(""); + const [expanded, setExpanded] = useState(null); + + const peers = useMemo(() => { + const all = status?.peers ?? []; + if (!filter.trim()) return all; + const q = filter.trim().toLowerCase(); + return all.filter( + (p) => + p.fqdn.toLowerCase().includes(q) || + p.ip.toLowerCase().includes(q) || + p.networks.some((n) => n.toLowerCase().includes(q)), + ); + }, [status?.peers, filter]); + + return ( +
+
+

+ Peers + + {status?.peers?.length ?? 0} + +

+
+ setFilter(e.target.value)} + /> +
+
+ + {peers.length === 0 ? ( + + {status?.peers?.length === 0 + ? "No peers visible from this client." + : "No peers match the filter."} + + ) : ( +
    + {peers.map((p) => ( + setExpanded(expanded === p.pubKey ? null : p.pubKey)} + /> + ))} +
+ )} +
+ ); +} + +function PeerRow({ + peer, + expanded, + onToggle, +}: { + peer: PeerStatus; + expanded: boolean; + onToggle: () => void; +}) { + return ( +
  • + + + {expanded && } +
  • + ); +} + +function PeerDetails({ peer }: { peer: PeerStatus }) { + return ( +
    + + + + + + + {peer.relayed && ( + + )} + {peer.networks.length > 0 && ( + + )} +
    + ); +} + +function Detail({ label, value, mono }: { label: string; value: string; mono?: boolean }) { + return ( +
    + {label} + + {value} + +
    + ); +} + +function ChevronIcon({ expanded }: { expanded: boolean }) { + const Icon = expanded ? ChevronDown : ChevronRight; + return ; +} + +function StateBadge({ state }: { state: string }) { + const cls = "h-2 w-2 rounded-full shrink-0"; + switch (state) { + case "Connected": + return ; + case "Connecting": + return ; + case "Idle": + return ; + default: + return ; + } +} + +function RouteIcon({ relayed, connected }: { relayed: boolean; connected: boolean }) { + if (!connected) { + return ; + } + if (relayed) { + return ( + + Relayed + + ); + } + return ( + + P2P + + ); +} + +function fmtBytes(n: number): string { + if (n < 1024) return `${n} B`; + if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; + if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`; + return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`; +} + +function fmtRelative(unixSec: number): string { + if (!unixSec) return "—"; + const ageSec = Math.max(0, Math.floor(Date.now() / 1000) - unixSec); + if (ageSec < 60) return `${ageSec}s ago`; + if (ageSec < 3600) return `${Math.floor(ageSec / 60)}m ago`; + if (ageSec < 86400) return `${Math.floor(ageSec / 3600)}h ago`; + return `${Math.floor(ageSec / 86400)}d ago`; +} diff --git a/client/ui-wails/frontend/src/pages/Profiles.tsx b/client/ui-wails/frontend/src/pages/Profiles.tsx new file mode 100644 index 000000000..3a1035afa --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Profiles.tsx @@ -0,0 +1,173 @@ +import { FormEvent, useCallback, useEffect, useState } from "react"; +import { Plus, RefreshCw } from "lucide-react"; +import { + Profiles as ProfilesSvc, + Connection, +} from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { Profile } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Button } from "../components/Button"; +import { Input } from "../components/Input"; +import { Card } from "../components/Card"; + +export default function Profiles() { + const [username, setUsername] = useState(""); + const [profiles, setProfiles] = useState([]); + const [error, setError] = useState(null); + const [adding, setAdding] = useState(false); + + const refresh = useCallback(async () => { + try { + const u = username || (await ProfilesSvc.Username()); + if (!username) setUsername(u); + const list = await ProfilesSvc.List(u); + setProfiles(list); + setError(null); + } catch (e) { + setError(String(e)); + } + }, [username]); + + useEffect(() => { + refresh(); + }, [refresh]); + + const select = async (name: string) => { + try { + await ProfilesSvc.Switch({ profileName: name, username }); + await Connection.Up({ profileName: name, username }); + await refresh(); + } catch (e) { + setError(String(e)); + } + }; + + const deregister = async (name: string) => { + try { + await Connection.Logout({ profileName: name, username }); + await refresh(); + } catch (e) { + setError(String(e)); + } + }; + + const remove = async (name: string) => { + if (name === "default") return; + try { + await ProfilesSvc.Remove({ profileName: name, username }); + await refresh(); + } catch (e) { + setError(String(e)); + } + }; + + return ( +
    +
    +

    Profiles

    +
    + + +
    +
    + + {error &&

    {error}

    } + +
    + {profiles.map((p) => ( + + select(p.name)} + className="h-4 w-4 accent-netbird" + /> +
    +

    {p.name}

    + {p.isActive &&

    Active

    } +
    + + +
    + ))} + {profiles.length === 0 && ( +

    No profiles.

    + )} +
    + + {adding && ( + setAdding(false)} + onAdded={async () => { + setAdding(false); + await refresh(); + }} + /> + )} +
    + ); +} + +function AddDialog({ + username, + onClose, + onAdded, +}: { + username: string; + onClose: () => void; + onAdded: () => void; +}) { + const [name, setName] = useState(""); + const [err, setErr] = useState(null); + + const submit = async (e: FormEvent) => { + e.preventDefault(); + if (!name.trim()) return; + try { + await ProfilesSvc.Add({ profileName: name.trim(), username }); + onAdded(); + } catch (e) { + setErr(String(e)); + } + }; + + return ( +
    +
    +

    New profile

    + setName(e.target.value)} + /> + {err &&

    {err}

    } +
    + + +
    +
    +
    + ); +} diff --git a/client/ui-wails/frontend/src/pages/QuickActions.tsx b/client/ui-wails/frontend/src/pages/QuickActions.tsx new file mode 100644 index 000000000..749b4bf8a --- /dev/null +++ b/client/ui-wails/frontend/src/pages/QuickActions.tsx @@ -0,0 +1,40 @@ +import { CheckCircle2, Circle, Loader2, Power } from "lucide-react"; +import { useStatus } from "../hooks/useStatus"; +import { Connection } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import { Button } from "../components/Button"; +import { cn } from "../lib/cn"; + +export default function QuickActions() { + const { status } = useStatus(); + const state = status?.status ?? "Disconnected"; + const connected = state === "Connected"; + const connecting = state === "Connecting"; + + return ( +
    + +

    {state}

    + {connected ? ( + + ) : ( + + )} +
    + ); +} + +function Icon({ state }: { state: string }) { + const cls = "h-12 w-12"; + switch (state) { + case "Connected": + return ; + case "Connecting": + return ; + default: + return ; + } +} diff --git a/client/ui-wails/frontend/src/pages/Settings.tsx b/client/ui-wails/frontend/src/pages/Settings.tsx new file mode 100644 index 000000000..3781611b6 --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Settings.tsx @@ -0,0 +1,240 @@ +import { useCallback, useEffect, useState } from "react"; +import { + Settings as SettingsSvc, + Profiles as ProfilesSvc, +} from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { Config } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Button } from "../components/Button"; +import { Input } from "../components/Input"; +import { Switch } from "../components/Switch"; +import { Tabs } from "../components/Tabs"; + +interface Ctx { + cfg: Config; + setField: (k: K, v: Config[K]) => void; +} + +export default function Settings() { + const [username, setUsername] = useState(""); + const [profile, setProfile] = useState(""); + const [cfg, setCfg] = useState(null); + const [error, setError] = useState(null); + const [saving, setSaving] = useState(false); + + const load = useCallback(async () => { + try { + const u = await ProfilesSvc.Username(); + const active = await ProfilesSvc.GetActive(); + const profileName = active.profileName || "default"; + setUsername(u); + setProfile(profileName); + const c = await SettingsSvc.GetConfig({ profileName, username: u }); + setCfg(c); + setError(null); + } catch (e) { + setError(String(e)); + } + }, []); + + useEffect(() => { + load(); + }, [load]); + + const setField: Ctx["setField"] = (k, v) => { + setCfg((c) => (c ? { ...c, [k]: v } : c)); + }; + + const save = async () => { + if (!cfg) return; + setSaving(true); + try { + await SettingsSvc.SetConfig({ + profileName: profile, + username, + managementUrl: cfg.managementUrl, + adminUrl: cfg.adminUrl, + interfaceName: cfg.interfaceName, + wireguardPort: cfg.wireguardPort, + mtu: cfg.mtu, + preSharedKey: cfg.preSharedKey, + disableAutoConnect: cfg.disableAutoConnect, + serverSshAllowed: cfg.serverSshAllowed, + rosenpassEnabled: cfg.rosenpassEnabled, + rosenpassPermissive: cfg.rosenpassPermissive, + disableNotifications: cfg.disableNotifications, + lazyConnectionEnabled: cfg.lazyConnectionEnabled, + blockInbound: cfg.blockInbound, + networkMonitor: cfg.networkMonitor, + disableClientRoutes: cfg.disableClientRoutes, + disableServerRoutes: cfg.disableServerRoutes, + disableDns: cfg.disableDns, + blockLanAccess: cfg.blockLanAccess, + enableSshRoot: cfg.enableSshRoot, + enableSshSftp: cfg.enableSshSftp, + enableSshLocalPortForwarding: cfg.enableSshLocalPortForwarding, + enableSshRemotePortForwarding: cfg.enableSshRemotePortForwarding, + disableSshAuth: cfg.disableSshAuth, + sshJwtCacheTtl: cfg.sshJwtCacheTtl, + }); + setError(null); + } catch (e) { + setError(String(e)); + } finally { + setSaving(false); + } + }; + + if (!cfg) { + return
    Loading…
    ; + } + + const ctx: Ctx = { cfg, setField }; + + return ( +
    +
    +

    Settings

    + +
    + {error &&

    {error}

    } +
    + }, + { value: "net", label: "Network", content: }, + { value: "ssh", label: "SSH", content: }, + ]} + /> +
    +
    + ); +} + +function ConnectionTab({ cfg, setField }: Ctx) { + return ( +
    + setField("managementUrl", e.target.value)} + /> + setField("preSharedKey", e.target.value)} + /> + setField("interfaceName", e.target.value)} + /> +
    + setField("wireguardPort", Number(e.target.value))} + /> + setField("mtu", Number(e.target.value))} + /> +
    + setField("rosenpassEnabled", v)} + label="Rosenpass (post-quantum)" + /> + setField("rosenpassPermissive", v)} + label="Rosenpass permissive mode" + /> +
    + ); +} + +function NetworkTab({ cfg, setField }: Ctx) { + return ( +
    + setField("networkMonitor", v)} + label="Network monitor" + /> + setField("disableDns", v)} + label="Disable DNS" + /> + setField("disableClientRoutes", v)} + label="Disable client routes" + /> + setField("disableServerRoutes", v)} + label="Disable server routes" + /> + setField("blockLanAccess", v)} + label="Block LAN access" + /> + setField("blockInbound", v)} + label="Block inbound connections" + /> +
    + ); +} + +function SSHTab({ cfg, setField }: Ctx) { + return ( +
    + setField("serverSshAllowed", v)} + label="Server SSH allowed" + /> + setField("enableSshRoot", v)} + label="SSH root login" + /> + setField("enableSshSftp", v)} + label="SFTP" + /> + setField("enableSshLocalPortForwarding", v)} + label="Local port forwarding" + /> + setField("enableSshRemotePortForwarding", v)} + label="Remote port forwarding" + /> + setField("disableSshAuth", v)} + label="Disable SSH auth" + /> + setField("sshJwtCacheTtl", Number(e.target.value))} + /> +
    + ); +} diff --git a/client/ui-wails/frontend/src/pages/Status.tsx b/client/ui-wails/frontend/src/pages/Status.tsx new file mode 100644 index 000000000..ec0533568 --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Status.tsx @@ -0,0 +1,161 @@ +import { CheckCircle2, Circle, Loader2, AlertTriangle, Power } from "lucide-react"; +import { useStatus } from "../hooks/useStatus"; +import { Connection } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; +import type { SystemEvent } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services/models.js"; +import { Button } from "../components/Button"; +import { Card } from "../components/Card"; +import { cn } from "../lib/cn"; + +export default function Status() { + const { status, error } = useStatus(); + + const connState = status?.status ?? "Disconnected"; + const connected = connState === "Connected"; + const connecting = connState === "Connecting"; + + const connect = () => Connection.Up({ profileName: "", username: "" }).catch(console.error); + const disconnect = () => Connection.Down().catch(console.error); + + return ( +
    +
    +
    + +
    +

    {connState}

    +

    + {status?.local.fqdn || "—"} +

    +
    +
    +
    + + +
    +
    + + {error && ( +
    + + {error} +
    + )} + +
    + + + + +
    + + +

    + Recent events +

    + {(() => { + const events = dedupEvents(status?.events ?? []).slice(0, 8); + if (events.length === 0) { + return

    No recent events.

    ; + } + return ( +
      + {events.map((e, i) => ( +
    • + + {e.severity} + + + {e.userMessage || e.message} + +
    • + ))} +
    + ); + })()} +
    +
    + ); +} + +function StateIcon({ state }: { state: string }) { + const cls = "h-7 w-7"; + switch (state) { + case "Connected": + return ; + case "Connecting": + return ; + case "Error": + return ; + default: + return ; + } +} + +function InfoCard({ label, value }: { label: string; value: string }) { + return ( + +

    {label}

    +

    {value}

    +
    + ); +} + +// dedupEvents collapses repeated daemon events that carry the same logical +// content. The daemon emits one "new_version_available" event per check tick, +// so its 10-event ring buffer fills with duplicates after a quiet hour. Same +// goes for periodic "DNS unreachable" or "auth retry" events. We key by +// message + a small set of identity-bearing metadata fields and keep the +// newest occurrence (the events array is already in publish order). +function dedupEvents(events: SystemEvent[]): SystemEvent[] { + const seen = new Set(); + const out: SystemEvent[] = []; + for (let i = events.length - 1; i >= 0; i--) { + const e = events[i]; + const md = e.metadata ?? {}; + const key = [ + e.severity, + e.category, + e.userMessage || e.message, + md["new_version_available"] ?? "", + md["enforced"] ?? "", + ].join("|"); + // eslint-disable-next-line no-console + console.log("[dedup]", { key, event: e }); + if (seen.has(key)) continue; + seen.add(key); + out.unshift(e); + } + return out; +} + +function LinkCard({ + label, + link, +}: { + label: string; + link?: { url: string; connected: boolean; error?: string }; +}) { + return ( + +
    +

    {label}

    + +
    +

    + {link?.url || "—"} +

    + {link?.error && ( +

    {link.error}

    + )} +
    + ); +} diff --git a/client/ui-wails/frontend/src/pages/Update.tsx b/client/ui-wails/frontend/src/pages/Update.tsx new file mode 100644 index 000000000..04d9eb245 --- /dev/null +++ b/client/ui-wails/frontend/src/pages/Update.tsx @@ -0,0 +1,61 @@ +import { useEffect, useState } from "react"; +import { Loader2 } from "lucide-react"; +import { Update as UpdateSvc } from "../../bindings/github.com/netbirdio/netbird/client/ui-wails/services"; + +const TIMEOUT_MS = 15 * 60 * 1000; + +export default function Update() { + const [done, setDone] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + UpdateSvc.Trigger().catch((e) => !cancelled && setError(String(e))); + + const start = Date.now(); + const timer = setInterval(async () => { + if (Date.now() - start > TIMEOUT_MS) { + setError("Update timed out."); + clearInterval(timer); + return; + } + try { + const r = await UpdateSvc.GetInstallerResult(); + if (r.success) { + setDone(true); + clearInterval(timer); + } else if (r.errorMsg) { + setError(r.errorMsg); + clearInterval(timer); + } + } catch { + // installer not finished yet + } + }, 2000); + + return () => { + cancelled = true; + clearInterval(timer); + }; + }, []); + + return ( +
    +
    + {done ? ( +

    Update complete

    + ) : error ? ( +

    {error}

    + ) : ( + <> + +

    Updating…

    +

    + Please don't close this window. +

    + + )} +
    +
    + ); +} diff --git a/client/ui-wails/frontend/src/vite-env.d.ts b/client/ui-wails/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/client/ui-wails/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/ui-wails/frontend/tailwind.config.ts b/client/ui-wails/frontend/tailwind.config.ts new file mode 100644 index 000000000..9dc4f1178 --- /dev/null +++ b/client/ui-wails/frontend/tailwind.config.ts @@ -0,0 +1,44 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./index.html", "./src/**/*.{ts,tsx}"], + darkMode: "class", + theme: { + extend: { + colors: { + netbird: { + DEFAULT: "#f68330", + 50: "#fff6ed", + 100: "#feecd6", + 200: "#ffd4a6", + 300: "#fab677", + 400: "#f68330", + 500: "#f46d1b", + 600: "#e55311", + 700: "#be3e10", + 800: "#973215", + 900: "#7a2b14", + }, + "nb-gray": { + DEFAULT: "#181A1D", + 50: "#f4f6f7", + 100: "#e4e7e9", + 200: "#cbd2d6", + 300: "#a3adb5", + 400: "#7c8994", + 500: "#616e79", + 600: "#535d67", + 700: "#474e57", + 800: "#3f444b", + 900: "#2e3238", + 925: "#1e2123", + 940: "#1c1e21", + 950: "#181a1d", + }, + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; + +export default config; diff --git a/client/ui-wails/frontend/tsconfig.json b/client/ui-wails/frontend/tsconfig.json new file mode 100644 index 000000000..ae81ea6d5 --- /dev/null +++ b/client/ui-wails/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "bindings"], +} diff --git a/client/ui-wails/frontend/vite.config.ts b/client/ui-wails/frontend/vite.config.ts new file mode 100644 index 000000000..f58d1fa4a --- /dev/null +++ b/client/ui-wails/frontend/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import wails from "@wailsio/runtime/plugins/vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), wails("./bindings")], + server: { + port: 9245, + strictPort: true, + }, +}); diff --git a/client/ui-wails/grpc.go b/client/ui-wails/grpc.go new file mode 100644 index 000000000..1ee3e5518 --- /dev/null +++ b/client/ui-wails/grpc.go @@ -0,0 +1,57 @@ +//go:build !android && !ios && !freebsd && !js + +package main + +import ( + "fmt" + "runtime" + "strings" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/netbirdio/netbird/client/proto" + "github.com/netbirdio/netbird/client/ui/desktop" +) + +// Conn is a lazy, lock-protected gRPC connection to the NetBird daemon. +// One Conn instance is shared by all services so they reuse the same channel. +type Conn struct { + addr string + + mu sync.Mutex + client proto.DaemonServiceClient +} + +func NewConn(addr string) *Conn { + return &Conn{addr: addr} +} + +func (c *Conn) Client() (proto.DaemonServiceClient, error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.client != nil { + return c.client, nil + } + + cc, err := grpc.NewClient( + strings.TrimPrefix(c.addr, "tcp://"), + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUserAgent(desktop.GetUIUserAgent()), + ) + if err != nil { + return nil, fmt.Errorf("dial daemon: %w", err) + } + c.client = proto.NewDaemonServiceClient(cc) + return c.client, nil +} + +// DaemonAddr returns the default daemon gRPC address for the current OS. +// Linux/macOS use a Unix socket; Windows uses TCP loopback. +func DaemonAddr() string { + if runtime.GOOS == "windows" { + return "tcp://127.0.0.1:41731" + } + return "unix:///var/run/netbird.sock" +} diff --git a/client/ui-wails/icons.go b/client/ui-wails/icons.go new file mode 100644 index 000000000..3f4ec97f2 --- /dev/null +++ b/client/ui-wails/icons.go @@ -0,0 +1,49 @@ +//go:build !android && !ios && !freebsd && !js + +package main + +import _ "embed" + +// Tray icons embedded from the legacy Fyne UI's asset set so the rewrite has +// something to render until Stage 3 produces SVG sources. Each pair is a +// light-mode PNG and its dark-mode variant; macOS template variants live +// alongside for menubar use. + +//go:embed assets/netbird-systemtray-connected.png +var iconConnected []byte + +//go:embed assets/netbird-systemtray-connected-dark.png +var iconConnectedDark []byte + +//go:embed assets/netbird-systemtray-disconnected.png +var iconDisconnected []byte + +//go:embed assets/netbird-systemtray-connecting.png +var iconConnecting []byte + +//go:embed assets/netbird-systemtray-error.png +var iconError []byte + +//go:embed assets/netbird-systemtray-update-connected.png +var iconUpdateConnected []byte + +//go:embed assets/netbird-systemtray-update-disconnected.png +var iconUpdateDisconnected []byte + +//go:embed assets/netbird-systemtray-connected-macos.png +var iconConnectedMacOS []byte + +//go:embed assets/netbird-systemtray-disconnected-macos.png +var iconDisconnectedMacOS []byte + +//go:embed assets/netbird-systemtray-connecting-macos.png +var iconConnectingMacOS []byte + +//go:embed assets/netbird-systemtray-error-macos.png +var iconErrorMacOS []byte + +//go:embed assets/netbird-systemtray-update-connected-macos.png +var iconUpdateConnectedMacOS []byte + +//go:embed assets/netbird-systemtray-update-disconnected-macos.png +var iconUpdateDisconnectedMacOS []byte diff --git a/client/ui-wails/icons_windows.go b/client/ui-wails/icons_windows.go new file mode 100644 index 000000000..1e0763770 --- /dev/null +++ b/client/ui-wails/icons_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package main + +import _ "embed" + +// Windows tray icons. Wails3 hands these to Shell_NotifyIcon via +// CreateIconFromResourceEx, which picks the frame matching SM_CXSMICON +// (16/32 px depending on DPI). A single high-res PNG forces the OS to +// downscale and the result is fuzzy at tray size — multi-frame .ico files +// avoid that by embedding 16/24/32/48 px raster frames in one resource. + +//go:embed assets/netbird-systemtray-connected.ico +var winIconConnected []byte + +//go:embed assets/netbird-systemtray-disconnected.ico +var winIconDisconnected []byte + +//go:embed assets/netbird-systemtray-connecting.ico +var winIconConnecting []byte + +//go:embed assets/netbird-systemtray-error.ico +var winIconError []byte + +//go:embed assets/netbird-systemtray-update-connected.ico +var winIconUpdateConnected []byte + +//go:embed assets/netbird-systemtray-update-disconnected.ico +var winIconUpdateDisconnected []byte diff --git a/client/ui-wails/main.go b/client/ui-wails/main.go new file mode 100644 index 000000000..480b3b21f --- /dev/null +++ b/client/ui-wails/main.go @@ -0,0 +1,101 @@ +//go:build !android && !ios && !freebsd && !js + +package main + +import ( + "context" + "embed" + "flag" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/services/notifications" + + "github.com/netbirdio/netbird/client/ui-wails/services" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func init() { + application.RegisterEvent[services.Status](services.EventStatus) + application.RegisterEvent[services.SystemEvent](services.EventSystem) + application.RegisterEvent[services.UpdateAvailable](services.EventUpdateAvailable) + application.RegisterEvent[services.UpdateProgress](services.EventUpdateProgress) +} + +func main() { + daemonAddr := flag.String("daemon-addr", DaemonAddr(), "Daemon gRPC address: unix:///path or tcp://host:port") + flag.Parse() + + conn := NewConn(*daemonAddr) + + // tray is captured in the SingleInstance callback below; the var is + // declared before app.New so the closure has a stable reference. + var tray *Tray + + app := application.New(application.Options{ + Name: "netbird-ui", + Description: "NetBird desktop client", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "io.netbird.ui", + OnSecondInstanceLaunch: func(_ application.SecondInstanceData) { + if tray != nil { + tray.ShowWindow() + } + }, + }, + }) + + connection := services.NewConnection(conn) + settings := services.NewSettings(conn) + profiles := services.NewProfiles(conn) + peers := services.NewPeers(conn, app.Event) + notifier := notifications.New() + + app.RegisterService(application.NewService(connection)) + app.RegisterService(application.NewService(settings)) + app.RegisterService(application.NewService(services.NewNetworks(conn))) + app.RegisterService(application.NewService(profiles)) + app.RegisterService(application.NewService(services.NewDebug(conn))) + app.RegisterService(application.NewService(services.NewUpdate(conn))) + app.RegisterService(application.NewService(peers)) + app.RegisterService(application.NewService(notifier)) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "NetBird", + Width: 960, + Height: 640, + Hidden: false, + BackgroundColour: application.NewRGB(24, 26, 29), + URL: "/", + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 38, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInset, + }, + }) + + // Intercept the window close to hide instead of quit. The user reaches + // "really quit" via tray -> Quit. + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + e.Cancel() + window.Hide() + }) + + tray = NewTray(app, window, connection, settings, profiles, peers, notifier) + listenForShowSignal(context.Background(), tray) + + peers.Watch(context.Background()) + + if err := app.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/client/ui-wails/services/conn.go b/client/ui-wails/services/conn.go new file mode 100644 index 000000000..531abe7d9 --- /dev/null +++ b/client/ui-wails/services/conn.go @@ -0,0 +1,13 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import "github.com/netbirdio/netbird/client/proto" + +// DaemonConn returns a lazy gRPC client to the NetBird daemon. +// All services receive a DaemonConn so they share a single connection. +type DaemonConn interface { + Client() (proto.DaemonServiceClient, error) +} + +func ptrStr(s string) *string { return &s } diff --git a/client/ui-wails/services/connection.go b/client/ui-wails/services/connection.go new file mode 100644 index 000000000..282bd04f7 --- /dev/null +++ b/client/ui-wails/services/connection.go @@ -0,0 +1,146 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + + "github.com/netbirdio/netbird/client/proto" +) + +// LoginParams carries the fields the UI sets when starting a login. +type LoginParams struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` + ManagementURL string `json:"managementUrl"` + SetupKey string `json:"setupKey"` + PreSharedKey string `json:"preSharedKey"` + Hostname string `json:"hostname"` + Hint string `json:"hint"` +} + +// LoginResult is the daemon's reply to a Login call. +type LoginResult struct { + NeedsSSOLogin bool `json:"needsSsoLogin"` + UserCode string `json:"userCode"` + VerificationURI string `json:"verificationUri"` + VerificationURIComplete string `json:"verificationUriComplete"` +} + +// WaitSSOParams carries the fields the UI passes to WaitSSOLogin. +type WaitSSOParams struct { + UserCode string `json:"userCode"` + Hostname string `json:"hostname"` +} + +// UpParams selects the profile the daemon should bring up. +type UpParams struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` +} + +// LogoutParams selects the profile the daemon should log out. +type LogoutParams struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` +} + +// Connection groups the daemon RPCs that drive login / connect / disconnect. +type Connection struct { + conn DaemonConn +} + +func NewConnection(conn DaemonConn) *Connection { + return &Connection{conn: conn} +} + +func (s *Connection) Login(ctx context.Context, p LoginParams) (LoginResult, error) { + cli, err := s.conn.Client() + if err != nil { + return LoginResult{}, err + } + req := &proto.LoginRequest{ + ManagementUrl: p.ManagementURL, + SetupKey: p.SetupKey, + Hostname: p.Hostname, + } + if p.ProfileName != "" { + req.ProfileName = ptrStr(p.ProfileName) + } + if p.Username != "" { + req.Username = ptrStr(p.Username) + } + if p.PreSharedKey != "" { + req.OptionalPreSharedKey = ptrStr(p.PreSharedKey) + } + if p.Hint != "" { + req.Hint = ptrStr(p.Hint) + } + + resp, err := cli.Login(ctx, req) + if err != nil { + return LoginResult{}, err + } + return LoginResult{ + NeedsSSOLogin: resp.GetNeedsSSOLogin(), + UserCode: resp.GetUserCode(), + VerificationURI: resp.GetVerificationURI(), + VerificationURIComplete: resp.GetVerificationURIComplete(), + }, nil +} + +func (s *Connection) WaitSSOLogin(ctx context.Context, p WaitSSOParams) (string, error) { + cli, err := s.conn.Client() + if err != nil { + return "", err + } + resp, err := cli.WaitSSOLogin(ctx, &proto.WaitSSOLoginRequest{ + UserCode: p.UserCode, + Hostname: p.Hostname, + }) + if err != nil { + return "", err + } + return resp.GetEmail(), nil +} + +func (s *Connection) Up(ctx context.Context, p UpParams) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + req := &proto.UpRequest{} + if p.ProfileName != "" { + req.ProfileName = ptrStr(p.ProfileName) + } + if p.Username != "" { + req.Username = ptrStr(p.Username) + } + _, err = cli.Up(ctx, req) + return err +} + +func (s *Connection) Down(ctx context.Context) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.Down(ctx, &proto.DownRequest{}) + return err +} + +func (s *Connection) Logout(ctx context.Context, p LogoutParams) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + req := &proto.LogoutRequest{} + if p.ProfileName != "" { + req.ProfileName = ptrStr(p.ProfileName) + } + if p.Username != "" { + req.Username = ptrStr(p.Username) + } + _, err = cli.Logout(ctx, req) + return err +} diff --git a/client/ui-wails/services/debug.go b/client/ui-wails/services/debug.go new file mode 100644 index 000000000..71ea6138a --- /dev/null +++ b/client/ui-wails/services/debug.go @@ -0,0 +1,88 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + + "github.com/netbirdio/netbird/client/proto" +) + +// DebugBundleParams configures what the daemon collects when generating a +// debug bundle. +type DebugBundleParams struct { + Anonymize bool `json:"anonymize"` + SystemInfo bool `json:"systemInfo"` + UploadURL string `json:"uploadUrl"` + LogFileCount uint32 `json:"logFileCount"` +} + +// DebugBundleResult mirrors DebugBundleResponse — Path is set on local-only +// bundles, UploadedKey on successful uploads, UploadFailureReason on failed +// uploads. +type DebugBundleResult struct { + Path string `json:"path"` + UploadedKey string `json:"uploadedKey"` + UploadFailureReason string `json:"uploadFailureReason"` +} + +// LogLevel is a single log-level value the daemon understands ("error", +// "warn", "info", "debug", "trace"). +type LogLevel struct { + Level string `json:"level"` +} + +// Debug groups debug / log-level / packet-trace RPCs. +type Debug struct { + conn DaemonConn +} + +func NewDebug(conn DaemonConn) *Debug { + return &Debug{conn: conn} +} + +func (s *Debug) Bundle(ctx context.Context, p DebugBundleParams) (DebugBundleResult, error) { + cli, err := s.conn.Client() + if err != nil { + return DebugBundleResult{}, err + } + resp, err := cli.DebugBundle(ctx, &proto.DebugBundleRequest{ + Anonymize: p.Anonymize, + SystemInfo: p.SystemInfo, + UploadURL: p.UploadURL, + LogFileCount: p.LogFileCount, + }) + if err != nil { + return DebugBundleResult{}, err + } + return DebugBundleResult{ + Path: resp.GetPath(), + UploadedKey: resp.GetUploadedKey(), + UploadFailureReason: resp.GetUploadFailureReason(), + }, nil +} + +func (s *Debug) GetLogLevel(ctx context.Context) (LogLevel, error) { + cli, err := s.conn.Client() + if err != nil { + return LogLevel{}, err + } + resp, err := cli.GetLogLevel(ctx, &proto.GetLogLevelRequest{}) + if err != nil { + return LogLevel{}, err + } + return LogLevel{Level: resp.GetLevel().String()}, nil +} + +func (s *Debug) SetLogLevel(ctx context.Context, lvl LogLevel) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + level, ok := proto.LogLevel_value[lvl.Level] + if !ok { + level = int32(proto.LogLevel_INFO) + } + _, err = cli.SetLogLevel(ctx, &proto.SetLogLevelRequest{Level: proto.LogLevel(level)}) + return err +} diff --git a/client/ui-wails/services/network.go b/client/ui-wails/services/network.go new file mode 100644 index 000000000..44257e120 --- /dev/null +++ b/client/ui-wails/services/network.go @@ -0,0 +1,92 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + + "github.com/netbirdio/netbird/client/proto" +) + +// Network is one routed network the daemon offers to the client. +type Network struct { + ID string `json:"id"` + Range string `json:"range"` + Selected bool `json:"selected"` + Domains []string `json:"domains"` + ResolvedIPs map[string][]string `json:"resolvedIps"` +} + +// SelectNetworksParams selects which networks to enable / disable. +// All means "every available network" (used by Select-All / Deselect-All buttons); +// Append means "leave the existing selection in place and merge these IDs in". +type SelectNetworksParams struct { + NetworkIDs []string `json:"networkIds"` + Append bool `json:"append"` + All bool `json:"all"` +} + +// Networks groups the daemon RPCs that read and toggle routed networks. +type Networks struct { + conn DaemonConn +} + +func NewNetworks(conn DaemonConn) *Networks { + return &Networks{conn: conn} +} + +func (s *Networks) List(ctx context.Context) ([]Network, error) { + cli, err := s.conn.Client() + if err != nil { + return nil, err + } + resp, err := cli.ListNetworks(ctx, &proto.ListNetworksRequest{}) + if err != nil { + return nil, err + } + out := make([]Network, 0, len(resp.GetRoutes())) + for _, n := range resp.GetRoutes() { + out = append(out, networkFromProto(n)) + } + return out, nil +} + +func (s *Networks) Select(ctx context.Context, p SelectNetworksParams) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.SelectNetworks(ctx, &proto.SelectNetworksRequest{ + NetworkIDs: p.NetworkIDs, + Append: p.Append, + All: p.All, + }) + return err +} + +func (s *Networks) Deselect(ctx context.Context, p SelectNetworksParams) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.DeselectNetworks(ctx, &proto.SelectNetworksRequest{ + NetworkIDs: p.NetworkIDs, + Append: p.Append, + All: p.All, + }) + return err +} + +func networkFromProto(n *proto.Network) Network { + resolved := make(map[string][]string, len(n.GetResolvedIPs())) + for k, v := range n.GetResolvedIPs() { + resolved[k] = append([]string{}, v.GetIps()...) + } + return Network{ + ID: n.GetID(), + Range: n.GetRange(), + Selected: n.GetSelected(), + Domains: append([]string{}, n.GetDomains()...), + ResolvedIPs: resolved, + } +} diff --git a/client/ui-wails/services/peers.go b/client/ui-wails/services/peers.go new file mode 100644 index 000000000..093371e39 --- /dev/null +++ b/client/ui-wails/services/peers.go @@ -0,0 +1,328 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/cenkalti/backoff/v4" + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/proto" +) + +// PollInterval is how often Watch falls back to Status polling when the +// SubscribeEvents stream is unavailable. Matches the Fyne UI's 2-second cadence. +const PollInterval = 2 * time.Second + +const ( + // EventStatus is emitted to the frontend whenever a fresh Status snapshot + // is captured (from a poll or a stream-driven refresh). + EventStatus = "netbird:status" + // EventSystem is emitted for each SubscribeEvents message (DNS, network, + // auth, connectivity categories). + EventSystem = "netbird:event" + // EventUpdateAvailable fires when the daemon detects a new version. The + // metadata's enforced flag is propagated as part of the payload. + EventUpdateAvailable = "netbird:update:available" + // EventUpdateProgress fires when the daemon is about to start (or has + // started) installing an update — Mode 2 enforced flow. The UI opens the + // progress window in response. + EventUpdateProgress = "netbird:update:progress" +) + +// Emitter is what peers.Watch needs from the host application: a simple +// "send this name and payload to the frontend" hook. The Wails app.Event +// satisfies this with its Emit method. +type Emitter interface { + Emit(name string, data ...any) bool +} + +// UpdateAvailable carries the new_version_available metadata. +type UpdateAvailable struct { + Version string `json:"version"` + Enforced bool `json:"enforced"` +} + +// UpdateProgress carries the progress_window metadata. +type UpdateProgress struct { + Action string `json:"action"` + Version string `json:"version"` +} + +// SystemEvent is the frontend-facing shape of a daemon SystemEvent. +type SystemEvent struct { + ID string `json:"id"` + Severity string `json:"severity"` + Category string `json:"category"` + Message string `json:"message"` + UserMessage string `json:"userMessage"` + Timestamp int64 `json:"timestamp"` + Metadata map[string]string `json:"metadata"` +} + +// PeerStatus is the frontend-facing shape of a daemon PeerState. Carries +// enough detail for the dashboard's compact peer row plus the on-click +// troubleshooting expansion (ICE candidate types, endpoints, handshake age). +type PeerStatus struct { + IP string `json:"ip"` + PubKey string `json:"pubKey"` + ConnStatus string `json:"connStatus"` + ConnStatusUpdateUnix int64 `json:"connStatusUpdateUnix"` + Relayed bool `json:"relayed"` + LocalIceCandidateType string `json:"localIceCandidateType"` + RemoteIceCandidateType string `json:"remoteIceCandidateType"` + LocalIceCandidateEndpoint string `json:"localIceCandidateEndpoint"` + RemoteIceCandidateEndpoint string `json:"remoteIceCandidateEndpoint"` + Fqdn string `json:"fqdn"` + BytesRx int64 `json:"bytesRx"` + BytesTx int64 `json:"bytesTx"` + LatencyMs int64 `json:"latencyMs"` + RelayAddress string `json:"relayAddress"` + LastHandshakeUnix int64 `json:"lastHandshakeUnix"` + RosenpassEnabled bool `json:"rosenpassEnabled"` + Networks []string `json:"networks"` +} + +// PeerLink is one of the named connections between this peer and its mgmt +// or signal server. +type PeerLink struct { + URL string `json:"url"` + Connected bool `json:"connected"` + Error string `json:"error,omitempty"` +} + +// LocalPeer mirrors LocalPeerState — what this client looks like on the mesh. +type LocalPeer struct { + IP string `json:"ip"` + PubKey string `json:"pubKey"` + Fqdn string `json:"fqdn"` + Networks []string `json:"networks"` +} + +// Status is the snapshot the frontend renders on the dashboard. +type Status struct { + Status string `json:"status"` + DaemonVersion string `json:"daemonVersion"` + Management PeerLink `json:"management"` + Signal PeerLink `json:"signal"` + Local LocalPeer `json:"local"` + Peers []PeerStatus `json:"peers"` + Events []SystemEvent `json:"events"` +} + +// Peers serves the dashboard data: one polled Status RPC and a long-running +// SubscribeEvents stream that re-emits every event over the Wails event bus. +type Peers struct { + conn DaemonConn + emitter Emitter + + mu sync.Mutex + cancel context.CancelFunc + streamWg sync.WaitGroup +} + +func NewPeers(conn DaemonConn, emitter Emitter) *Peers { + return &Peers{conn: conn, emitter: emitter} +} + +// Watch starts the background loop: a poll-then-stream pair that runs until +// ctx (or the service shutdown) cancels it. Safe to call once at boot. +func (s *Peers) Watch(ctx context.Context) { + s.mu.Lock() + if s.cancel != nil { + s.mu.Unlock() + return + } + ctx, cancel := context.WithCancel(ctx) + s.cancel = cancel + s.mu.Unlock() + + s.streamWg.Add(2) + go s.pollLoop(ctx) + go s.streamLoop(ctx) +} + +// ServiceShutdown is the Wails service hook fired on app exit. +func (s *Peers) ServiceShutdown() error { + s.mu.Lock() + cancel := s.cancel + s.cancel = nil + s.mu.Unlock() + if cancel != nil { + cancel() + } + s.streamWg.Wait() + return nil +} + +// Get returns the current daemon status snapshot. +func (s *Peers) Get(ctx context.Context) (Status, error) { + cli, err := s.conn.Client() + if err != nil { + return Status{}, err + } + resp, err := cli.Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true}) + if err != nil { + return Status{}, err + } + return statusFromProto(resp), nil +} + +func (s *Peers) pollLoop(ctx context.Context) { + defer s.streamWg.Done() + ticker := time.NewTicker(PollInterval) + defer ticker.Stop() + + for { + st, err := s.Get(ctx) + if err == nil { + s.emitter.Emit(EventStatus, st) + } else if ctx.Err() == nil { + log.Debugf("status poll: %v", err) + } + + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +func (s *Peers) streamLoop(ctx context.Context) { + defer s.streamWg.Done() + + bo := backoff.WithContext(&backoff.ExponentialBackOff{ + InitialInterval: time.Second, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: 10 * time.Second, + MaxElapsedTime: 0, + Stop: backoff.Stop, + Clock: backoff.SystemClock, + }, ctx) + + op := func() error { + cli, err := s.conn.Client() + if err != nil { + return fmt.Errorf("get client: %w", err) + } + stream, err := cli.SubscribeEvents(ctx, &proto.SubscribeRequest{}) + if err != nil { + return fmt.Errorf("subscribe: %w", err) + } + for { + ev, err := stream.Recv() + if err != nil { + if ctx.Err() != nil { + return ctx.Err() + } + return fmt.Errorf("stream recv: %w", err) + } + s.emitter.Emit(EventSystem, systemEventFromProto(ev)) + s.fanOutUpdateEvents(ev) + } + } + + if err := backoff.Retry(op, bo); err != nil && ctx.Err() == nil { + log.Errorf("event stream ended: %v", err) + } +} + +func statusFromProto(resp *proto.StatusResponse) Status { + full := resp.GetFullStatus() + mgmt := full.GetManagementState() + sig := full.GetSignalState() + local := full.GetLocalPeerState() + + st := Status{ + Status: resp.GetStatus(), + DaemonVersion: resp.GetDaemonVersion(), + Management: PeerLink{ + URL: mgmt.GetURL(), + Connected: mgmt.GetConnected(), + Error: mgmt.GetError(), + }, + Signal: PeerLink{ + URL: sig.GetURL(), + Connected: sig.GetConnected(), + Error: sig.GetError(), + }, + Local: LocalPeer{ + IP: local.GetIP(), + PubKey: local.GetPubKey(), + Fqdn: local.GetFqdn(), + Networks: append([]string{}, local.GetNetworks()...), + }, + } + + for _, p := range full.GetPeers() { + st.Peers = append(st.Peers, PeerStatus{ + IP: p.GetIP(), + PubKey: p.GetPubKey(), + ConnStatus: p.GetConnStatus(), + ConnStatusUpdateUnix: p.GetConnStatusUpdate().GetSeconds(), + Relayed: p.GetRelayed(), + LocalIceCandidateType: p.GetLocalIceCandidateType(), + RemoteIceCandidateType: p.GetRemoteIceCandidateType(), + LocalIceCandidateEndpoint: p.GetLocalIceCandidateEndpoint(), + RemoteIceCandidateEndpoint: p.GetRemoteIceCandidateEndpoint(), + Fqdn: p.GetFqdn(), + BytesRx: p.GetBytesRx(), + BytesTx: p.GetBytesTx(), + LatencyMs: p.GetLatency().AsDuration().Milliseconds(), + RelayAddress: p.GetRelayAddress(), + LastHandshakeUnix: p.GetLastWireguardHandshake().GetSeconds(), + RosenpassEnabled: p.GetRosenpassEnabled(), + Networks: append([]string{}, p.GetNetworks()...), + }) + } + for _, e := range full.GetEvents() { + st.Events = append(st.Events, systemEventFromProto(e)) + } + return st +} + +// fanOutUpdateEvents inspects the daemon SystemEvent for update-related +// metadata keys and re-emits them as dedicated Wails events. This lets the +// tray and React update window listen for a single, narrow event instead of +// re-checking metadata on every system event they receive. +func (s *Peers) fanOutUpdateEvents(ev *proto.SystemEvent) { + md := ev.GetMetadata() + if md == nil { + return + } + if v, ok := md["new_version_available"]; ok { + _, enforced := md["enforced"] + s.emitter.Emit(EventUpdateAvailable, UpdateAvailable{Version: v, Enforced: enforced}) + } + if action, ok := md["progress_window"]; ok { + s.emitter.Emit(EventUpdateProgress, UpdateProgress{ + Action: action, + Version: md["version"], + }) + } +} + +func systemEventFromProto(e *proto.SystemEvent) SystemEvent { + out := SystemEvent{ + ID: e.GetId(), + Severity: strings.ToLower(strings.TrimPrefix(e.GetSeverity().String(), "SystemEvent_")), + Category: strings.ToLower(strings.TrimPrefix(e.GetCategory().String(), "SystemEvent_")), + Message: e.GetMessage(), + UserMessage: e.GetUserMessage(), + Metadata: map[string]string{}, + } + if ts := e.GetTimestamp(); ts != nil { + out.Timestamp = ts.GetSeconds() + } + for k, v := range e.GetMetadata() { + out.Metadata[k] = v + } + return out +} diff --git a/client/ui-wails/services/profile.go b/client/ui-wails/services/profile.go new file mode 100644 index 000000000..7efcf46bc --- /dev/null +++ b/client/ui-wails/services/profile.go @@ -0,0 +1,118 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + "os/user" + + "github.com/netbirdio/netbird/client/proto" +) + +// Profile is one named daemon profile. +type Profile struct { + Name string `json:"name"` + IsActive bool `json:"isActive"` +} + +// ProfileRef identifies a profile by name+username. +type ProfileRef struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` +} + +// ActiveProfile is the result of GetActiveProfile. +type ActiveProfile struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` +} + +// Profiles groups the daemon RPCs that manage named profiles. +type Profiles struct { + conn DaemonConn +} + +func NewProfiles(conn DaemonConn) *Profiles { + return &Profiles{conn: conn} +} + +// Username returns the OS username the daemon expects for profile lookups. +// The frontend calls this once at boot and reuses the result. +func (s *Profiles) Username() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + return u.Username, nil +} + +func (s *Profiles) List(ctx context.Context, username string) ([]Profile, error) { + cli, err := s.conn.Client() + if err != nil { + return nil, err + } + resp, err := cli.ListProfiles(ctx, &proto.ListProfilesRequest{Username: username}) + if err != nil { + return nil, err + } + out := make([]Profile, 0, len(resp.GetProfiles())) + for _, p := range resp.GetProfiles() { + out = append(out, Profile{Name: p.GetName(), IsActive: p.GetIsActive()}) + } + return out, nil +} + +func (s *Profiles) GetActive(ctx context.Context) (ActiveProfile, error) { + cli, err := s.conn.Client() + if err != nil { + return ActiveProfile{}, err + } + resp, err := cli.GetActiveProfile(ctx, &proto.GetActiveProfileRequest{}) + if err != nil { + return ActiveProfile{}, err + } + return ActiveProfile{ + ProfileName: resp.GetProfileName(), + Username: resp.GetUsername(), + }, nil +} + +func (s *Profiles) Switch(ctx context.Context, p ProfileRef) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + req := &proto.SwitchProfileRequest{} + if p.ProfileName != "" { + req.ProfileName = ptrStr(p.ProfileName) + } + if p.Username != "" { + req.Username = ptrStr(p.Username) + } + _, err = cli.SwitchProfile(ctx, req) + return err +} + +func (s *Profiles) Add(ctx context.Context, p ProfileRef) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.AddProfile(ctx, &proto.AddProfileRequest{ + ProfileName: p.ProfileName, + Username: p.Username, + }) + return err +} + +func (s *Profiles) Remove(ctx context.Context, p ProfileRef) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.RemoveProfile(ctx, &proto.RemoveProfileRequest{ + ProfileName: p.ProfileName, + Username: p.Username, + }) + return err +} diff --git a/client/ui-wails/services/settings.go b/client/ui-wails/services/settings.go new file mode 100644 index 000000000..c5d24232a --- /dev/null +++ b/client/ui-wails/services/settings.go @@ -0,0 +1,192 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + + "github.com/netbirdio/netbird/client/proto" +) + +// ConfigParams selects which profile/user to read or write config for. +type ConfigParams struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` +} + +// Config is the daemon configuration the UI exposes in the settings window. +// Pointer fields mark "set" vs "unset" so the UI can omit a value to keep the +// daemon's current setting (matching SetConfigRequest's optional semantics). +type Config struct { + ManagementURL string `json:"managementUrl"` + AdminURL string `json:"adminUrl"` + ConfigFile string `json:"configFile"` + LogFile string `json:"logFile"` + PreSharedKey string `json:"preSharedKey"` + InterfaceName string `json:"interfaceName"` + WireguardPort int64 `json:"wireguardPort"` + MTU int64 `json:"mtu"` + DisableAutoConnect bool `json:"disableAutoConnect"` + ServerSSHAllowed bool `json:"serverSshAllowed"` + RosenpassEnabled bool `json:"rosenpassEnabled"` + RosenpassPermissive bool `json:"rosenpassPermissive"` + DisableNotifications bool `json:"disableNotifications"` + LazyConnectionEnabled bool `json:"lazyConnectionEnabled"` + BlockInbound bool `json:"blockInbound"` + NetworkMonitor bool `json:"networkMonitor"` + DisableClientRoutes bool `json:"disableClientRoutes"` + DisableServerRoutes bool `json:"disableServerRoutes"` + DisableDNS bool `json:"disableDns"` + BlockLANAccess bool `json:"blockLanAccess"` + EnableSSHRoot bool `json:"enableSshRoot"` + EnableSSHSFTP bool `json:"enableSshSftp"` + EnableSSHLocalPortForwarding bool `json:"enableSshLocalPortForwarding"` + EnableSSHRemotePortForwarding bool `json:"enableSshRemotePortForwarding"` + DisableSSHAuth bool `json:"disableSshAuth"` + SSHJWTCacheTTL int32 `json:"sshJwtCacheTtl"` +} + +// SetConfigParams is a partial update — only fields with non-nil pointers +// are sent to the daemon. The frontend uses this to flip individual toggles. +type SetConfigParams struct { + ProfileName string `json:"profileName"` + Username string `json:"username"` + ManagementURL string `json:"managementUrl"` + AdminURL string `json:"adminUrl"` + InterfaceName *string `json:"interfaceName,omitempty"` + WireguardPort *int64 `json:"wireguardPort,omitempty"` + MTU *int64 `json:"mtu,omitempty"` + PreSharedKey *string `json:"preSharedKey,omitempty"` + DisableAutoConnect *bool `json:"disableAutoConnect,omitempty"` + ServerSSHAllowed *bool `json:"serverSshAllowed,omitempty"` + RosenpassEnabled *bool `json:"rosenpassEnabled,omitempty"` + RosenpassPermissive *bool `json:"rosenpassPermissive,omitempty"` + DisableNotifications *bool `json:"disableNotifications,omitempty"` + LazyConnectionEnabled *bool `json:"lazyConnectionEnabled,omitempty"` + BlockInbound *bool `json:"blockInbound,omitempty"` + NetworkMonitor *bool `json:"networkMonitor,omitempty"` + DisableClientRoutes *bool `json:"disableClientRoutes,omitempty"` + DisableServerRoutes *bool `json:"disableServerRoutes,omitempty"` + DisableDNS *bool `json:"disableDns,omitempty"` + DisableFirewall *bool `json:"disableFirewall,omitempty"` + BlockLANAccess *bool `json:"blockLanAccess,omitempty"` + EnableSSHRoot *bool `json:"enableSshRoot,omitempty"` + EnableSSHSFTP *bool `json:"enableSshSftp,omitempty"` + EnableSSHLocalPortForwarding *bool `json:"enableSshLocalPortForwarding,omitempty"` + EnableSSHRemotePortForwarding *bool `json:"enableSshRemotePortForwarding,omitempty"` + DisableSSHAuth *bool `json:"disableSshAuth,omitempty"` + SSHJWTCacheTTL *int32 `json:"sshJwtCacheTtl,omitempty"` +} + +// Features reports which UI surfaces the daemon has disabled. The Fyne UI uses +// these flags to grey out menu items the operator turned off server-side. +type Features struct { + DisableProfiles bool `json:"disableProfiles"` + DisableUpdateSettings bool `json:"disableUpdateSettings"` + DisableNetworks bool `json:"disableNetworks"` +} + +// Settings groups the daemon RPCs that read and write the daemon config. +type Settings struct { + conn DaemonConn +} + +func NewSettings(conn DaemonConn) *Settings { + return &Settings{conn: conn} +} + +func (s *Settings) GetConfig(ctx context.Context, p ConfigParams) (Config, error) { + cli, err := s.conn.Client() + if err != nil { + return Config{}, err + } + resp, err := cli.GetConfig(ctx, &proto.GetConfigRequest{ + ProfileName: p.ProfileName, + Username: p.Username, + }) + if err != nil { + return Config{}, err + } + return Config{ + ManagementURL: resp.GetManagementUrl(), + AdminURL: resp.GetAdminURL(), + ConfigFile: resp.GetConfigFile(), + LogFile: resp.GetLogFile(), + PreSharedKey: resp.GetPreSharedKey(), + InterfaceName: resp.GetInterfaceName(), + WireguardPort: resp.GetWireguardPort(), + MTU: resp.GetMtu(), + DisableAutoConnect: resp.GetDisableAutoConnect(), + ServerSSHAllowed: resp.GetServerSSHAllowed(), + RosenpassEnabled: resp.GetRosenpassEnabled(), + RosenpassPermissive: resp.GetRosenpassPermissive(), + DisableNotifications: resp.GetDisableNotifications(), + LazyConnectionEnabled: resp.GetLazyConnectionEnabled(), + BlockInbound: resp.GetBlockInbound(), + NetworkMonitor: resp.GetNetworkMonitor(), + DisableClientRoutes: resp.GetDisableClientRoutes(), + DisableServerRoutes: resp.GetDisableServerRoutes(), + DisableDNS: resp.GetDisableDns(), + BlockLANAccess: resp.GetBlockLanAccess(), + EnableSSHRoot: resp.GetEnableSSHRoot(), + EnableSSHSFTP: resp.GetEnableSSHSFTP(), + EnableSSHLocalPortForwarding: resp.GetEnableSSHLocalPortForwarding(), + EnableSSHRemotePortForwarding: resp.GetEnableSSHRemotePortForwarding(), + DisableSSHAuth: resp.GetDisableSSHAuth(), + SSHJWTCacheTTL: resp.GetSshJWTCacheTTL(), + }, nil +} + +func (s *Settings) SetConfig(ctx context.Context, p SetConfigParams) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + req := &proto.SetConfigRequest{ + ProfileName: p.ProfileName, + Username: p.Username, + ManagementUrl: p.ManagementURL, + AdminURL: p.AdminURL, + InterfaceName: p.InterfaceName, + WireguardPort: p.WireguardPort, + Mtu: p.MTU, + OptionalPreSharedKey: p.PreSharedKey, + DisableAutoConnect: p.DisableAutoConnect, + ServerSSHAllowed: p.ServerSSHAllowed, + RosenpassEnabled: p.RosenpassEnabled, + RosenpassPermissive: p.RosenpassPermissive, + DisableNotifications: p.DisableNotifications, + LazyConnectionEnabled: p.LazyConnectionEnabled, + BlockInbound: p.BlockInbound, + NetworkMonitor: p.NetworkMonitor, + DisableClientRoutes: p.DisableClientRoutes, + DisableServerRoutes: p.DisableServerRoutes, + DisableDns: p.DisableDNS, + DisableFirewall: p.DisableFirewall, + BlockLanAccess: p.BlockLANAccess, + EnableSSHRoot: p.EnableSSHRoot, + EnableSSHSFTP: p.EnableSSHSFTP, + EnableSSHLocalPortForwarding: p.EnableSSHLocalPortForwarding, + EnableSSHRemotePortForwarding: p.EnableSSHRemotePortForwarding, + DisableSSHAuth: p.DisableSSHAuth, + SshJWTCacheTTL: p.SSHJWTCacheTTL, + } + _, err = cli.SetConfig(ctx, req) + return err +} + +func (s *Settings) GetFeatures(ctx context.Context) (Features, error) { + cli, err := s.conn.Client() + if err != nil { + return Features{}, err + } + resp, err := cli.GetFeatures(ctx, &proto.GetFeaturesRequest{}) + if err != nil { + return Features{}, err + } + return Features{ + DisableProfiles: resp.GetDisableProfiles(), + DisableUpdateSettings: resp.GetDisableUpdateSettings(), + DisableNetworks: resp.GetDisableNetworks(), + }, nil +} diff --git a/client/ui-wails/services/update.go b/client/ui-wails/services/update.go new file mode 100644 index 000000000..e7f9ad9c9 --- /dev/null +++ b/client/ui-wails/services/update.go @@ -0,0 +1,55 @@ +//go:build !android && !ios && !freebsd && !js + +package services + +import ( + "context" + + "github.com/netbirdio/netbird/client/proto" +) + +// UpdateResult mirrors TriggerUpdateResponse: Success false carries an error +// message in ErrorMsg. +type UpdateResult struct { + Success bool `json:"success"` + ErrorMsg string `json:"errorMsg"` +} + +// Update groups the RPCs that drive the enforced-update install flow. +type Update struct { + conn DaemonConn +} + +func NewUpdate(conn DaemonConn) *Update { + return &Update{conn: conn} +} + +func (s *Update) Trigger(ctx context.Context) (UpdateResult, error) { + cli, err := s.conn.Client() + if err != nil { + return UpdateResult{}, err + } + resp, err := cli.TriggerUpdate(ctx, &proto.TriggerUpdateRequest{}) + if err != nil { + return UpdateResult{}, err + } + return UpdateResult{ + Success: resp.GetSuccess(), + ErrorMsg: resp.GetErrorMsg(), + }, nil +} + +func (s *Update) GetInstallerResult(ctx context.Context) (UpdateResult, error) { + cli, err := s.conn.Client() + if err != nil { + return UpdateResult{}, err + } + resp, err := cli.GetInstallerResult(ctx, &proto.InstallerResultRequest{}) + if err != nil { + return UpdateResult{}, err + } + return UpdateResult{ + Success: resp.GetSuccess(), + ErrorMsg: resp.GetErrorMsg(), + }, nil +} diff --git a/client/ui-wails/signal_unix.go b/client/ui-wails/signal_unix.go new file mode 100644 index 000000000..a5a9205c0 --- /dev/null +++ b/client/ui-wails/signal_unix.go @@ -0,0 +1,33 @@ +//go:build !windows && !android && !ios && !freebsd && !js + +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + log "github.com/sirupsen/logrus" +) + +// listenForShowSignal opens the main window when the process receives SIGUSR1. +// External tools (the daemon, the installer, or another `netbird-ui` invocation) +// can poke this channel by signalling the running pid. +func listenForShowSignal(ctx context.Context, tray *Tray) { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGUSR1) + + go func() { + for { + select { + case <-ctx.Done(): + signal.Stop(sigCh) + return + case <-sigCh: + log.Debug("SIGUSR1 received, showing window") + tray.ShowWindow() + } + } + }() +} diff --git a/client/ui-wails/signal_windows.go b/client/ui-wails/signal_windows.go new file mode 100644 index 000000000..22f1623cf --- /dev/null +++ b/client/ui-wails/signal_windows.go @@ -0,0 +1,81 @@ +//go:build windows + +package main + +import ( + "context" + "errors" + "time" + + log "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +const ( + quickActionsTriggerEventName = `Global\NetBirdQuickActionsTriggerEvent` + waitTimeout = 5 * time.Second + desiredAccesses = windows.SYNCHRONIZE | windows.EVENT_MODIFY_STATE + + // WaitForSingleObject returns this when the timeout elapses without the + // object being signalled. golang.org/x/sys/windows does not expose it. + waitTimeoutCode uint32 = 0x00000102 +) + +// listenForShowSignal opens the main window when an external process pulses +// the named event Global\NetBirdQuickActionsTriggerEvent. Mirrors the trigger +// the legacy Fyne UI used so the installer and CLI integrations keep working. +func listenForShowSignal(ctx context.Context, tray *Tray) { + namePtr, err := windows.UTF16PtrFromString(quickActionsTriggerEventName) + if err != nil { + log.Errorf("trigger event name: %v", err) + return + } + + handle, err := windows.CreateEvent(nil, 1, 0, namePtr) + if err != nil { + if !errors.Is(err, windows.ERROR_ALREADY_EXISTS) { + log.Errorf("create trigger event %q: %v", quickActionsTriggerEventName, err) + return + } + handle, err = windows.OpenEvent(desiredAccesses, false, namePtr) + if err != nil { + log.Errorf("open trigger event %q: %v", quickActionsTriggerEventName, err) + return + } + } + + if handle == windows.InvalidHandle { + log.Errorf("invalid handle for trigger event %q", quickActionsTriggerEventName) + return + } + + go waitForTrigger(ctx, handle, tray) +} + +func waitForTrigger(ctx context.Context, handle windows.Handle, tray *Tray) { + defer func() { + if err := windows.CloseHandle(handle); err != nil { + log.Errorf("close trigger event handle: %v", err) + } + }() + + timeoutMs := uint32(waitTimeout / time.Millisecond) + for { + if ctx.Err() != nil { + return + } + ev, err := windows.WaitForSingleObject(handle, timeoutMs) + switch { + case err != nil: + log.Errorf("wait trigger event: %v", err) + return + case ev == waitTimeoutCode: + continue + case ev == windows.WAIT_OBJECT_0: + if err := windows.ResetEvent(handle); err != nil { + log.Errorf("reset trigger event: %v", err) + } + tray.ShowWindow() + } + } +} diff --git a/client/ui-wails/tray.go b/client/ui-wails/tray.go new file mode 100644 index 000000000..91a3625e6 --- /dev/null +++ b/client/ui-wails/tray.go @@ -0,0 +1,565 @@ +//go:build !android && !ios && !freebsd && !js + +package main + +import ( + "context" + "fmt" + "runtime" + "sort" + "strings" + "sync" + + log "github.com/sirupsen/logrus" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/notifications" + + "github.com/netbirdio/netbird/client/ui-wails/services" +) + +// Tray builds and updates the systray menu. It mirrors the layout of the Fyne +// systray 1:1 and routes clicks back to the gRPC services. Dynamic state +// (status icon, exit-node submenu) is driven by the netbird:status event. +type Tray struct { + app *application.App + tray *application.SystemTray + window *application.WebviewWindow + connection *services.Connection + settings *services.Settings + profiles *services.Profiles + peers *services.Peers + notifier *notifications.NotificationService + + statusItem *application.MenuItem + upItem *application.MenuItem + downItem *application.MenuItem + exitNodeItem *application.MenuItem + networksItem *application.MenuItem + allowSSHItem *application.MenuItem + autoConnItem *application.MenuItem + rosenpassItem *application.MenuItem + lazyConnItem *application.MenuItem + blockInItem *application.MenuItem + notifyItem *application.MenuItem + + mu sync.Mutex + connected bool + hasUpdate bool + exitNodes []string + lastStatus string + notificationsEnabled bool + activeProfile string + activeUsername string +} + +func NewTray( + app *application.App, + window *application.WebviewWindow, + connection *services.Connection, + settings *services.Settings, + profiles *services.Profiles, + peers *services.Peers, + notifier *notifications.NotificationService, +) *Tray { + t := &Tray{ + app: app, + window: window, + connection: connection, + settings: settings, + profiles: profiles, + peers: peers, + notifier: notifier, + notificationsEnabled: true, + } + t.tray = app.SystemTray.New() + t.applyIcon() + t.tray.SetTooltip("NetBird") + t.tray.SetMenu(t.buildMenu()) + t.tray.AttachWindow(window) + t.tray.OnClick(func() { t.toggleWindow() }) + + app.Event.On(services.EventStatus, t.onStatusEvent) + app.Event.On(services.EventSystem, t.onSystemEvent) + app.Event.On(services.EventUpdateAvailable, t.onUpdateAvailable) + app.Event.On(services.EventUpdateProgress, t.onUpdateProgress) + + go t.loadConfig() + return t +} + +// ShowWindow brings the main window forward — used by SIGUSR1 / Windows event. +func (t *Tray) ShowWindow() { + if t.window == nil { + return + } + t.window.Show() +} + +func (t *Tray) buildMenu() *application.Menu { + menu := application.NewMenu() + + t.statusItem = menu.Add("Disconnected").SetEnabled(false) + + menu.AddSeparator() + t.upItem = menu.Add("Connect").OnClick(func(*application.Context) { t.handleConnect() }) + t.downItem = menu.Add("Disconnect").OnClick(func(*application.Context) { t.handleDisconnect() }) + t.downItem.SetEnabled(false) + + menu.AddSeparator() + + settingsSub := menu.AddSubmenu("Settings") + t.allowSSHItem = settingsSub.AddCheckbox("Allow SSH", false).OnClick(func(*application.Context) { + t.flipFlag("ssh", t.allowSSHItem.Checked()) + }) + t.autoConnItem = settingsSub.AddCheckbox("Connect on Startup", false).OnClick(func(*application.Context) { + t.flipFlag("auto", t.autoConnItem.Checked()) + }) + t.rosenpassItem = settingsSub.AddCheckbox("Enable Quantum-Resistance", false).OnClick(func(*application.Context) { + t.flipFlag("rosenpass", t.rosenpassItem.Checked()) + }) + t.lazyConnItem = settingsSub.AddCheckbox("Enable Lazy Connections", false).OnClick(func(*application.Context) { + t.flipFlag("lazy", t.lazyConnItem.Checked()) + }) + t.blockInItem = settingsSub.AddCheckbox("Block Inbound Connections", false).OnClick(func(*application.Context) { + t.flipFlag("blockin", t.blockInItem.Checked()) + }) + t.notifyItem = settingsSub.AddCheckbox("Notifications", true).OnClick(func(*application.Context) { + t.flipFlag("notify", t.notifyItem.Checked()) + }) + settingsSub.AddSeparator() + settingsSub.Add("Advanced Settings").OnClick(func(*application.Context) { t.openRoute("/settings") }) + settingsSub.Add("Create Debug Bundle").OnClick(func(*application.Context) { t.openRoute("/debug") }) + + t.exitNodeItem = menu.Add("Exit Node").SetEnabled(false) + + t.networksItem = menu.Add("Networks").OnClick(func(*application.Context) { t.openRoute("/networks") }) + + menu.AddSeparator() + + about := menu.AddSubmenu("About") + about.Add("GitHub").OnClick(func(*application.Context) { + _ = t.app.Browser.OpenURL("https://github.com/netbirdio/netbird") + }) + about.Add("Documentation").SetEnabled(false) + + menu.AddSeparator() + menu.Add("Quit").OnClick(func(*application.Context) { t.app.Quit() }) + + return menu +} + +func (t *Tray) toggleWindow() { + if t.window == nil { + return + } + if t.window.IsVisible() { + t.window.Hide() + return + } + t.window.Show() +} + +func (t *Tray) openRoute(route string) { + if t.window == nil { + return + } + t.window.Show() + t.window.SetURL("/#" + route) +} + +func (t *Tray) handleConnect() { + t.upItem.SetEnabled(false) + go func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := t.connection.Up(ctx, services.UpParams{}); err != nil { + log.Errorf("connect: %v", err) + t.notifyError("Failed to connect") + t.upItem.SetEnabled(true) + } + }() +} + +func (t *Tray) handleDisconnect() { + t.downItem.SetEnabled(false) + go func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := t.connection.Down(ctx); err != nil { + log.Errorf("disconnect: %v", err) + t.notifyError("Failed to disconnect") + t.downItem.SetEnabled(true) + } + }() +} + +// flipFlag pushes a partial SetConfig for one tray-toggled boolean. On +// failure the tray checkbox is reverted to keep it in sync with the daemon +// and an error notification is fired so the user knows the change didn't +// stick. The "notify" flag also updates the in-process gate that decides +// whether daemon SystemEvents become OS notifications. +func (t *Tray) flipFlag(name string, checked bool) { + go func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + t.mu.Lock() + profile, username := t.activeProfile, t.activeUsername + t.mu.Unlock() + + req := services.SetConfigParams{ProfileName: profile, Username: username} + var ( + label string + item *application.MenuItem + ) + switch name { + case "ssh": + req.ServerSSHAllowed = ptrBool(checked) + label, item = "SSH", t.allowSSHItem + case "auto": + // "Connect on Startup" is the inverse of disableAutoConnect. + req.DisableAutoConnect = ptrBool(!checked) + label, item = "auto-connect", t.autoConnItem + case "rosenpass": + req.RosenpassEnabled = ptrBool(checked) + label, item = "Rosenpass", t.rosenpassItem + case "lazy": + req.LazyConnectionEnabled = ptrBool(checked) + label, item = "lazy connection", t.lazyConnItem + case "blockin": + req.BlockInbound = ptrBool(checked) + label, item = "block inbound", t.blockInItem + case "notify": + req.DisableNotifications = ptrBool(!checked) + label, item = "notifications", t.notifyItem + default: + log.Debugf("tray flipFlag: unknown flag %q", name) + return + } + + if err := t.settings.SetConfig(ctx, req); err != nil { + log.Errorf("set %s: %v", label, err) + t.notifyError("Failed to update " + label + " settings") + if item != nil { + item.SetChecked(!checked) // revert + } + return + } + + if name == "notify" { + t.mu.Lock() + t.notificationsEnabled = checked + t.mu.Unlock() + } + }() +} + +func ptrBool(b bool) *bool { return &b } + +func (t *Tray) onStatusEvent(ev *application.CustomEvent) { + st, ok := ev.Data.(services.Status) + if !ok { + return + } + t.applyStatus(st) +} + +// onSystemEvent fires an OS notification for daemon SystemEvents that carry +// a user-facing message, mirroring the legacy event.Manager behaviour: gated +// by the user's "Notifications" toggle, with CRITICAL events bypassing the +// gate. The narrowly-scoped EventUpdate* events are skipped here because +// onUpdateAvailable already produces a richer notification for them. +func (t *Tray) onSystemEvent(ev *application.CustomEvent) { + se, ok := ev.Data.(services.SystemEvent) + if !ok || se.UserMessage == "" { + return + } + if _, isUpdate := se.Metadata["new_version_available"]; isUpdate { + return + } + + critical := se.Severity == "critical" + t.mu.Lock() + enabled := t.notificationsEnabled + t.mu.Unlock() + if !enabled && !critical { + return + } + + body := se.UserMessage + if id := se.Metadata["id"]; id != "" { + body += fmt.Sprintf(" ID: %s", id) + } + t.notify(eventTitle(se), body, "netbird-event-"+se.ID) +} + +// onUpdateAvailable runs when the daemon reports a new netbird version. It +// flips the tray's hasUpdate flag (icon swap) and posts an OS notification. +// The notification is what the legacy Fyne UI used to alert the user. +func (t *Tray) onUpdateAvailable(ev *application.CustomEvent) { + upd, ok := ev.Data.(services.UpdateAvailable) + if !ok { + return + } + t.mu.Lock() + t.hasUpdate = true + t.mu.Unlock() + t.applyIcon() + + body := fmt.Sprintf("NetBird %s is available.", upd.Version) + if upd.Enforced { + body += " Your administrator requires this update." + } + if err := t.notifier.SendNotification(notifications.NotificationOptions{ + ID: "netbird-update-" + upd.Version, + Title: "NetBird update available", + Body: body, + }); err != nil { + log.Debugf("send update notification: %v", err) + } +} + +// onUpdateProgress runs when the daemon enters the install phase of an +// enforced update. The Fyne UI used to spawn a separate process with the +// update window; here the window is already in-process, so we just route to +// the /update page and bring it forward. +func (t *Tray) onUpdateProgress(ev *application.CustomEvent) { + prog, ok := ev.Data.(services.UpdateProgress) + if !ok || prog.Action != "show" { + return + } + if t.window == nil { + return + } + url := "/#/update" + if prog.Version != "" { + url += "?version=" + prog.Version + } + t.window.SetURL(url) + t.window.Show() +} + +// applyStatus updates the tray icon, status label, exit-node submenu, and +// connect/disconnect enablement based on the latest daemon snapshot. +func (t *Tray) applyStatus(st services.Status) { + t.mu.Lock() + connected := strings.EqualFold(st.Status, "Connected") + t.connected = connected + t.lastStatus = st.Status + + exitNodes := exitNodesFromStatus(st) + exitNodesChanged := !equalStrings(exitNodes, t.exitNodes) + t.exitNodes = exitNodes + t.mu.Unlock() + + t.applyIcon() + if t.statusItem != nil { + t.statusItem.SetLabel(st.Status) + } + if t.upItem != nil { + t.upItem.SetEnabled(!connected) + } + if t.downItem != nil { + t.downItem.SetEnabled(connected) + } + if exitNodesChanged { + t.rebuildExitNodes(exitNodes) + } +} + +func (t *Tray) rebuildExitNodes(nodes []string) { + if t.exitNodeItem == nil { + return + } + if len(nodes) == 0 { + t.exitNodeItem.SetEnabled(false) + return + } + sub := application.NewMenu() + for _, fqdn := range nodes { + sub.AddCheckbox(fqdn, false) + } + t.exitNodeItem.SetEnabled(true) +} + +func (t *Tray) applyIcon() { + if runtime.GOOS == "windows" { + t.mu.Lock() + ico := trayIcon(t.connected, t.hasUpdate, t.lastStatus) + t.mu.Unlock() + if ico != nil { + t.tray.SetIcon(ico) + } + return + } + + icon, dark := t.iconForState() + if runtime.GOOS == "darwin" { + t.tray.SetTemplateIcon(icon) + return + } + t.tray.SetIcon(icon) + if dark != nil { + t.tray.SetDarkModeIcon(dark) + } +} + +func (t *Tray) iconForState() (icon, dark []byte) { + t.mu.Lock() + connected := t.connected + hasUpdate := t.hasUpdate + statusLabel := t.lastStatus + t.mu.Unlock() + + connecting := strings.EqualFold(statusLabel, "Connecting") + errored := strings.EqualFold(statusLabel, "Error") + + if runtime.GOOS == "darwin" { + switch { + case connecting: + return iconConnectingMacOS, nil + case errored: + return iconErrorMacOS, nil + case connected && hasUpdate: + return iconUpdateConnectedMacOS, nil + case connected: + return iconConnectedMacOS, nil + case hasUpdate: + return iconUpdateDisconnectedMacOS, nil + default: + return iconDisconnectedMacOS, nil + } + } + + switch { + case connecting: + return iconConnecting, nil + case errored: + return iconError, nil + case connected && hasUpdate: + return iconUpdateConnected, nil + case connected: + return iconConnected, iconConnectedDark + case hasUpdate: + return iconUpdateDisconnected, nil + default: + return iconDisconnected, nil + } +} + +// loadConfig syncs the tray-submenu checkboxes with the daemon's stored +// config and seeds the notifications gate. Called once at startup from a +// goroutine so a slow or unreachable daemon does not block menu construction. +func (t *Tray) loadConfig() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + active, err := t.profiles.GetActive(ctx) + if err != nil { + log.Debugf("get active profile: %v", err) + return + } + cfg, err := t.settings.GetConfig(ctx, services.ConfigParams(active)) + if err != nil { + log.Debugf("get config: %v", err) + return + } + + t.mu.Lock() + t.activeProfile = active.ProfileName + t.activeUsername = active.Username + t.notificationsEnabled = !cfg.DisableNotifications + t.mu.Unlock() + + if t.allowSSHItem != nil { + t.allowSSHItem.SetChecked(cfg.ServerSSHAllowed) + } + if t.autoConnItem != nil { + t.autoConnItem.SetChecked(!cfg.DisableAutoConnect) + } + if t.rosenpassItem != nil { + t.rosenpassItem.SetChecked(cfg.RosenpassEnabled) + } + if t.lazyConnItem != nil { + t.lazyConnItem.SetChecked(cfg.LazyConnectionEnabled) + } + if t.blockInItem != nil { + t.blockInItem.SetChecked(cfg.BlockInbound) + } + if t.notifyItem != nil { + t.notifyItem.SetChecked(!cfg.DisableNotifications) + } +} + +// notify wraps the Wails notification service with the tray's standard +// id-prefix scheme and swallows errors (notifications are best-effort). +func (t *Tray) notify(title, body, id string) { + if t.notifier == nil { + return + } + if err := t.notifier.SendNotification(notifications.NotificationOptions{ + ID: id, + Title: title, + Body: body, + }); err != nil { + log.Debugf("notify %q: %v", title, err) + } +} + +// notifyError fires a generic "Error" notification for tray-driven action +// failures. Each tray click site already logs the underlying error; this +// adds the user-visible toast. +func (t *Tray) notifyError(message string) { + t.notify("Error", message, "netbird-tray-error") +} + +func exitNodesFromStatus(st services.Status) []string { + seen := map[string]struct{}{} + out := []string{} + for _, p := range st.Peers { + if p.Fqdn == "" { + continue + } + if _, ok := seen[p.Fqdn]; ok { + continue + } + seen[p.Fqdn] = struct{}{} + out = append(out, p.Fqdn) + } + sort.Strings(out) + return out +} + +func equalStrings(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// eventTitle composes a notification title from a SystemEvent's severity and +// category — "Critical: DNS", "Warning: Authentication", etc. — matching the +// format the legacy Fyne event.Manager produced. +func eventTitle(e services.SystemEvent) string { + prefix := titleCase(e.Severity) + if prefix == "" { + prefix = "Info" + } + category := titleCase(e.Category) + if category == "" { + category = "System" + } + return prefix + ": " + category +} + +func titleCase(s string) string { + if s == "" { + return "" + } + return strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) +} diff --git a/client/ui-wails/tray_icon_other.go b/client/ui-wails/tray_icon_other.go new file mode 100644 index 000000000..6e2489929 --- /dev/null +++ b/client/ui-wails/tray_icon_other.go @@ -0,0 +1,8 @@ +//go:build !windows && !android && !ios && !freebsd && !js + +package main + +// trayIcon is unused on non-Windows hosts — Linux feeds setIcon a PNG and +// macOS uses SetTemplateIcon. This stub exists so the compiler is happy and +// callers don't need build tags around references. +func trayIcon(_, _ bool, _ string) []byte { return nil } diff --git a/client/ui-wails/tray_icon_windows.go b/client/ui-wails/tray_icon_windows.go new file mode 100644 index 000000000..8cd7ec606 --- /dev/null +++ b/client/ui-wails/tray_icon_windows.go @@ -0,0 +1,27 @@ +//go:build windows + +package main + +import "strings" + +// trayIcon returns the Windows-tray .ico bytes for the given state. The +// other-platform implementation in tray_icon_other.go returns the colored +// PNG instead. Splitting it this way keeps the Linux/macOS paths free of +// .ico artifacts in their //go:embed search and avoids loading large icon +// resources where they aren't used. +func trayIcon(connected, hasUpdate bool, statusLabel string) []byte { + switch { + case strings.EqualFold(statusLabel, "Connecting"): + return winIconConnecting + case strings.EqualFold(statusLabel, "Error"): + return winIconError + case connected && hasUpdate: + return winIconUpdateConnected + case connected: + return winIconConnected + case hasUpdate: + return winIconUpdateDisconnected + default: + return winIconDisconnected + } +} diff --git a/go.mod b/go.mod index 1958a3278..8ed0ba38e 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,17 @@ go 1.25.5 require ( cunicu.li/go-rosenpass v0.4.0 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/golang/protobuf v1.5.4 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/kardianos/service v1.2.3-0.20240613133416-becf2eb62b83 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.27.6 + github.com/onsi/gomega v1.34.1 github.com/rs/cors v1.8.0 github.com/sirupsen/logrus v1.9.4 github.com/spf13/cobra v1.10.1 - github.com/spf13/pflag v1.0.9 + github.com/spf13/pflag v1.0.10 github.com/vishvananda/netlink v1.3.1 golang.org/x/crypto v0.49.0 golang.org/x/sys v0.42.0 @@ -53,7 +53,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/gliderlabs/ssh v0.3.8 github.com/go-jose/go-jose/v4 v4.1.3 - github.com/godbus/dbus/v5 v5.1.0 + github.com/godbus/dbus/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.7.0 @@ -104,6 +104,7 @@ require ( github.com/ti-mo/conntrack v0.5.1 github.com/ti-mo/netfilter v0.5.2 github.com/vmihailenco/msgpack/v5 v5.4.1 + github.com/wailsapp/wails/v3 v3.0.0-alpha.78 github.com/yusufpapurcu/wmi v1.2.4 github.com/zcalusic/sysinfo v1.1.3 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 @@ -114,7 +115,7 @@ require ( go.uber.org/mock v0.5.2 go.uber.org/zap v1.27.0 goauthentik.io/api/v3 v3.2023051.3 - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 golang.org/x/mobile v0.0.0-20251113184115-a159579294ab golang.org/x/mod v0.33.0 golang.org/x/net v0.52.0 @@ -135,16 +136,18 @@ require ( cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.1 // indirect github.com/AppsFlyer/go-sundheit v0.6.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/BurntSushi/toml v1.5.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -166,6 +169,7 @@ require ( github.com/aws/smithy-go v1.23.0 // indirect github.com/beevik/etree v1.6.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bep/debounce v1.2.1 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -173,13 +177,15 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/crowdsecurity/go-cs-lib v0.0.25 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v28.0.1+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fredbi/uri v1.1.1 // indirect github.com/fyne-io/gl-js v0.2.0 // indirect @@ -187,6 +193,9 @@ require ( github.com/fyne-io/image v0.1.1 // indirect github.com/fyne-io/oksvg v0.2.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.16.4 // indirect github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect github.com/go-ldap/ldap/v3 v3.4.12 // indirect @@ -208,6 +217,7 @@ require ( github.com/goccy/go-yaml v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect @@ -225,6 +235,8 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -233,16 +245,22 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/u v1.1.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/libdns/libdns v0.2.2 // indirect + github.com/lmittmann/tint v1.1.2 // indirect github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mdelapenya/tlscert v0.2.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect @@ -263,7 +281,6 @@ require ( github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pion/dtls/v2 v2.2.10 // indirect @@ -271,6 +288,8 @@ require ( github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/transport/v2 v2.2.4 // indirect github.com/pion/turn/v4 v4.1.1 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect @@ -278,12 +297,16 @@ require ( github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/russellhaering/goxmldsig v1.6.0 // indirect github.com/rymdport/portal v0.4.2 // indirect + github.com/samber/lo v1.52.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect github.com/shirou/gopsutil/v4 v4.25.8 // indirect github.com/shoenig/go-m1cpu v0.2.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -291,8 +314,10 @@ require ( github.com/tklauser/numcpus v0.10.0 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/wailsapp/go-webview2 v1.0.23 // indirect github.com/wlynxg/anet v0.0.5 // indirect - github.com/yuin/goldmark v1.7.8 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/yuin/goldmark v1.7.16 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.mongodb.org/mongo-driver v1.17.9 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -301,13 +326,14 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/image v0.33.0 // indirect + golang.org/x/image v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/tools v0.42.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2abf55142..de267d08f 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cunicu.li/go-rosenpass v0.4.0 h1:LtPtBgFWY/9emfgC4glKLEqS0MJTylzV6+ChRhiZERw= cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJplf/M= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= fyne.io/fyne/v2 v2.7.0 h1:GvZSpE3X0liU/fqstInVvRsaboIVpIWQ4/sfjDGIGGQ= @@ -25,23 +25,30 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g= @@ -90,6 +97,8 @@ github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= @@ -132,6 +141,8 @@ github.com/crowdsecurity/go-cs-lib v0.0.25 h1:Ov6VPW9yV+OPsbAIQk1iTkEWhwkpaG0v3l github.com/crowdsecurity/go-cs-lib v0.0.25/go.mod h1:X0GMJY2CxdA1S09SpuqIKaWQsvRGxXmecUp9cP599dE= github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:/DS5cDX3FJdl+XaN2D7XAwFpuanTxnp52DBLZAaJKx0= github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -148,14 +159,18 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7Yuw= github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M= github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEMl+NivxtR5R3/hw= github.com/eko/gocache/store/go_cache/v4 v4.2.2/go.mod h1:T9zkHokzr8K9EiC7RfMbDg6HSwaV6rv3UdcNu13SGcA= github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0= github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -185,12 +200,22 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= +github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -226,9 +251,10 @@ github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZs github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= @@ -237,14 +263,16 @@ github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -281,8 +309,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg= github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -334,6 +362,10 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= +github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -366,13 +398,15 @@ github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6U github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= +github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -386,6 +420,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -395,6 +433,8 @@ github.com/libdns/route53 v1.5.0 h1:2SKdpPFl/qgWsXQvsLNJJAoX7rSxlk7zgoL4jnWdXVA= github.com/libdns/route53 v1.5.0/go.mod h1:joT4hKmaTNKHEwb7GmZ65eoDz1whTu7KKYPS8ZqIh6Q= github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81 h1:J56rFEfUTFT9j9CiRXhi1r8lUJ4W5idG3CiaBZGojNU= github.com/lrh3321/ipset-go v0.0.0-20250619021614-54a0a98ace81/go.mod h1:RD8ML/YdXctQ7qbcizZkw5mZ6l8Ogrl1dodBzVJduwI= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= @@ -402,9 +442,16 @@ github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tA github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= @@ -481,12 +528,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -527,6 +574,10 @@ github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc= github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -554,6 +605,9 @@ github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9M github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= @@ -565,6 +619,10 @@ github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBe github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU= github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= @@ -577,18 +635,22 @@ github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= @@ -600,6 +662,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -642,14 +705,20 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= +github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/wails/v3 v3.0.0-alpha.78 h1:31nJb4N8X+SIBZ88RNkptFA1eUnBOH805tDV0sN7Vpk= +github.com/wailsapp/wails/v3 v3.0.0-alpha.78/go.mod h1:4saK4A4K9970X+X7RkMwP2lyGbLogcUz54wVeq4C/V8= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0= @@ -702,6 +771,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= @@ -711,10 +781,10 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= -golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= +golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20251113184115-a159579294ab h1:Iqyc+2zr7aGyLuEadIm0KRJP0Wwt+fhlXLa51Fxf1+Q= golang.org/x/mobile v0.0.0-20251113184115-a159579294ab/go.mod h1:Eq3Nh/5pFSWug2ohiudJ1iyU59SO78QFuh4qTTN++I0= @@ -738,6 +808,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= @@ -772,23 +843,29 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -818,6 +895,7 @@ golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -893,6 +971,8 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=