mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-17 22:29:54 +00:00
wip
This commit is contained in:
1
client/ui-wails/.gitignore
vendored
1
client/ui-wails/.gitignore
vendored
@@ -3,5 +3,6 @@ bin
|
|||||||
frontend/dist
|
frontend/dist
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
frontend/bindings
|
frontend/bindings
|
||||||
|
frontend/.vite
|
||||||
build/linux/appimage/build
|
build/linux/appimage/build
|
||||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|||||||
@@ -11,17 +11,28 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
|
"@radix-ui/react-visually-hidden": "^1.2.4",
|
||||||
"@wailsio/runtime": "latest",
|
"@wailsio/runtime": "latest",
|
||||||
|
"chroma-js": "^3.2.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-loading-skeleton": "^3.5.0",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
"tailwind-merge": "^2.6.0"
|
"tailwind-merge": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chroma-js": "^3.1.2",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
|
|||||||
829
client/ui-wails/frontend/pnpm-lock.yaml
generated
829
client/ui-wails/frontend/pnpm-lock.yaml
generated
@@ -5,15 +5,42 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@radix-ui/react-dialog':
|
||||||
|
specifier: ^1.1.15
|
||||||
|
version: 1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dropdown-menu':
|
||||||
|
specifier: ^2.1.16
|
||||||
|
version: 2.1.16(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-label':
|
||||||
|
specifier: ^2.1.8
|
||||||
|
version: 2.1.8(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-popover':
|
||||||
|
specifier: ^1.1.15
|
||||||
|
version: 1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-scroll-area':
|
||||||
|
specifier: ^1.2.10
|
||||||
|
version: 1.2.10(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-visually-hidden':
|
||||||
|
specifier: ^1.2.4
|
||||||
|
version: 1.2.4(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@wailsio/runtime':
|
'@wailsio/runtime':
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 3.0.0-alpha.79
|
version: 3.0.0-alpha.79
|
||||||
|
chroma-js:
|
||||||
|
specifier: ^3.2.0
|
||||||
|
version: 3.2.0
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
classnames:
|
||||||
|
specifier: ^2.5.1
|
||||||
|
version: 2.5.1
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
cmdk:
|
||||||
|
specifier: ^1.1.1
|
||||||
|
version: 1.1.1(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^12.38.0
|
specifier: ^12.38.0
|
||||||
version: 12.38.0(react-dom@18.3.1)(react@18.3.1)
|
version: 12.38.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
@@ -26,6 +53,9 @@ dependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
|
react-loading-skeleton:
|
||||||
|
specifier: ^3.5.0
|
||||||
|
version: 3.5.0(react@18.3.1)
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^7.1.3
|
specifier: ^7.1.3
|
||||||
version: 7.14.2(react-dom@18.3.1)(react@18.3.1)
|
version: 7.14.2(react-dom@18.3.1)(react@18.3.1)
|
||||||
@@ -34,6 +64,9 @@ dependencies:
|
|||||||
version: 2.6.1
|
version: 2.6.1
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/chroma-js':
|
||||||
|
specifier: ^3.1.2
|
||||||
|
version: 3.1.2
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.6.0
|
specifier: ^25.6.0
|
||||||
version: 25.6.0
|
version: 25.6.0
|
||||||
@@ -482,6 +515,34 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@floating-ui/core@1.7.5:
|
||||||
|
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/utils': 0.2.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@floating-ui/dom@1.7.6:
|
||||||
|
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.7.5
|
||||||
|
'@floating-ui/utils': 0.2.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@floating-ui/react-dom@2.1.8(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/dom': 1.7.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@floating-ui/utils@0.2.11:
|
||||||
|
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@jridgewell/gen-mapping@0.3.13:
|
/@jridgewell/gen-mapping@0.3.13:
|
||||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -533,6 +594,635 @@ packages:
|
|||||||
fastq: 1.20.1
|
fastq: 1.20.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@radix-ui/number@1.1.1:
|
||||||
|
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/primitive@1.1.3:
|
||||||
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-label@2.1.8(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/react-dom': 2.1.8(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/number': 1.1.1
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-visually-hidden@1.2.4(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/rect@1.1.1:
|
||||||
|
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rolldown/pluginutils@1.0.0-beta.27:
|
/@rolldown/pluginutils@1.0.0-beta.27:
|
||||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -766,6 +1456,10 @@ packages:
|
|||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/chroma-js@3.1.2:
|
||||||
|
resolution: {integrity: sha512-YBTQqArPN8A0niHXCwrO1z5x++a+6l0mLBykncUpr23oIPW7L4h39s6gokdK/bDrPmSh8+TjMmrhBPnyiaWPmQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/estree@1.0.8:
|
/@types/estree@1.0.8:
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -778,7 +1472,6 @@ packages:
|
|||||||
|
|
||||||
/@types/prop-types@15.7.15:
|
/@types/prop-types@15.7.15:
|
||||||
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/react-dom@18.3.7(@types/react@18.3.28):
|
/@types/react-dom@18.3.7(@types/react@18.3.28):
|
||||||
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
||||||
@@ -786,14 +1479,12 @@ packages:
|
|||||||
'@types/react': ^18.0.0
|
'@types/react': ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.3.28
|
'@types/react': 18.3.28
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/react@18.3.28:
|
/@types/react@18.3.28:
|
||||||
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
|
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.15
|
'@types/prop-types': 15.7.15
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@vitejs/plugin-react@4.7.0(vite@6.4.2):
|
/@vitejs/plugin-react@4.7.0(vite@6.4.2):
|
||||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||||
@@ -832,6 +1523,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/aria-hidden@1.2.6:
|
||||||
|
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/autoprefixer@10.5.0(postcss@8.5.12):
|
/autoprefixer@10.5.0(postcss@8.5.12):
|
||||||
resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==}
|
resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -901,17 +1599,42 @@ packages:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/chroma-js@3.2.0:
|
||||||
|
resolution: {integrity: sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/class-variance-authority@0.7.1:
|
/class-variance-authority@0.7.1:
|
||||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/classnames@2.5.1:
|
||||||
|
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/clsx@2.1.1:
|
/clsx@2.1.1:
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/cmdk@1.1.1(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7)(@types/react@18.3.28)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/commander@4.1.1:
|
/commander@4.1.1:
|
||||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -934,7 +1657,6 @@ packages:
|
|||||||
|
|
||||||
/csstype@3.2.3:
|
/csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/debug@4.4.3:
|
/debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
@@ -948,6 +1670,10 @@ packages:
|
|||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/detect-node-es@1.1.0:
|
||||||
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/didyoumean@1.2.2:
|
/didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1082,6 +1808,11 @@ packages:
|
|||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/get-nonce@1.0.1:
|
||||||
|
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/glob-parent@5.1.2:
|
/glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -1362,11 +2093,54 @@ packages:
|
|||||||
scheduler: 0.23.2
|
scheduler: 0.23.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-loading-skeleton@3.5.0(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-refresh@0.17.0:
|
/react-refresh@0.17.0:
|
||||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-remove-scroll@2.7.2(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
tslib: 2.8.1
|
||||||
|
use-callback-ref: 1.3.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
use-sidecar: 1.1.3(@types/react@18.3.28)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-router-dom@7.14.2(react-dom@18.3.1)(react@18.3.1):
|
/react-router-dom@7.14.2(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==}
|
resolution: {integrity: sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -1395,6 +2169,22 @@ packages:
|
|||||||
set-cookie-parser: 2.7.2
|
set-cookie-parser: 2.7.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-style-singleton@2.2.3(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
get-nonce: 1.0.1
|
||||||
|
react: 18.3.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react@18.3.1:
|
/react@18.3.1:
|
||||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1612,6 +2402,37 @@ packages:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-callback-ref@1.3.3(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
react: 18.3.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/use-sidecar@1.1.3(@types/react@18.3.28)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.28
|
||||||
|
detect-node-es: 1.1.0
|
||||||
|
react: 18.3.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import "./globals.css";
|
|
||||||
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
|
|
||||||
import QuickActions from "@/screens/QuickActions.tsx";
|
|
||||||
import LoginUrl from "@/screens/LoginUrl.tsx";
|
|
||||||
import Update from "@/screens/Update.tsx";
|
|
||||||
import Layout from "@/layout.tsx";
|
|
||||||
import Peers from "@/screens/Peers.tsx";
|
|
||||||
import Networks from "@/screens/Networks.tsx";
|
|
||||||
import Profiles from "@/screens/Profiles.tsx";
|
|
||||||
import Settings from "@/screens/Settings.tsx";
|
|
||||||
import Debug from "@/screens/Debug.tsx";
|
|
||||||
import {Main} from "@/screens/Main.tsx";
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<HashRouter>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/quick" element={<QuickActions />} />
|
|
||||||
<Route path="/login" element={<LoginUrl />} />
|
|
||||||
<Route path="/update" element={<Update />} />
|
|
||||||
<Route element={<Layout />}>
|
|
||||||
<Route index element={<Main />} />
|
|
||||||
<Route path="peers" element={<Peers />} />
|
|
||||||
<Route path="networks" element={<Networks />} />
|
|
||||||
<Route path="profiles" element={<Profiles />} />
|
|
||||||
<Route path="settings" element={<Settings />} />
|
|
||||||
<Route path="debug" element={<Debug />} />
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</HashRouter>
|
|
||||||
</React.StrictMode>,
|
|
||||||
);
|
|
||||||
39
client/ui-wails/frontend/src/app.tsx
Normal file
39
client/ui-wails/frontend/src/app.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import "./globals.css";
|
||||||
|
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
import QuickActions from "@/screens/QuickActions.tsx";
|
||||||
|
import LoginUrl from "@/screens/LoginUrl.tsx";
|
||||||
|
import Update from "@/screens/Update.tsx";
|
||||||
|
import Layout from "@/layout.tsx";
|
||||||
|
import Peers from "@/screens/Peers.tsx";
|
||||||
|
import Networks from "@/screens/Networks.tsx";
|
||||||
|
import Profiles from "@/screens/Profiles.tsx";
|
||||||
|
import Settings from "@/screens/Settings.tsx";
|
||||||
|
import Debug from "@/screens/Debug.tsx";
|
||||||
|
import {Main} from "@/screens/Main.tsx";
|
||||||
|
import { SkeletonTheme } from "react-loading-skeleton";
|
||||||
|
import "react-loading-skeleton/dist/skeleton.css";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<SkeletonTheme baseColor={"#25282d"} highlightColor={"#33373e"}>
|
||||||
|
<HashRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/quick" element={<QuickActions />} />
|
||||||
|
<Route path="/login" element={<LoginUrl />} />
|
||||||
|
<Route path="/update" element={<Update />} />
|
||||||
|
<Route element={<Layout />}>
|
||||||
|
<Route index element={<Main />} />
|
||||||
|
<Route path="peers" element={<Peers />} />
|
||||||
|
<Route path="networks" element={<Networks />} />
|
||||||
|
<Route path="profiles" element={<Profiles />} />
|
||||||
|
<Route path="settings" element={<Settings />} />
|
||||||
|
<Route path="debug" element={<Debug />} />
|
||||||
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</HashRouter>
|
||||||
|
</SkeletonTheme>
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
@@ -1,42 +1,151 @@
|
|||||||
|
import { cva, VariantProps } from "class-variance-authority";
|
||||||
|
import classNames from "classnames";
|
||||||
import { ButtonHTMLAttributes, forwardRef } from "react";
|
import { ButtonHTMLAttributes, forwardRef } from "react";
|
||||||
import { cn } from "../lib/cn";
|
|
||||||
|
|
||||||
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||||
type Size = "sm" | "md";
|
|
||||||
|
|
||||||
const variants: Record<Variant, string> = {
|
export interface ButtonProps
|
||||||
primary: "bg-netbird text-white hover:bg-netbird-500 disabled:bg-nb-gray-300",
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
secondary:
|
ButtonVariants {
|
||||||
"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",
|
disabled?: boolean;
|
||||||
ghost:
|
stopPropagation?: boolean;
|
||||||
"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<Size, string> = {
|
|
||||||
sm: "h-7 px-2 text-xs",
|
|
||||||
md: "h-9 px-3 text-sm",
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
||||||
variant?: Variant;
|
|
||||||
size?: Size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Button = forwardRef<HTMLButtonElement, Props>(function Button(
|
export const buttonVariants = cva(
|
||||||
{ variant = "primary", size = "md", className, ...rest },
|
[
|
||||||
ref,
|
"relative",
|
||||||
) {
|
"text-sm focus:z-10 focus:ring-2 font-medium focus:outline-none whitespace-nowrap shadow-sm",
|
||||||
return (
|
"inline-flex gap-2 items-center justify-center transition-colors focus:ring-offset-1",
|
||||||
<button
|
"disabled:opacity-40 disabled:cursor-not-allowed disabled:dark:text-nb-gray-300 dark:ring-offset-neutral-950/50",
|
||||||
ref={ref}
|
],
|
||||||
className={cn(
|
{
|
||||||
"inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60",
|
variants: {
|
||||||
variants[variant],
|
variant: {
|
||||||
sizes[size],
|
default: [
|
||||||
className,
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
||||||
)}
|
"dark:focus:ring-zinc-800/50 dark:bg-nb-gray dark:text-gray-400 dark:border-gray-700/30 dark:hover:text-white dark:hover:bg-zinc-800/50",
|
||||||
{...rest}
|
],
|
||||||
/>
|
primary: [
|
||||||
);
|
"dark:focus:ring-netbird-600/50 dark:ring-offset-neutral-950/50 enabled:dark:bg-netbird disabled:dark:bg-nb-gray-910 dark:text-gray-100 enabled:dark:hover:text-white enabled:dark:hover:bg-netbird-500/80",
|
||||||
});
|
"enabled:bg-netbird enabled:text-white enabled:focus:ring-netbird-400/50 enabled:hover:bg-netbird-500",
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
||||||
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20",
|
||||||
|
"dark:bg-nb-gray-920 dark:text-gray-400 dark:border-gray-700/40 dark:hover:text-white dark:hover:bg-nb-gray-910",
|
||||||
|
],
|
||||||
|
secondaryLighter: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
||||||
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20",
|
||||||
|
"dark:bg-nb-gray-900/70 dark:text-gray-400 dark:border-gray-700/70 dark:hover:text-white dark:hover:bg-nb-gray-800/60",
|
||||||
|
],
|
||||||
|
input: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
|
||||||
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20",
|
||||||
|
"dark:bg-nb-gray-900 dark:text-gray-400 dark:border-nb-gray-700 dark:hover:bg-nb-gray-900/80",
|
||||||
|
],
|
||||||
|
dropdown: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
|
||||||
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20",
|
||||||
|
"dark:bg-nb-gray-900/40 dark:text-gray-400 dark:border-nb-gray-900 dark:hover:bg-nb-gray-900/50",
|
||||||
|
],
|
||||||
|
dotted: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900 border-dashed",
|
||||||
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20",
|
||||||
|
"dark:bg-nb-gray-900/30 dark:text-gray-400 dark:border-gray-500/40 dark:hover:text-white dark:hover:bg-nb-gray-900/50",
|
||||||
|
],
|
||||||
|
tertiary: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
||||||
|
"dark:focus:ring-zinc-800/50 dark:bg-white dark:text-gray-800 dark:border-gray-700/40 dark:hover:bg-neutral-200 disabled:dark:bg-nb-gray-920 disabled:dark:text-nb-gray-300",
|
||||||
|
],
|
||||||
|
white: [
|
||||||
|
"focus:ring-white/50 bg-white text-gray-800 border-white outline-none hover:bg-neutral-200 disabled:dark:bg-nb-gray-920 disabled:dark:text-nb-gray-300",
|
||||||
|
"disabled:dark:bg-nb-gray-900 disabled:dark:text-nb-gray-300 disabled:dark:border-nb-gray-900",
|
||||||
|
],
|
||||||
|
outline: [
|
||||||
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
||||||
|
"dark:focus:ring-zinc-800/50 dark:bg-transparent dark:text-netbird dark:border-netbird dark:hover:bg-nb-gray-900/30",
|
||||||
|
],
|
||||||
|
"danger-outline": [
|
||||||
|
"enabled:dark:focus:ring-red-800/20 enabled:dark:focus:bg-red-950/40 enabled:hover:dark:bg-red-950/50 enabled:dark:hover:border-red-800/50 dark:bg-transparent dark:text-red-500",
|
||||||
|
],
|
||||||
|
"danger-text": [
|
||||||
|
"dark:bg-transparent dark:text-red-500 dark:hover:text-red-600 dark:border-transparent !px-0 !shadow-none !py-0 focus:ring-red-500/30 dark:ring-offset-neutral-950/50 rounded-sm",
|
||||||
|
],
|
||||||
|
"default-outline": [
|
||||||
|
"dark:ring-offset-nb-gray-950/50 dark:focus:ring-nb-gray-500/20",
|
||||||
|
"dark:bg-transparent dark:text-nb-gray-400 dark:border-transparent dark:hover:text-white dark:hover:bg-nb-gray-900/30 dark:hover:border-nb-gray-800/50",
|
||||||
|
"data-[state=open]:dark:text-white data-[state=open]:dark:bg-nb-gray-900/30 data-[state=open]:dark:border-nb-gray-800/50",
|
||||||
|
],
|
||||||
|
ghost: [
|
||||||
|
"dark:ring-offset-nb-gray-950/50 dark:focus:ring-nb-gray-500/20",
|
||||||
|
"dark:bg-transparent dark:text-nb-gray-400 dark:border-transparent dark:hover:text-white dark:hover:bg-nb-gray-900/30",
|
||||||
|
],
|
||||||
|
danger: [
|
||||||
|
"dark:focus:ring-red-700/20 dark:focus:bg-red-700 hover:dark:bg-red-700 dark:hover:border-red-800/50 dark:bg-red-600 dark:text-red-100",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
xs: "text-xs py-2 px-4",
|
||||||
|
xs2: "text-[0.78rem] py-2 px-4",
|
||||||
|
sm: "text-sm py-2.5 px-4",
|
||||||
|
md: "text-md py-2.5 px-4",
|
||||||
|
lg: "text-lg py-2.5 px-4",
|
||||||
|
},
|
||||||
|
rounded: {
|
||||||
|
true: "rounded-md",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
0: "border",
|
||||||
|
1: "border border-transparent",
|
||||||
|
2: "border border-t-0 border-b-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
function Button(
|
||||||
|
{
|
||||||
|
variant = "default",
|
||||||
|
rounded = true,
|
||||||
|
border = 1,
|
||||||
|
size = "md",
|
||||||
|
stopPropagation = true,
|
||||||
|
type = "button",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
disabled={disabled}
|
||||||
|
className={classNames(
|
||||||
|
buttonVariants({
|
||||||
|
variant,
|
||||||
|
rounded,
|
||||||
|
border: border ? 1 : 0,
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (stopPropagation) e.stopPropagation();
|
||||||
|
onClick?.(e);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Button;
|
||||||
|
|||||||
149
client/ui-wails/frontend/src/components/Dialog.tsx
Normal file
149
client/ui-wails/frontend/src/components/Dialog.tsx
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
ElementRef,
|
||||||
|
HTMLAttributes,
|
||||||
|
} from "react";
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
export const Root = DialogPrimitive.Root;
|
||||||
|
export const Trigger = DialogPrimitive.Trigger;
|
||||||
|
export const Close = DialogPrimitive.Close;
|
||||||
|
export const Portal = DialogPrimitive.Portal;
|
||||||
|
|
||||||
|
export const Overlay = forwardRef<
|
||||||
|
ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(function DialogOverlay({ className, ...props }, ref) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 grid place-items-start overflow-y-auto py-16",
|
||||||
|
"bg-black/40 backdrop-blur-sm",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
|
"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0",
|
||||||
|
"duration-150 ease-out",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={{ scrollbarGutter: "stable both-edges" }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
type ContentProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||||
|
showClose?: boolean;
|
||||||
|
maxWidthClass?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Content = forwardRef<
|
||||||
|
ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
ContentProps
|
||||||
|
>(function DialogContent(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showClose = true,
|
||||||
|
maxWidthClass = "max-w-md",
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<Overlay>
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mx-auto relative z-[52] w-full outline-none ring-0",
|
||||||
|
"focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0",
|
||||||
|
"border border-nb-gray-900 bg-nb-gray py-6 shadow-2xl rounded-lg",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||||
|
"data-[state=closed]:slide-out-to-left-1 data-[state=open]:slide-in-from-left-1",
|
||||||
|
"duration-150 ease-out",
|
||||||
|
maxWidthClass,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<VisuallyHidden asChild>
|
||||||
|
<DialogPrimitive.Title>Dialog</DialogPrimitive.Title>
|
||||||
|
</VisuallyHidden>
|
||||||
|
{children}
|
||||||
|
{showClose && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
className={cn(
|
||||||
|
"absolute right-4 top-4 z-10 rounded-sm opacity-70 transition-opacity",
|
||||||
|
"hover:opacity-100 focus:outline-none disabled:pointer-events-none",
|
||||||
|
"text-nb-gray-300",
|
||||||
|
)}
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</Overlay>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Title = forwardRef<
|
||||||
|
ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(function DialogTitle({ className, ...props }, ref) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-md font-semibold leading-none tracking-tight text-nb-gray-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Description = forwardRef<
|
||||||
|
ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(function DialogDescription({ className, ...props }, ref) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-sm text-nb-gray-400 mt-2 leading-snug",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
type FooterProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
separator?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Footer = ({
|
||||||
|
className,
|
||||||
|
separator = true,
|
||||||
|
...props
|
||||||
|
}: FooterProps) => (
|
||||||
|
<div className={cn(separator && "border-t border-nb-gray-900 mt-6")}>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-3",
|
||||||
|
"px-8 pt-6",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
22
client/ui-wails/frontend/src/components/HelpText.tsx
Normal file
22
client/ui-wails/frontend/src/components/HelpText.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children?: ReactNode;
|
||||||
|
margin?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HelpText = ({ children, margin = true, className }: Props) => (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-[.8rem] dark:text-nb-gray-300 block font-light tracking-wide",
|
||||||
|
margin && "mb-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default HelpText;
|
||||||
41
client/ui-wails/frontend/src/components/IconButton.tsx
Normal file
41
client/ui-wails/frontend/src/components/IconButton.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { ComponentType, forwardRef } from "react";
|
||||||
|
import { motion, HTMLMotionProps } from "framer-motion";
|
||||||
|
import { LucideProps } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
type Props = HTMLMotionProps<"button"> & {
|
||||||
|
icon: ComponentType<LucideProps>;
|
||||||
|
iconSize?: number;
|
||||||
|
iconClassName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
||||||
|
function IconButton(
|
||||||
|
{
|
||||||
|
icon: Icon,
|
||||||
|
iconSize = 18,
|
||||||
|
iconClassName,
|
||||||
|
className,
|
||||||
|
type = "button",
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className={cn(
|
||||||
|
"h-11 w-11 flex items-center justify-center rounded-md cursor-default outline-none",
|
||||||
|
"text-nb-gray-400 hover:text-nb-gray-300 hover:bg-nb-gray-930",
|
||||||
|
"transition-colors duration-150",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Icon size={iconSize} className={iconClassName} />
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,33 +1,172 @@
|
|||||||
import { InputHTMLAttributes, forwardRef } from "react";
|
import { cva, VariantProps } from "class-variance-authority";
|
||||||
import { cn } from "../lib/cn";
|
import { AlertCircle, Eye, EyeOff } from "lucide-react";
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
InputHTMLAttributes,
|
||||||
|
ReactNode,
|
||||||
|
useId,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
import { Label } from "@/components/Label";
|
||||||
|
|
||||||
interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
type InputVariants = VariantProps<typeof inputVariants>;
|
||||||
label?: string;
|
|
||||||
|
export interface InputProps
|
||||||
|
extends InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
InputVariants {
|
||||||
|
label?: string;
|
||||||
|
customPrefix?: ReactNode;
|
||||||
|
customSuffix?: ReactNode;
|
||||||
|
maxWidthClass?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
error?: string;
|
||||||
|
prefixClassName?: string;
|
||||||
|
showPasswordToggle?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = forwardRef<HTMLInputElement, Props>(function Input(
|
const inputVariants = cva("", {
|
||||||
{ label, className, id, ...rest },
|
variants: {
|
||||||
ref,
|
variant: {
|
||||||
) {
|
default: [
|
||||||
const inputId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
|
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-nb-gray-700",
|
||||||
return (
|
"ring-offset-neutral-200/20 dark:ring-offset-neutral-950/50 dark:focus-visible:ring-neutral-500/20 focus-visible:ring-neutral-300/10",
|
||||||
<div className="flex flex-col gap-1">
|
],
|
||||||
{label && (
|
darker: [
|
||||||
<label htmlFor={inputId} className="text-xs font-medium text-nb-gray-600 dark:text-nb-gray-300">
|
"dark:bg-nb-gray-920 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-300 dark:border-nb-gray-800",
|
||||||
{label}
|
"ring-offset-neutral-200/20 dark:ring-offset-neutral-950/50 dark:focus-visible:ring-neutral-500/20 focus-visible:ring-neutral-300/10",
|
||||||
</label>
|
],
|
||||||
)}
|
error: [
|
||||||
<input
|
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-red-500 text-red-500",
|
||||||
id={inputId}
|
"ring-offset-red-500/10 dark:ring-offset-red-500/10 dark:focus-visible:ring-red-500/10 focus-visible:ring-red-500/10",
|
||||||
ref={ref}
|
],
|
||||||
className={cn(
|
},
|
||||||
"h-9 rounded-md border border-nb-gray-300 bg-white px-3 text-sm",
|
prefixSuffixVariant: {
|
||||||
"focus:border-netbird focus:outline-none focus:ring-1 focus:ring-netbird",
|
default: [
|
||||||
"dark:border-nb-gray-700 dark:bg-nb-gray-925 dark:text-nb-gray-50",
|
"dark:bg-nb-gray-900 border-neutral-200 dark:border-nb-gray-700 text-nb-gray-300",
|
||||||
className,
|
],
|
||||||
)}
|
error: [
|
||||||
{...rest}
|
"dark:bg-nb-gray-900 border-red-500 text-nb-gray-300 text-red-500",
|
||||||
/>
|
],
|
||||||
</div>
|
},
|
||||||
);
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
type,
|
||||||
|
label,
|
||||||
|
customSuffix,
|
||||||
|
customPrefix,
|
||||||
|
icon,
|
||||||
|
maxWidthClass = "",
|
||||||
|
error,
|
||||||
|
variant = "default",
|
||||||
|
prefixClassName,
|
||||||
|
showPasswordToggle = false,
|
||||||
|
id,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const isPasswordType = type === "password";
|
||||||
|
const inputType = isPasswordType && showPassword ? "text" : type;
|
||||||
|
|
||||||
|
const reactId = useId();
|
||||||
|
const inputId =
|
||||||
|
id ?? (label ? `input-${reactId}` : undefined);
|
||||||
|
|
||||||
|
const passwordToggle =
|
||||||
|
isPasswordType && showPasswordToggle ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword((s) => !s)}
|
||||||
|
className="hover:text-white transition-all"
|
||||||
|
aria-label="Toggle password visibility"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||||
|
</button>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const suffix = passwordToggle || customSuffix;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{label && <Label htmlFor={inputId}>{label}</Label>}
|
||||||
|
<div className={cn("flex relative h-[42px]", maxWidthClass)}>
|
||||||
|
{customPrefix && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
inputVariants({
|
||||||
|
prefixSuffixVariant: error
|
||||||
|
? "error"
|
||||||
|
: "default",
|
||||||
|
}),
|
||||||
|
"flex h-[42px] w-auto rounded-l-md bg-white px-3 py-2 text-sm",
|
||||||
|
"border items-center whitespace-nowrap",
|
||||||
|
props.disabled && "opacity-40",
|
||||||
|
prefixClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{customPrefix}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{icon && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute left-0 top-0 h-full flex items-center text-xs dark:text-nb-gray-300 pl-3 leading-[0]",
|
||||||
|
props.disabled && "opacity-40",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<input
|
||||||
|
id={inputId}
|
||||||
|
type={inputType}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
inputVariants({
|
||||||
|
variant: error ? "error" : variant,
|
||||||
|
}),
|
||||||
|
"flex h-[42px] w-full rounded-md bg-white px-3 py-2 text-sm",
|
||||||
|
"file:bg-transparent file:text-sm file:font-medium file:border-0",
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
||||||
|
"disabled:cursor-not-allowed disabled:opacity-40",
|
||||||
|
customPrefix && "!border-l-0 !rounded-l-none",
|
||||||
|
suffix && "!pr-16",
|
||||||
|
icon && "!pl-10",
|
||||||
|
"border",
|
||||||
|
props.readOnly &&
|
||||||
|
"!bg-nb-gray-920 text-nb-gray-400 !border-nb-gray-800",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{suffix && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute right-0 top-0 h-full flex items-center text-xs dark:text-nb-gray-300 pr-4 leading-[0] select-none",
|
||||||
|
props.disabled && "opacity-30",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{suffix}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<span className="text-xs text-red-500 mt-2 inline-flex items-center gap-1">
|
||||||
|
<AlertCircle size={13} />
|
||||||
|
{error}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Input;
|
||||||
|
|||||||
40
client/ui-wails/frontend/src/components/Label.tsx
Normal file
40
client/ui-wails/frontend/src/components/Label.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { ComponentPropsWithoutRef, forwardRef, Ref } from "react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium tracking-wider leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 mb-1.5 inline-block dark:text-nb-gray-200 flex items-center gap-2",
|
||||||
|
);
|
||||||
|
|
||||||
|
type LabelProps = ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants> & {
|
||||||
|
as?: "label" | "div";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Label = forwardRef<HTMLElement, LabelProps>(function Label(
|
||||||
|
{ className, as = "label", children, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const classes = cn(labelVariants(), className, "select-none");
|
||||||
|
|
||||||
|
if (as === "div") {
|
||||||
|
return (
|
||||||
|
<div ref={ref as Ref<HTMLDivElement>} className={classes}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref as Ref<HTMLLabelElement>}
|
||||||
|
className={classes}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</LabelPrimitive.Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Label;
|
||||||
81
client/ui-wails/frontend/src/components/NavItem.tsx
Normal file
81
client/ui-wails/frontend/src/components/NavItem.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { ComponentType, forwardRef } from "react";
|
||||||
|
import { motion, HTMLMotionProps } from "framer-motion";
|
||||||
|
import { LucideProps } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
type Props = HTMLMotionProps<"button"> & {
|
||||||
|
icon: ComponentType<LucideProps>;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
active?: boolean;
|
||||||
|
iconSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavItem = forwardRef<HTMLButtonElement, Props>(
|
||||||
|
function NavItem(
|
||||||
|
{
|
||||||
|
icon: Icon,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
active = false,
|
||||||
|
iconSize = 15,
|
||||||
|
className,
|
||||||
|
type = "button",
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
className={cn(
|
||||||
|
"w-full flex items-center gap-3 p-1.5 rounded-lg cursor-default outline-none text-left",
|
||||||
|
"transition-colors duration-150",
|
||||||
|
active
|
||||||
|
? "bg-nb-gray-930"
|
||||||
|
: "hover:bg-nb-gray-940",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-9 w-9 rounded-md flex items-center justify-center shrink-0",
|
||||||
|
"transition-colors duration-150",
|
||||||
|
active ? "bg-nb-gray-800" : "bg-nb-gray-920",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
size={iconSize}
|
||||||
|
className={cn(
|
||||||
|
"transition-colors duration-150",
|
||||||
|
active ? "text-nb-gray-200" : "text-nb-gray-400",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={"min-w-0"}>
|
||||||
|
<h2
|
||||||
|
className={cn(
|
||||||
|
"font-medium text-[0.81rem] truncate",
|
||||||
|
active ? "text-nb-gray-100" : "text-nb-gray-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
{description && (
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"text-xs font-medium truncate",
|
||||||
|
active ? "text-nb-gray-300" : "text-nb-gray-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -46,7 +46,7 @@ export const NetBirdConnectToggle = ({ state, size = 140, onClick }: NetBirdConn
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<motion.button
|
<motion.button
|
||||||
className="rounded-full relative overflow-visible cursor-pointer outline-none border-none bg-transparent"
|
className="rounded-full relative overflow-visible cursor-default outline-none border-none bg-transparent"
|
||||||
style={{ padding }}
|
style={{ padding }}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
|
|||||||
73
client/ui-wails/frontend/src/components/NewProfileDialog.tsx
Normal file
73
client/ui-wails/frontend/src/components/NewProfileDialog.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { FormEvent, useEffect, useState } from "react";
|
||||||
|
import * as Dialog from "@/components/Dialog";
|
||||||
|
import { Input } from "@/components/Input";
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onCreate: (name: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewProfileDialog = ({ open, onOpenChange, onCreate }: Props) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) setName("");
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const trimmed = name.trim();
|
||||||
|
const canSubmit = trimmed.length > 0;
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!canSubmit) return;
|
||||||
|
onCreate(trimmed);
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
||||||
|
<Dialog.Content
|
||||||
|
maxWidthClass="max-w-md"
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="px-8 pt-2">
|
||||||
|
<Dialog.Title>New Profile</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
Profiles let you keep separate NetBird connections
|
||||||
|
side by side. Give your profile a memorable name.
|
||||||
|
</Dialog.Description>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-8 pt-3">
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
placeholder="e.g. Work"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
export default function PlaceholderHeader() {
|
export default function PlaceholderHeader() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-[38px] shrink-0 cursor-default"
|
className="h-[36px] shrink-0 cursor-default"
|
||||||
style={{ "--wails-draggable": "drag" } as React.CSSProperties}
|
style={{ "--wails-draggable": "drag" } as React.CSSProperties}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
407
client/ui-wails/frontend/src/components/ProfileSelector.tsx
Normal file
407
client/ui-wails/frontend/src/components/ProfileSelector.tsx
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { Command } from "cmdk";
|
||||||
|
import { Dialogs } from "@wailsio/runtime";
|
||||||
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
MoreVertical,
|
||||||
|
PlusCircle,
|
||||||
|
Search,
|
||||||
|
Trash2,
|
||||||
|
UserMinus,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
import { generateColorFromString } from "@/lib/color";
|
||||||
|
import { NewProfileDialog } from "@/components/NewProfileDialog";
|
||||||
|
|
||||||
|
export type Profile = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MOCK_PROFILES: Profile[] = [
|
||||||
|
{ id: "default", name: "Default Profile" },
|
||||||
|
{ id: "work", name: "Work" },
|
||||||
|
{ id: "personal", name: "Personal" },
|
||||||
|
{ id: "staging", name: "Staging" },
|
||||||
|
{ id: "production", name: "Production" },
|
||||||
|
{ id: "dev", name: "Development" },
|
||||||
|
{ id: "qa", name: "QA Environment" },
|
||||||
|
{ id: "demo", name: "Demo" },
|
||||||
|
{ id: "client-acme", name: "Client - ACME" },
|
||||||
|
{ id: "client-globex", name: "Client - Globex" },
|
||||||
|
{ id: "client-initech", name: "Client - Initech" },
|
||||||
|
{ id: "homelab", name: "Homelab" },
|
||||||
|
{ id: "office-berlin", name: "Office Berlin" },
|
||||||
|
{ id: "office-sf", name: "Office San Francisco" },
|
||||||
|
{ id: "office-tokyo", name: "Office Tokyo" },
|
||||||
|
{ id: "vpn-eu", name: "VPN EU" },
|
||||||
|
{ id: "vpn-us", name: "VPN US" },
|
||||||
|
{ id: "vpn-asia", name: "VPN Asia" },
|
||||||
|
{ id: "test", name: "Test" },
|
||||||
|
{ id: "sandbox", name: "Sandbox" },
|
||||||
|
];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfileSelector = ({ email = "" }: Props) => {
|
||||||
|
const [profiles, setProfiles] = useState<Profile[]>(MOCK_PROFILES);
|
||||||
|
const [selectedId, setSelectedId] = useState<string>(MOCK_PROFILES[0].id);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [newOpen, setNewOpen] = useState(false);
|
||||||
|
|
||||||
|
const selected = profiles.find((p) => p.id === selectedId) ?? profiles[0];
|
||||||
|
|
||||||
|
const sorted = [...profiles].sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelect = (id: string) => {
|
||||||
|
setSelectedId(id);
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeregister = async (id: string) => {
|
||||||
|
const profile = profiles.find((p) => p.id === id);
|
||||||
|
if (!profile) return;
|
||||||
|
const result = await Dialogs.Warning({
|
||||||
|
Title: "Deregister Profile",
|
||||||
|
Message: `Are you sure you want to deregister "${profile.name}"? You will need to log in again to use it.`,
|
||||||
|
Buttons: [
|
||||||
|
{ Label: "Cancel", IsCancel: true },
|
||||||
|
{ Label: "Deregister", IsDefault: true },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (result !== "Deregister") return;
|
||||||
|
console.log("Deregister profile", id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
const profile = profiles.find((p) => p.id === id);
|
||||||
|
if (!profile) return;
|
||||||
|
const result = await Dialogs.Warning({
|
||||||
|
Title: "Delete Profile",
|
||||||
|
Message: `Are you sure you want to delete "${profile.name}"? This action cannot be undone.`,
|
||||||
|
Buttons: [
|
||||||
|
{ Label: "Cancel", IsCancel: true },
|
||||||
|
{ Label: "Delete", IsDefault: true },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (result !== "Delete") return;
|
||||||
|
setProfiles((prev) => prev.filter((p) => p.id !== id));
|
||||||
|
if (selectedId === id) {
|
||||||
|
const remaining = profiles.filter((p) => p.id !== id);
|
||||||
|
if (remaining.length > 0) setSelectedId(remaining[0].id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewProfile = () => {
|
||||||
|
setOpen(false);
|
||||||
|
setNewOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateProfile = (name: string) => {
|
||||||
|
const id = `${name.toLowerCase().replace(/\s+/g, "-")}-${Date.now()}`;
|
||||||
|
setProfiles((prev) => [...prev, { id, name }]);
|
||||||
|
setSelectedId(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initial = selected?.name.charAt(0).toUpperCase() ?? "?";
|
||||||
|
const initialColor = generateColorFromString(selected?.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popover.Root open={open} onOpenChange={setOpen}>
|
||||||
|
<Popover.Trigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={
|
||||||
|
"h-11 rounded-md text-nb-gray-300 flex items-center gap-1 text-xs hover:bg-nb-gray-930 data-[state=open]:bg-nb-gray-930 px-2 -mx-2 outline-none cursor-default transition-colors duration-150"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"h-7 w-7 flex items-center justify-center bg-nb-gray-900 rounded-md text-xs font-semibold"
|
||||||
|
}
|
||||||
|
style={{ color: initialColor }}
|
||||||
|
>
|
||||||
|
{initial}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"whitespace-nowrap flex flex-col ml-1 text-left",
|
||||||
|
email ? "mt-1" : "justify-center",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"leading-none text-nb-gray-200 font-semibold"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{selected?.name ?? "No profile"}
|
||||||
|
</span>
|
||||||
|
{email && (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"text-[0.73rem] font-normal text-nb-gray-300"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{email}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ChevronDown size={14} className={"ml-2 mr-2"} />
|
||||||
|
</button>
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Portal>
|
||||||
|
<Popover.Content
|
||||||
|
align="start"
|
||||||
|
sideOffset={6}
|
||||||
|
className={cn(
|
||||||
|
"w-72 rounded-md border border-nb-gray-920 bg-nb-gray-935 shadow-lg",
|
||||||
|
"p-1 z-50 origin-[var(--radix-popover-content-transform-origin)]",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
|
"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0",
|
||||||
|
"data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95",
|
||||||
|
"data-[side=bottom]:slide-in-from-top-1",
|
||||||
|
"data-[side=top]:slide-in-from-bottom-1",
|
||||||
|
"duration-150 ease-out",
|
||||||
|
)}
|
||||||
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<Command
|
||||||
|
loop
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col",
|
||||||
|
"[&_[cmdk-input-wrapper]]:flex [&_[cmdk-input-wrapper]]:items-center",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="px-1 pb-1">
|
||||||
|
<div className="group flex items-center gap-2 px-2 h-8">
|
||||||
|
<Search
|
||||||
|
size={12}
|
||||||
|
className="text-nb-gray-300 shrink-0"
|
||||||
|
/>
|
||||||
|
<Command.Input
|
||||||
|
autoFocus
|
||||||
|
placeholder="Search profile by name..."
|
||||||
|
className={cn(
|
||||||
|
"w-full bg-transparent text-xs text-nb-gray-200 placeholder:text-nb-gray-400",
|
||||||
|
"outline-none border-none",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ScrollArea.Root
|
||||||
|
type="auto"
|
||||||
|
className="overflow-hidden -mx-1"
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport className="max-h-64 px-1 pb-1">
|
||||||
|
<Command.List>
|
||||||
|
<Command.Empty>
|
||||||
|
<div className="flex flex-col items-center text-center px-4 pt-2 pb-3">
|
||||||
|
<h3 className="text-xs font-semibold text-nb-gray-200">
|
||||||
|
No Profiles Found
|
||||||
|
</h3>
|
||||||
|
<p className="text-[0.7rem] leading-snug text-nb-gray-400 mt-1 text-balance">
|
||||||
|
Try a different search term
|
||||||
|
or create a new profile.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Command.Empty>
|
||||||
|
|
||||||
|
{sorted.map((profile) => (
|
||||||
|
<ProfileRow
|
||||||
|
key={profile.id}
|
||||||
|
profile={profile}
|
||||||
|
selected={
|
||||||
|
profile.id === selectedId
|
||||||
|
}
|
||||||
|
onSelect={() =>
|
||||||
|
handleSelect(profile.id)
|
||||||
|
}
|
||||||
|
onDeregister={() =>
|
||||||
|
handleDeregister(profile.id)
|
||||||
|
}
|
||||||
|
onDelete={() =>
|
||||||
|
handleDelete(profile.id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Command.List>
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
orientation="vertical"
|
||||||
|
className={cn(
|
||||||
|
"flex select-none touch-none transition-colors",
|
||||||
|
"w-1.5 bg-transparent py-1",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb className="flex-1 rounded-full bg-nb-gray-800 hover:bg-nb-gray-700 relative" />
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
</ScrollArea.Root>
|
||||||
|
|
||||||
|
<div className="h-px bg-nb-gray-920 -mx-1 my-1" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleNewProfile}
|
||||||
|
className={cn(
|
||||||
|
"w-full flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-md cursor-default outline-none",
|
||||||
|
"text-netbird hover:bg-nb-gray-920",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"h-6 w-6 flex items-center justify-center rounded-md bg-nb-gray-900 shrink-0"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PlusCircle
|
||||||
|
size={12}
|
||||||
|
className="text-netbird"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-semibold">
|
||||||
|
New Profile
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Command>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Portal>
|
||||||
|
</Popover.Root>
|
||||||
|
<NewProfileDialog
|
||||||
|
open={newOpen}
|
||||||
|
onOpenChange={setNewOpen}
|
||||||
|
onCreate={handleCreateProfile}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProfileRowProps = {
|
||||||
|
profile: Profile;
|
||||||
|
selected: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
onDeregister: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProfileRow = ({
|
||||||
|
profile,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
|
onDeregister,
|
||||||
|
onDelete,
|
||||||
|
}: ProfileRowProps) => {
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const initial = profile.name.charAt(0).toUpperCase();
|
||||||
|
const initialColor = generateColorFromString(profile.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Command.Item
|
||||||
|
value={profile.name}
|
||||||
|
keywords={[profile.id]}
|
||||||
|
onSelect={() => onSelect()}
|
||||||
|
className={cn(
|
||||||
|
"group flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-md cursor-default outline-none",
|
||||||
|
"data-[selected=true]:bg-nb-gray-920",
|
||||||
|
selected && "bg-nb-gray-920",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-6 w-6 flex items-center justify-center rounded-md text-[0.65rem] font-semibold shrink-0 bg-nb-gray-900",
|
||||||
|
)}
|
||||||
|
style={{ color: initialColor }}
|
||||||
|
>
|
||||||
|
{initial}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"flex-1 truncate text-xs",
|
||||||
|
selected
|
||||||
|
? "text-nb-gray-200 font-semibold"
|
||||||
|
: "text-nb-gray-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{profile.name}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<DropdownMenu.Root
|
||||||
|
open={menuOpen}
|
||||||
|
onOpenChange={setMenuOpen}
|
||||||
|
modal={false}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Trigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
className={cn(
|
||||||
|
"h-6 w-6 flex items-center justify-center rounded text-nb-gray-400 cursor-default",
|
||||||
|
"hover:bg-nb-gray-900 hover:text-nb-gray-200 outline-none",
|
||||||
|
"data-[state=open]:bg-nb-gray-900 data-[state=open]:text-nb-gray-200",
|
||||||
|
)}
|
||||||
|
aria-label="More options"
|
||||||
|
>
|
||||||
|
<MoreVertical size={14} />
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
sideOffset={4}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
className={cn(
|
||||||
|
"w-44 rounded-md border border-nb-gray-920 bg-nb-gray-935 shadow-lg p-1 z-50",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onSelect={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onDeregister();
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-default outline-none",
|
||||||
|
"text-xs text-nb-gray-200 data-[highlighted]:bg-nb-gray-920",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<UserMinus
|
||||||
|
size={14}
|
||||||
|
className="text-nb-gray-300"
|
||||||
|
/>
|
||||||
|
<span>Deregister</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onSelect={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onDelete();
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-default outline-none",
|
||||||
|
"text-xs text-red-500 data-[highlighted]:bg-nb-gray-920",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Trash2 size={14} />
|
||||||
|
<span>Delete Profile</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</Command.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
30
client/ui-wails/frontend/src/components/SearchInput.tsx
Normal file
30
client/ui-wails/frontend/src/components/SearchInput.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { forwardRef, InputHTMLAttributes } from "react";
|
||||||
|
import { SearchIcon } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
type Props = InputHTMLAttributes<HTMLInputElement> & {
|
||||||
|
iconSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchInput = forwardRef<HTMLInputElement, Props>(
|
||||||
|
function SearchInput({ iconSize = 14, className, ...props }, ref) {
|
||||||
|
return (
|
||||||
|
<div className={"flex items-center gap-2 px-2 h-9"}>
|
||||||
|
<SearchIcon
|
||||||
|
size={iconSize}
|
||||||
|
className={"text-nb-gray-300 shrink-0"}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
type={"text"}
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
"w-full bg-transparent text-xs text-nb-gray-200 placeholder:text-nb-gray-400",
|
||||||
|
"outline-none border-none",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -13,6 +13,7 @@ html,
|
|||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
25
client/ui-wails/frontend/src/layouts/ConnectionStatus.tsx
Normal file
25
client/ui-wails/frontend/src/layouts/ConnectionStatus.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
ConnectionState,
|
||||||
|
NetBirdConnectToggle,
|
||||||
|
} from "@/components/NetBirdConnectToggle.tsx";
|
||||||
|
|
||||||
|
export const ConnectionStatus = () => {
|
||||||
|
return (
|
||||||
|
<div className={"flex flex-col items-center"}>
|
||||||
|
<NetBirdConnectToggle state={ConnectionState.Connected} />
|
||||||
|
<h1
|
||||||
|
className={
|
||||||
|
"text-base font-medium mt-8 text-nb-gray-200 tracking-wide"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Connected
|
||||||
|
</h1>
|
||||||
|
<p className={"font-mono text-xs text-nb-gray-300 mt-1"}>
|
||||||
|
peer-hostname.netbird.cloud
|
||||||
|
</p>
|
||||||
|
<p className={"font-mono text-xs text-nb-gray-300 mt-0.5"}>
|
||||||
|
192.168.0.1
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
12
client/ui-wails/frontend/src/layouts/Header.tsx
Normal file
12
client/ui-wails/frontend/src/layouts/Header.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ProfileSelector } from "@/components/ProfileSelector.tsx";
|
||||||
|
import { IconButton } from "@/components/IconButton.tsx";
|
||||||
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export const Header = () => {
|
||||||
|
return (
|
||||||
|
<div className={"w-full justify-between flex mb-12"}>
|
||||||
|
<ProfileSelector />
|
||||||
|
<IconButton icon={SettingsIcon} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
31
client/ui-wails/frontend/src/layouts/Navigation.tsx
Normal file
31
client/ui-wails/frontend/src/layouts/Navigation.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { NavItem } from "@/components/NavItem.tsx";
|
||||||
|
import {
|
||||||
|
Layers3Icon,
|
||||||
|
MonitorSmartphoneIcon,
|
||||||
|
SquareArrowUpRight,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export const Navigation = () => {
|
||||||
|
return (
|
||||||
|
<nav className={"w-full flex flex-col gap-1 mt-8"}>
|
||||||
|
<NavItem
|
||||||
|
icon={MonitorSmartphoneIcon}
|
||||||
|
title={"Peers"}
|
||||||
|
description={"13 of 16 Online"}
|
||||||
|
active
|
||||||
|
/>
|
||||||
|
<NavItem
|
||||||
|
icon={Layers3Icon}
|
||||||
|
title={"Resources"}
|
||||||
|
description={"13 of 16 Active"}
|
||||||
|
iconSize={14}
|
||||||
|
/>
|
||||||
|
<NavItem
|
||||||
|
icon={SquareArrowUpRight}
|
||||||
|
title={"Exit Node Berlin"}
|
||||||
|
description={"192.168..."}
|
||||||
|
iconSize={14}
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
17
client/ui-wails/frontend/src/lib/color.ts
Normal file
17
client/ui-wails/frontend/src/lib/color.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
|
||||||
|
export const generateColorFromString = (str?: string) => {
|
||||||
|
if (!str) return "#f68330";
|
||||||
|
if (str.includes("System")) return "#808080";
|
||||||
|
if (str.toLowerCase().startsWith("netbird")) return "#f68330";
|
||||||
|
let hash = 0;
|
||||||
|
str.split("").forEach((char) => {
|
||||||
|
hash = char.charCodeAt(0) + ((hash << 5) - hash);
|
||||||
|
});
|
||||||
|
let colour = "#";
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
|
colour += value.toString(16).padStart(2, "0");
|
||||||
|
}
|
||||||
|
return chroma(colour).saturate(2).luminance(0.4).hex();
|
||||||
|
};
|
||||||
53
client/ui-wails/frontend/src/modules/peers/PeerFilters.tsx
Normal file
53
client/ui-wails/frontend/src/modules/peers/PeerFilters.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
|
||||||
|
export type StatusFilter = "all" | "online" | "offline";
|
||||||
|
|
||||||
|
const FILTERS: { value: StatusFilter; label: string }[] = [
|
||||||
|
{ value: "all", label: "All" },
|
||||||
|
{ value: "online", label: "Online" },
|
||||||
|
{ value: "offline", label: "Offline" },
|
||||||
|
];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: StatusFilter;
|
||||||
|
onChange: (value: StatusFilter) => void;
|
||||||
|
counts: Record<StatusFilter, number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PeerFilters = ({ value, onChange, counts }: Props) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"flex w-full rounded-md border border-nb-gray-900 bg-nb-gray-940 p-0.5"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{FILTERS.map((f) => {
|
||||||
|
const active = value === f.value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={f.value}
|
||||||
|
type={"button"}
|
||||||
|
onClick={() => onChange(f.value)}
|
||||||
|
className={cn(
|
||||||
|
"flex-1 inline-flex items-center justify-center gap-1.5 rounded px-2.5 py-2 text-xs font-medium",
|
||||||
|
"transition-colors duration-150 cursor-default outline-none",
|
||||||
|
active
|
||||||
|
? "bg-nb-gray-800 text-nb-gray-100"
|
||||||
|
: "text-nb-gray-400 hover:text-nb-gray-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{f.label}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-[0.65rem] font-mono",
|
||||||
|
active ? "text-nb-gray-300" : "text-nb-gray-500",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{counts[f.value]}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
51
client/ui-wails/frontend/src/modules/peers/PeersList.tsx
Normal file
51
client/ui-wails/frontend/src/modules/peers/PeersList.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
import { Peer, PeerStatus } from "./types";
|
||||||
|
|
||||||
|
const DOT: Record<PeerStatus, string> = {
|
||||||
|
connected: "bg-green-400",
|
||||||
|
connecting: "bg-yellow-300 animate-pulse-slow",
|
||||||
|
disconnected: "bg-nb-gray-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PeersList = ({ data }: { data: Peer[] }) => {
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className={"py-12 text-center text-sm text-nb-gray-400"}>
|
||||||
|
No peers match the current filters.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={"flex flex-col"}>
|
||||||
|
{data.map((peer) => (
|
||||||
|
<li
|
||||||
|
key={peer.id}
|
||||||
|
className={"flex items-center gap-3 px-4 py-3 min-w-0"}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"h-2 w-2 rounded-full shrink-0",
|
||||||
|
DOT[peer.status],
|
||||||
|
)}
|
||||||
|
title={peer.status}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"text-[0.81rem] font-medium text-nb-gray-100 truncate"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{peer.fqdn}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"ml-auto text-xs font-mono text-nb-gray-400 shrink-0"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{peer.ip}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
73
client/ui-wails/frontend/src/modules/peers/PeersModule.tsx
Normal file
73
client/ui-wails/frontend/src/modules/peers/PeersModule.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
|
import { cn } from "@/lib/cn";
|
||||||
|
import { SearchInput } from "@/components/SearchInput";
|
||||||
|
import { mockPeers } from "./mockPeers";
|
||||||
|
import { PeerFilters, StatusFilter } from "./PeerFilters";
|
||||||
|
import { PeersList } from "./PeersList";
|
||||||
|
|
||||||
|
const isOnline = (status: string) => status === "connected";
|
||||||
|
|
||||||
|
export const PeersModule = () => {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
|
||||||
|
|
||||||
|
const counts = useMemo<Record<StatusFilter, number>>(() => {
|
||||||
|
const online = mockPeers.filter((p) => isOnline(p.status)).length;
|
||||||
|
return {
|
||||||
|
all: mockPeers.length,
|
||||||
|
online,
|
||||||
|
offline: mockPeers.length - online,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const filtered = useMemo(() => {
|
||||||
|
const q = search.trim().toLowerCase();
|
||||||
|
return mockPeers.filter((p) => {
|
||||||
|
if (statusFilter === "online" && !isOnline(p.status)) return false;
|
||||||
|
if (statusFilter === "offline" && isOnline(p.status)) return false;
|
||||||
|
if (q && !p.fqdn.toLowerCase().includes(q) && !p.ip.includes(q)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [search, statusFilter]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"flex flex-col w-full h-full min-h-0 pt-4"}>
|
||||||
|
<div className={"flex flex-col gap-3 px-4"}>
|
||||||
|
<SearchInput
|
||||||
|
placeholder={"Search by FQDN or IP…"}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
<PeerFilters
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={setStatusFilter}
|
||||||
|
counts={counts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollArea.Root
|
||||||
|
type={"auto"}
|
||||||
|
className={"flex-1 min-h-0 overflow-hidden mt-3"}
|
||||||
|
>
|
||||||
|
<ScrollArea.Viewport className={"h-full w-full"}>
|
||||||
|
<PeersList data={filtered} />
|
||||||
|
</ScrollArea.Viewport>
|
||||||
|
<ScrollArea.Scrollbar
|
||||||
|
orientation={"vertical"}
|
||||||
|
className={cn(
|
||||||
|
"flex select-none touch-none transition-colors",
|
||||||
|
"w-1.5 bg-transparent py-1",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ScrollArea.Thumb
|
||||||
|
className={
|
||||||
|
"flex-1 rounded-full bg-nb-gray-800 hover:bg-nb-gray-700 relative"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ScrollArea.Scrollbar>
|
||||||
|
</ScrollArea.Root>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
385
client/ui-wails/frontend/src/modules/peers/mockPeers.ts
Normal file
385
client/ui-wails/frontend/src/modules/peers/mockPeers.ts
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
import { Peer } from "./types";
|
||||||
|
|
||||||
|
const minutesAgo = (m: number) => new Date(Date.now() - m * 60 * 1000);
|
||||||
|
|
||||||
|
export const mockPeers: Peer[] = [
|
||||||
|
{
|
||||||
|
id: "p-001",
|
||||||
|
fqdn: "alice-laptop.netbird.cloud",
|
||||||
|
ip: "100.64.0.12",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(1),
|
||||||
|
latencyMs: 18,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 84,
|
||||||
|
bytesTx: 1024 * 1024 * 12,
|
||||||
|
endpointLocal: "192.168.1.24:51820",
|
||||||
|
endpointRemote: "203.0.113.45:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-002",
|
||||||
|
fqdn: "bob-desktop.netbird.cloud",
|
||||||
|
ip: "100.64.0.21",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(3),
|
||||||
|
latencyMs: 42,
|
||||||
|
relayed: true,
|
||||||
|
relayAddress: "rel.eu-central.netbird.io:443",
|
||||||
|
iceLocalCandidate: "relay",
|
||||||
|
iceRemoteCandidate: "relay",
|
||||||
|
bytesRx: 1024 * 380,
|
||||||
|
bytesTx: 1024 * 940,
|
||||||
|
endpointLocal: "10.0.0.8:51820",
|
||||||
|
endpointRemote: "198.51.100.7:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-003",
|
||||||
|
fqdn: "build-runner-01.netbird.cloud",
|
||||||
|
ip: "100.64.0.34",
|
||||||
|
status: "connecting",
|
||||||
|
lastHandshake: minutesAgo(15),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 0,
|
||||||
|
bytesTx: 0,
|
||||||
|
endpointLocal: "192.168.1.45:51820",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-004",
|
||||||
|
fqdn: "carol-phone.netbird.cloud",
|
||||||
|
ip: "100.64.0.55",
|
||||||
|
status: "disconnected",
|
||||||
|
lastHandshake: minutesAgo(620),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 5,
|
||||||
|
bytesTx: 1024 * 1024 * 2,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-005",
|
||||||
|
fqdn: "exit-berlin.netbird.cloud",
|
||||||
|
ip: "100.64.0.2",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.2),
|
||||||
|
latencyMs: 9,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 1024 * 2,
|
||||||
|
bytesTx: 1024 * 1024 * 512,
|
||||||
|
endpointLocal: "10.10.0.4:51820",
|
||||||
|
endpointRemote: "203.0.113.99:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-006",
|
||||||
|
fqdn: "db-replica-eu.netbird.cloud",
|
||||||
|
ip: "100.64.0.78",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(7),
|
||||||
|
latencyMs: 64,
|
||||||
|
relayed: true,
|
||||||
|
relayAddress: "rel.us-east.netbird.io:443",
|
||||||
|
iceLocalCandidate: "relay",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 240,
|
||||||
|
bytesTx: 1024 * 1024 * 90,
|
||||||
|
endpointLocal: "172.16.0.10:51820",
|
||||||
|
endpointRemote: "198.51.100.42:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-007",
|
||||||
|
fqdn: "dev-vm-mac.netbird.cloud",
|
||||||
|
ip: "100.64.0.91",
|
||||||
|
status: "disconnected",
|
||||||
|
lastHandshake: minutesAgo(2880),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 0,
|
||||||
|
bytesTx: 0,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-008",
|
||||||
|
fqdn: "ci-worker-03.netbird.cloud",
|
||||||
|
ip: "100.64.0.103",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.5),
|
||||||
|
latencyMs: 27,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "prflx",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 14,
|
||||||
|
bytesTx: 1024 * 1024 * 3,
|
||||||
|
endpointLocal: "192.168.50.7:51820",
|
||||||
|
endpointRemote: "203.0.113.61:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-009",
|
||||||
|
fqdn: "k8s-control-plane.netbird.cloud",
|
||||||
|
ip: "100.64.0.110",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(2),
|
||||||
|
latencyMs: 12,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 410,
|
||||||
|
bytesTx: 1024 * 1024 * 380,
|
||||||
|
endpointLocal: "10.0.1.10:51820",
|
||||||
|
endpointRemote: "10.0.1.11:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-010",
|
||||||
|
fqdn: "k8s-worker-01.netbird.cloud",
|
||||||
|
ip: "100.64.0.111",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(2),
|
||||||
|
latencyMs: 14,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 220,
|
||||||
|
bytesTx: 1024 * 1024 * 190,
|
||||||
|
endpointLocal: "10.0.1.20:51820",
|
||||||
|
endpointRemote: "10.0.1.21:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-011",
|
||||||
|
fqdn: "k8s-worker-02.netbird.cloud",
|
||||||
|
ip: "100.64.0.112",
|
||||||
|
status: "connecting",
|
||||||
|
lastHandshake: minutesAgo(8),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 0,
|
||||||
|
bytesTx: 0,
|
||||||
|
endpointLocal: "10.0.1.22:51820",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-012",
|
||||||
|
fqdn: "monitoring-prom.netbird.cloud",
|
||||||
|
ip: "100.64.0.130",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.3),
|
||||||
|
latencyMs: 22,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 56,
|
||||||
|
bytesTx: 1024 * 1024 * 18,
|
||||||
|
endpointLocal: "10.20.0.5:51820",
|
||||||
|
endpointRemote: "203.0.113.122:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-013",
|
||||||
|
fqdn: "grafana.netbird.cloud",
|
||||||
|
ip: "100.64.0.131",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.4),
|
||||||
|
latencyMs: 19,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 32,
|
||||||
|
bytesTx: 1024 * 1024 * 8,
|
||||||
|
endpointLocal: "10.20.0.6:51820",
|
||||||
|
endpointRemote: "203.0.113.123:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-014",
|
||||||
|
fqdn: "loki-log-aggregator.netbird.cloud",
|
||||||
|
ip: "100.64.0.132",
|
||||||
|
status: "disconnected",
|
||||||
|
lastHandshake: minutesAgo(45),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 12,
|
||||||
|
bytesTx: 1024 * 1024 * 4,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-015",
|
||||||
|
fqdn: "dave-laptop.netbird.cloud",
|
||||||
|
ip: "100.64.0.140",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(1),
|
||||||
|
latencyMs: 38,
|
||||||
|
relayed: true,
|
||||||
|
relayAddress: "rel.eu-west.netbird.io:443",
|
||||||
|
iceLocalCandidate: "relay",
|
||||||
|
iceRemoteCandidate: "relay",
|
||||||
|
bytesRx: 1024 * 720,
|
||||||
|
bytesTx: 1024 * 410,
|
||||||
|
endpointLocal: "192.168.43.21:51820",
|
||||||
|
endpointRemote: "198.51.100.88:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-016",
|
||||||
|
fqdn: "eve-iphone.netbird.cloud",
|
||||||
|
ip: "100.64.0.150",
|
||||||
|
status: "connecting",
|
||||||
|
lastHandshake: minutesAgo(20),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 0,
|
||||||
|
bytesTx: 0,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-017",
|
||||||
|
fqdn: "frank-windows.netbird.cloud",
|
||||||
|
ip: "100.64.0.155",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(4),
|
||||||
|
latencyMs: 76,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 6,
|
||||||
|
bytesTx: 1024 * 1024 * 2,
|
||||||
|
endpointLocal: "192.168.1.55:51820",
|
||||||
|
endpointRemote: "203.0.113.200:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-018",
|
||||||
|
fqdn: "exit-frankfurt.netbird.cloud",
|
||||||
|
ip: "100.64.0.3",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.1),
|
||||||
|
latencyMs: 6,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 1024 * 5,
|
||||||
|
bytesTx: 1024 * 1024 * 1024 * 1,
|
||||||
|
endpointLocal: "10.10.0.5:51820",
|
||||||
|
endpointRemote: "203.0.113.150:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-019",
|
||||||
|
fqdn: "exit-singapore.netbird.cloud",
|
||||||
|
ip: "100.64.0.4",
|
||||||
|
status: "disconnected",
|
||||||
|
lastHandshake: minutesAgo(180),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 880,
|
||||||
|
bytesTx: 1024 * 1024 * 220,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-020",
|
||||||
|
fqdn: "nas-home.netbird.cloud",
|
||||||
|
ip: "100.64.0.180",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.7),
|
||||||
|
latencyMs: 31,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 1024 * 3,
|
||||||
|
bytesTx: 1024 * 1024 * 480,
|
||||||
|
endpointLocal: "192.168.0.50:51820",
|
||||||
|
endpointRemote: "203.0.113.45:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-021",
|
||||||
|
fqdn: "raspberrypi-iot.netbird.cloud",
|
||||||
|
ip: "100.64.0.181",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(5),
|
||||||
|
latencyMs: 54,
|
||||||
|
relayed: true,
|
||||||
|
relayAddress: "rel.eu-central.netbird.io:443",
|
||||||
|
iceLocalCandidate: "relay",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 240,
|
||||||
|
bytesTx: 1024 * 110,
|
||||||
|
endpointLocal: "192.168.0.121:51820",
|
||||||
|
endpointRemote: "198.51.100.42:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-022",
|
||||||
|
fqdn: "staging-api.netbird.cloud",
|
||||||
|
ip: "100.64.0.200",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.2),
|
||||||
|
latencyMs: 16,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 92,
|
||||||
|
bytesTx: 1024 * 1024 * 140,
|
||||||
|
endpointLocal: "10.30.0.10:51820",
|
||||||
|
endpointRemote: "10.30.0.11:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-023",
|
||||||
|
fqdn: "prod-api-eu.netbird.cloud",
|
||||||
|
ip: "100.64.0.201",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.1),
|
||||||
|
latencyMs: 8,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 1024 * 1024 * 1024 * 12,
|
||||||
|
bytesTx: 1024 * 1024 * 1024 * 3,
|
||||||
|
endpointLocal: "10.40.0.10:51820",
|
||||||
|
endpointRemote: "10.40.0.11:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-024",
|
||||||
|
fqdn: "prod-api-us.netbird.cloud",
|
||||||
|
ip: "100.64.0.202",
|
||||||
|
status: "connected",
|
||||||
|
lastHandshake: minutesAgo(0.1),
|
||||||
|
latencyMs: 92,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "srflx",
|
||||||
|
iceRemoteCandidate: "srflx",
|
||||||
|
bytesRx: 1024 * 1024 * 1024 * 8,
|
||||||
|
bytesTx: 1024 * 1024 * 1024 * 2,
|
||||||
|
endpointLocal: "10.50.0.10:51820",
|
||||||
|
endpointRemote: "203.0.113.210:51820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "p-025",
|
||||||
|
fqdn: "old-jenkins.netbird.cloud",
|
||||||
|
ip: "100.64.0.220",
|
||||||
|
status: "disconnected",
|
||||||
|
lastHandshake: minutesAgo(8640),
|
||||||
|
latencyMs: 0,
|
||||||
|
relayed: false,
|
||||||
|
iceLocalCandidate: "host",
|
||||||
|
iceRemoteCandidate: "host",
|
||||||
|
bytesRx: 0,
|
||||||
|
bytesTx: 0,
|
||||||
|
endpointLocal: "—",
|
||||||
|
endpointRemote: "—",
|
||||||
|
},
|
||||||
|
];
|
||||||
20
client/ui-wails/frontend/src/modules/peers/types.ts
Normal file
20
client/ui-wails/frontend/src/modules/peers/types.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export type PeerStatus = "connected" | "connecting" | "disconnected";
|
||||||
|
|
||||||
|
export type IceCandidateType = "host" | "srflx" | "relay" | "prflx";
|
||||||
|
|
||||||
|
export type Peer = {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
ip: string;
|
||||||
|
status: PeerStatus;
|
||||||
|
lastHandshake: Date;
|
||||||
|
latencyMs: number;
|
||||||
|
relayed: boolean;
|
||||||
|
relayAddress?: string;
|
||||||
|
iceLocalCandidate: IceCandidateType;
|
||||||
|
iceRemoteCandidate: IceCandidateType;
|
||||||
|
bytesRx: number;
|
||||||
|
bytesTx: number;
|
||||||
|
endpointLocal: string;
|
||||||
|
endpointRemote: string;
|
||||||
|
};
|
||||||
@@ -1,76 +1,22 @@
|
|||||||
import {ConnectionState, NetBirdConnectToggle} from "@/components/NetBirdConnectToggle.tsx";
|
import { ConnectionStatus } from "@/layouts/ConnectionStatus.tsx";
|
||||||
import {
|
import { Header } from "@/layouts/Header.tsx";
|
||||||
BoltIcon,
|
import { Navigation } from "@/layouts/Navigation.tsx";
|
||||||
ChevronDown,
|
import { PeersModule } from "@/modules/peers/PeersModule.tsx";
|
||||||
CircleUserRound,
|
|
||||||
Layers3Icon,
|
|
||||||
MonitorSmartphoneIcon, SettingsIcon,
|
|
||||||
SquareArrowUpRight
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
};
|
};
|
||||||
export const Main = ({}: Props) => {
|
export const Main = ({}: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={"flex"}>
|
<div className={"flex h-full p-4 gap-4 min-h-0"}>
|
||||||
<div className={"flex flex-col max-w-xs w-full items-center mt-4"}>
|
<div className={"flex flex-col max-w-xs w-full shrink-0 items-center"}>
|
||||||
<div className={"w-full justify-between flex px-6 mb-14"}>
|
<Header />
|
||||||
<div>
|
<ConnectionStatus />
|
||||||
<div className={"h-8 rounded-md text-nb-gray-300 flex items-center gap-1.5 text-xs font-bold"}>
|
<Navigation />
|
||||||
<div className={"h-7 w-7 flex items-center justify-center bg-purple-900 text-white rounded-md"}>
|
|
||||||
D
|
|
||||||
</div>
|
|
||||||
<div className={"whitespace-nowrap flex flex-col mt-0.5 ml-1"}>
|
|
||||||
<span>Default</span>
|
|
||||||
<span className={"text-[0.67rem] font-normal"}>eduard@netbird.io</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ChevronDown size={14} className={""} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className={"h-8 rounded-md text-nb-gray-300 flex items-center gap-1.5 text-xs font-bold px-2.5"}>
|
|
||||||
<SettingsIcon size={18} className={""} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NetBirdConnectToggle state={ConnectionState.Connected} />
|
|
||||||
<h1 className={"text-base font-medium mt-8 text-nb-gray-200 tracking-wide"}>Connected</h1>
|
|
||||||
<p className={"font-mono text-xs text-nb-gray-300 mt-1"}>peer-hostname.netbird.cloud</p>
|
|
||||||
<p className={"font-mono text-xs text-nb-gray-300 mt-0.5"}>192.168.0.1</p>
|
|
||||||
<nav className={"w-full px-6 py-8 flex flex-col gap-1"}>
|
|
||||||
<div className={"flex items-center gap-3 bg-nb-gray-930 p-1.5 rounded-lg"}>
|
|
||||||
<div className={"h-9 w-9 bg-nb-gray-800 rounded-md flex items-center justify-center"}>
|
|
||||||
<MonitorSmartphoneIcon size={15} className={"text-nb-gray-200"} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 className={"font-medium text-[0.81rem]"}>Peers</h2>
|
|
||||||
<p className={"text-xs font-medium text-nb-gray-300"}>13 of 16 Online</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={"flex items-center gap-3 p-1.5 rounded-lg"}>
|
|
||||||
<div className={"h-9 w-9 bg-nb-gray-920 rounded-md flex items-center justify-center"}>
|
|
||||||
<Layers3Icon size={14} className={"text-nb-gray-400"} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 className={"font-medium text-[0.81rem]"}>Resources</h2>
|
|
||||||
<p className={"text-xs text-nb-gray-400 font-medium"}>13 of 16 Active</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={"flex items-center gap-3 p-1.5 rounded-lg"}>
|
|
||||||
<div className={"h-9 w-9 bg-nb-gray-920 rounded-md flex items-center justify-center"}>
|
|
||||||
<SquareArrowUpRight size={14} className={"text-nb-gray-400"} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 className={"font-medium text-[0.81rem]"}>Exit Node Berlin</h2>
|
|
||||||
<p className={"text-xs text-nb-gray-400 font-medium"}>192.168...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={"bg-nb-gray-930 w-full m-6 rounded-lg"}>
|
|
||||||
|
|
||||||
|
<div className={"flex-1 min-h-0 min-w-0 flex flex-col bg-nb-gray-935 rounded-xl border border-nb-gray-900"}>
|
||||||
|
<PeersModule />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,102 @@ const config: Config = {
|
|||||||
950: "#181a1d",
|
950: "#181a1d",
|
||||||
960: "#16181b",
|
960: "#16181b",
|
||||||
},
|
},
|
||||||
|
gray: {
|
||||||
|
50: "#F9FAFB",
|
||||||
|
100: "#F3F4F6",
|
||||||
|
200: "#E5E7EB",
|
||||||
|
300: "#D1D5DB",
|
||||||
|
400: "#9CA3AF",
|
||||||
|
500: "#6B7280",
|
||||||
|
600: "#4B5563",
|
||||||
|
700: "#374151",
|
||||||
|
800: "#1F2937",
|
||||||
|
900: "#111827",
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
50: "#FDF2F2",
|
||||||
|
100: "#FDE8E8",
|
||||||
|
200: "#FBD5D5",
|
||||||
|
300: "#F8B4B4",
|
||||||
|
400: "#F98080",
|
||||||
|
500: "#F05252",
|
||||||
|
600: "#E02424",
|
||||||
|
700: "#C81E1E",
|
||||||
|
800: "#9B1C1C",
|
||||||
|
900: "#771D1D",
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
50: "#FDFDEA",
|
||||||
|
100: "#FDF6B2",
|
||||||
|
200: "#FCE96A",
|
||||||
|
300: "#FACA15",
|
||||||
|
400: "#E3A008",
|
||||||
|
500: "#C27803",
|
||||||
|
600: "#9F580A",
|
||||||
|
700: "#8E4B10",
|
||||||
|
800: "#723B13",
|
||||||
|
900: "#633112",
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
50: "#F3FAF7",
|
||||||
|
100: "#DEF7EC",
|
||||||
|
200: "#BCF0DA",
|
||||||
|
300: "#84E1BC",
|
||||||
|
400: "#31C48D",
|
||||||
|
500: "#0E9F6E",
|
||||||
|
600: "#057A55",
|
||||||
|
700: "#046C4E",
|
||||||
|
800: "#03543F",
|
||||||
|
900: "#014737",
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
50: "#EBF5FF",
|
||||||
|
100: "#E1EFFE",
|
||||||
|
200: "#C3DDFD",
|
||||||
|
300: "#A4CAFE",
|
||||||
|
400: "#76A9FA",
|
||||||
|
500: "#3F83F8",
|
||||||
|
600: "#1C64F2",
|
||||||
|
700: "#1A56DB",
|
||||||
|
800: "#1E429F",
|
||||||
|
900: "#233876",
|
||||||
|
},
|
||||||
|
indigo: {
|
||||||
|
50: "#F0F5FF",
|
||||||
|
100: "#E5EDFF",
|
||||||
|
200: "#CDDBFE",
|
||||||
|
300: "#B4C6FC",
|
||||||
|
400: "#8DA2FB",
|
||||||
|
500: "#6875F5",
|
||||||
|
600: "#5850EC",
|
||||||
|
700: "#5145CD",
|
||||||
|
800: "#42389D",
|
||||||
|
900: "#362F78",
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
50: "#F6F5FF",
|
||||||
|
100: "#EDEBFE",
|
||||||
|
200: "#DCD7FE",
|
||||||
|
300: "#CABFFD",
|
||||||
|
400: "#AC94FA",
|
||||||
|
500: "#9061F9",
|
||||||
|
600: "#7E3AF2",
|
||||||
|
700: "#6C2BD9",
|
||||||
|
800: "#5521B5",
|
||||||
|
900: "#4A1D96",
|
||||||
|
},
|
||||||
|
pink: {
|
||||||
|
50: "#FDF2F8",
|
||||||
|
100: "#FCE8F3",
|
||||||
|
200: "#FAD1E8",
|
||||||
|
300: "#F8B4D9",
|
||||||
|
400: "#F17EB8",
|
||||||
|
500: "#E74694",
|
||||||
|
600: "#D61F69",
|
||||||
|
700: "#BF125D",
|
||||||
|
800: "#99154B",
|
||||||
|
900: "#751A3D",
|
||||||
|
},
|
||||||
netbird: {
|
netbird: {
|
||||||
DEFAULT: "#f68330",
|
DEFAULT: "#f68330",
|
||||||
50: "#fff6ed",
|
50: "#fff6ed",
|
||||||
|
|||||||
@@ -102,8 +102,10 @@ func main() {
|
|||||||
|
|
||||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
Title: "NetBird",
|
Title: "NetBird",
|
||||||
Width: 960,
|
Width: 880,
|
||||||
Height: 640,
|
Height: 620,
|
||||||
|
MinWidth: 880,
|
||||||
|
MinHeight: 620,
|
||||||
Hidden: false,
|
Hidden: false,
|
||||||
BackgroundColour: application.NewRGB(24, 26, 29),
|
BackgroundColour: application.NewRGB(24, 26, 29),
|
||||||
URL: "/",
|
URL: "/",
|
||||||
|
|||||||
Reference in New Issue
Block a user