Merge pull request #199 from gurucomputing:save-server-settings

Save-server-settings
This commit is contained in:
routerino 2025-03-23 11:54:10 +11:00 committed by GitHub
commit efe2aee09f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 474 additions and 244 deletions

257
package-lock.json generated
View file

@ -607,9 +607,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
"integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz",
"integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==",
"cpu": [
"arm"
],
@ -621,9 +621,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz",
"integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz",
"integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==",
"cpu": [
"arm64"
],
@ -635,9 +635,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz",
"integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz",
"integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==",
"cpu": [
"arm64"
],
@ -649,9 +649,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz",
"integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz",
"integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==",
"cpu": [
"x64"
],
@ -663,9 +663,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz",
"integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz",
"integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==",
"cpu": [
"arm64"
],
@ -677,9 +677,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz",
"integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz",
"integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==",
"cpu": [
"x64"
],
@ -691,9 +691,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz",
"integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz",
"integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==",
"cpu": [
"arm"
],
@ -705,9 +705,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz",
"integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz",
"integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==",
"cpu": [
"arm"
],
@ -719,9 +719,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz",
"integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz",
"integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==",
"cpu": [
"arm64"
],
@ -733,9 +733,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz",
"integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz",
"integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==",
"cpu": [
"arm64"
],
@ -747,9 +747,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz",
"integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz",
"integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==",
"cpu": [
"loong64"
],
@ -761,9 +761,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz",
"integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz",
"integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==",
"cpu": [
"ppc64"
],
@ -775,9 +775,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz",
"integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz",
"integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==",
"cpu": [
"riscv64"
],
@ -789,9 +789,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz",
"integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz",
"integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==",
"cpu": [
"s390x"
],
@ -803,9 +803,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz",
"integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz",
"integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==",
"cpu": [
"x64"
],
@ -817,9 +817,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz",
"integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz",
"integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==",
"cpu": [
"x64"
],
@ -831,9 +831,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz",
"integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz",
"integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==",
"cpu": [
"arm64"
],
@ -845,9 +845,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz",
"integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz",
"integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==",
"cpu": [
"ia32"
],
@ -859,9 +859,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz",
"integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz",
"integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==",
"cpu": [
"x64"
],
@ -896,25 +896,23 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.15.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.2.tgz",
"integrity": "sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==",
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.17.1.tgz",
"integrity": "sha512-CpoGSLqE2MCmcQwA2CWJvOsZ9vW+p/1H3itrFykdgajUNAEyQPbsaSn7fZb6PLHQwe+07njxje9ss0fjZoCAyw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"devalue": "^5.1.0",
"esm-env": "^1.2.1",
"esm-env": "^1.2.2",
"import-meta-resolve": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"sirv": "^3.0.0",
"tiny-glob": "^0.2.9"
"sirv": "^3.0.0"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@ -1243,9 +1241,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001692",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
"integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
"version": "1.0.30001699",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
"integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==",
"dev": true,
"funding": [
{
@ -1455,9 +1453,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.82",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.82.tgz",
"integrity": "sha512-Zq16uk1hfQhyGx5GpwPAYDwddJuSGhtRhgOA2mCxANYaDT79nAeGnaXogMGng4KqLaJUVnOnuL0+TDop9nLOiA==",
"version": "1.5.97",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.97.tgz",
"integrity": "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==",
"dev": true,
"license": "ISC"
},
@ -1574,9 +1572,9 @@
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
"integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -1584,9 +1582,9 @@
}
},
"node_modules/fdir": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
"integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -1701,20 +1699,6 @@
"node": ">=10.13.0"
}
},
"node_modules/globalyzer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
"dev": true,
"license": "MIT"
},
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true,
"license": "MIT"
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@ -2181,9 +2165,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
"integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
"dev": true,
"funding": [
{
@ -2345,9 +2329,9 @@
"license": "MIT"
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
"integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
"dev": true,
"license": "MIT",
"bin": {
@ -2372,9 +2356,9 @@
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.6.9",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz",
"integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==",
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
"integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
"dev": true,
"license": "MIT",
"engines": {
@ -2385,7 +2369,7 @@
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"@zackad/prettier-plugin-twig-melody": "*",
"@zackad/prettier-plugin-twig": "*",
"prettier": "^3.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
@ -2412,7 +2396,7 @@
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"@zackad/prettier-plugin-twig-melody": {
"@zackad/prettier-plugin-twig": {
"optional": true
},
"prettier-plugin-astro": {
@ -2528,9 +2512,9 @@
}
},
"node_modules/rollup": {
"version": "4.30.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz",
"integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==",
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz",
"integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2544,25 +2528,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.30.1",
"@rollup/rollup-android-arm64": "4.30.1",
"@rollup/rollup-darwin-arm64": "4.30.1",
"@rollup/rollup-darwin-x64": "4.30.1",
"@rollup/rollup-freebsd-arm64": "4.30.1",
"@rollup/rollup-freebsd-x64": "4.30.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.30.1",
"@rollup/rollup-linux-arm-musleabihf": "4.30.1",
"@rollup/rollup-linux-arm64-gnu": "4.30.1",
"@rollup/rollup-linux-arm64-musl": "4.30.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.30.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.30.1",
"@rollup/rollup-linux-riscv64-gnu": "4.30.1",
"@rollup/rollup-linux-s390x-gnu": "4.30.1",
"@rollup/rollup-linux-x64-gnu": "4.30.1",
"@rollup/rollup-linux-x64-musl": "4.30.1",
"@rollup/rollup-win32-arm64-msvc": "4.30.1",
"@rollup/rollup-win32-ia32-msvc": "4.30.1",
"@rollup/rollup-win32-x64-msvc": "4.30.1",
"@rollup/rollup-android-arm-eabi": "4.34.6",
"@rollup/rollup-android-arm64": "4.34.6",
"@rollup/rollup-darwin-arm64": "4.34.6",
"@rollup/rollup-darwin-x64": "4.34.6",
"@rollup/rollup-freebsd-arm64": "4.34.6",
"@rollup/rollup-freebsd-x64": "4.34.6",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.6",
"@rollup/rollup-linux-arm-musleabihf": "4.34.6",
"@rollup/rollup-linux-arm64-gnu": "4.34.6",
"@rollup/rollup-linux-arm64-musl": "4.34.6",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.6",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.6",
"@rollup/rollup-linux-riscv64-gnu": "4.34.6",
"@rollup/rollup-linux-s390x-gnu": "4.34.6",
"@rollup/rollup-linux-x64-gnu": "4.34.6",
"@rollup/rollup-linux-x64-musl": "4.34.6",
"@rollup/rollup-win32-arm64-msvc": "4.34.6",
"@rollup/rollup-win32-ia32-msvc": "4.34.6",
"@rollup/rollup-win32-x64-msvc": "4.34.6",
"fsevents": "~2.3.2"
}
},
@ -2822,9 +2806,9 @@
}
},
"node_modules/svelte": {
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.18.0.tgz",
"integrity": "sha512-/Eb81lB8bVUxQPmkPVNBYrU9cZ544+9hE91ZUUXTMf7eWcGW84N1hS3gvv/XsUNOWLLg3IicXP2qa8W3KpTUHA==",
"version": "5.19.9",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.9.tgz",
"integrity": "sha512-860s752/ZZxHIsii31ELkdKBOCeAuDsfb/AGUXJyQyzUVLRSt4oqEw/BV5+2+mNg8mbqmD3OK+vMvwWMPM6f8A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3010,17 +2994,6 @@
"node": ">=0.8"
}
},
"node_modules/tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
"integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"globalyzer": "0.1.0",
"globrex": "^0.1.2"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -3104,15 +3077,15 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
"integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz",
"integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.4.49",
"rollup": "^4.23.0"
"postcss": "^8.5.1",
"rollup": "^4.30.1"
},
"bin": {
"vite": "bin/vite.js"

View file

@ -0,0 +1,52 @@
<script module lang="ts">
import { SvelteMap } from 'svelte/reactivity';
export class PersistentAppSettingsObject {
daisyUITheme = ''; // for setting the UI theme. See https://daisyui.com/docs/themes/
headscaleAPIKey = ''; // sensitive, allows for administrative access to headscale
headscaleURL = ''; // url for headscale to use
debugLogging = false; // to turn on additional messages
public constructor(init?: Partial<PersistentAppSettingsObject>) {
Object.assign(this, init);
}
}
export class AppSettingsObject {
navbarTitle = ''; // for setting the title of the page
appLoaded = false; // for hiding the screen until hydration has completed
sidebarDrawerOpen = false; // for determining if the sidebar is open when on a small screen
toastAlerts = new SvelteMap<string, toastAlert>(); // for adding or removing alerts
apiTested = true; // used to hide the app if the api tests are failing
apiKeyList: APIKey[] = []; //list of apikeys retrieved from headscale API
apiKeyExpiration?: number = undefined; // number of days left until the key in use expires
public constructor(init?: Partial<AppSettingsObject>) {
Object.assign(this, init);
}
}
// alert used for populating toasts in the layout
export class toastAlert {
message = ''; //message to display
notificationType = 'alert'; //to style the toast
id = ''; //UUID generated to reference the toast
public constructor(init?: Partial<toastAlert>) {
Object.assign(this, init);
}
}
// retrieved as an array from headscale
export class APIKey {
id = ''; // unique identifier for headscale
prefix = ''; // beginning of key to match full string
expiration = ''; // when key expires, formatting as datetime
createdAt = ''; // date of creation
lastSeen = ''; // date last seen, seems to be always null?
public constructor(init?: Partial<APIKey>) {
Object.assign(this, init);
}
}
</script>

View file

@ -1,31 +0,0 @@
export class PersistentAppSettingsObject {
daisyUITheme = "" // for setting the UI theme. See https://daisyui.com/docs/themes/
headscaleAPIKey = "" // sensitive, allows for administrative access to headscale
headscaleURL = "" //url for headscale to use
public constructor(init?: Partial<PersistentAppSettingsObject>) {
Object.assign(this, init);
}
}
export class AppSettingsObject {
navbarTitle = "" // for setting the title of the page
appLoaded = false // for hiding the screen until hydration has completed
sidebarDrawerOpen = false // for determining if the sidebar is open when on a small screen
toastAlerts: toastAlert[] = [] // for adding or removing alerts
public constructor(init?: Partial<AppSettingsObject>) {
Object.assign(this, init);
}
}
// alert used for populating toasts in the layout
export class toastAlert {
message = "" //message to display
notificationType = "alert" //to style the toast
id = "" //UUID generated to reference the toast
public constructor(init?: Partial<toastAlert>) {
Object.assign(this, init);
}
}

View file

@ -4,7 +4,7 @@
// defines global state objects for the application
var appSettingsInitializer = new AppSettingsObject();
export let appSettings = $state(Object.assign({}, appSettingsInitializer));
// defines global state objects that get written to localStorage for persistence
var persistentAppSettingsInitializer = new PersistentAppSettingsObject();
export let persistentAppSettings = $state(Object.assign({}, persistentAppSettingsInitializer));

View file

@ -0,0 +1,15 @@
<script module lang="ts">
import { appSettings } from '../common/state.svelte';
import { toastAlert } from '../common/classes.svelte';
export function newToastAlert(message: string) {
let uuid = crypto.randomUUID();
appSettings.toastAlerts.set(
uuid,
new toastAlert({
id: uuid,
message: message
})
);
}
</script>

View file

@ -1,40 +1,56 @@
<!-- used to generate alerts or messages -->
<script lang="ts">
import { appSettings } from '$lib/components/common/state.svelte';
let { toastAlert } = $props();
import { toastAlert } from '../common/classes.svelte';
interface Props {
toast: toastAlert;
}
let progress = $state(100);
let { toast }: Props = $props();
// sets a timer and removes the alert after totalTime miliseconds
const intervalTime = 20; // 20ms
const totalTime = 4000; // 4 seconds
const decrement = 100 / (totalTime / intervalTime);
async function delayedClose(id: string) {
await new Promise((resolve) => setTimeout(resolve, 4000));
removeAlert(id);
}
delayedClose(toast.id);
const interval = setInterval(() => {
progress -= decrement;
if (progress <= 0) {
progress = 0;
removeAlert();
}
}, intervalTime);
function removeAlert() {
clearInterval(interval); // Stop the interval at 0
appSettings.toastAlerts = appSettings.toastAlerts.filter(function (returnObj) {
return returnObj.id !== toastAlert.id;
});
function removeAlert(id: string) {
appSettings.toastAlerts.delete(id);
}
</script>
<div class="alert flex justify-between min-w-60 text-wrap">
<div>{toastAlert.message}</div>
<div>
<button aria-label="close notification" class="mask mask-circle hover:bg-base-300" onclick={() => removeAlert()}>
<div class="radial-progress" style="--value:{progress}; --size:2rem; --thickness: 2px;" role="progressbar">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</button>
</div>
<div class="alert flex min-w-60 justify-between text-wrap">
<div>{toast.message}</div>
<div>
<button aria-label="close notification" class="mask mask-circle hover:bg-base-300" onclick={() => removeAlert(toast.id)}>
<div class="animate-value radial-progress" style="--value:var(--_value); --size:2rem; --thickness: 2px;" role="progressbar">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</button>
</div>
</div>
<style>
/* used to animate the timeout */
@property --_value {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.animate-value {
animation-name: grow;
animation-duration: 4s;
animation-direction: reverse;
}
@keyframes grow {
from {
--_value: 0;
}
to {
--_value: 100;
}
}
</style>

View file

@ -0,0 +1,141 @@
<script module lang="ts">
import { appSettings, persistentAppSettings } from '../common/state.svelte';
import { newToastAlert } from '../layout/toast-functions.svelte';
export async function getAPIKeys() {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'GET',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
appSettings.apiKeyList = (await response.json()).apiKeys;
appSettings.apiTested = true;
// determine the remaining time for the key we are currently using.
for (const key of appSettings.apiKeyList) {
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
getKeyRemainingTime(new Date(key.expiration));
}
}
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API test failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
function getKeyRemainingTime(expiration: Date) {
let currentTime = new Date();
// gets time difference in seconds
appSettings.apiKeyExpiration = Math.round((expiration.getTime() - currentTime.getTime()) / 1000 / 60 / 60 / 24);
if (appSettings.apiKeyExpiration < 30) {
newToastAlert(`${appSettings.apiKeyExpiration} days left before API Key expiry, consider rolling your key`);
}
}
export async function rotateAPIKey() {
for (const key of appSettings.apiKeyList) {
// select the current key being used in the app settings
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
let currentKey = key;
// generate a new expiration time 90 days in the future
let newExpiration = new Date();
newExpiration.setDate(newExpiration.getDate() + 90);
// create a new API key with the new expiration
let apiKey = await createNewAPIKey(newExpiration);
// The above should always return a value, let's check that
if (apiKey == undefined) {
throw new Error('expecting API key string, string was undefined');
}
// Set the new key as the current key in the persistent settings
persistentAppSettings.headscaleAPIKey = apiKey;
// Expire the previously current key
await expireAPIKey(currentKey.prefix);
// Get keys again to make sure it all worked
await getAPIKeys();
}
}
}
export async function createNewAPIKey(expireDate: Date) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'POST',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
expiration: expireDate.toISOString()
})
});
if (!response.ok) {
newToastAlert(`Creating new API Key Failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
let apiKey = '';
apiKey = (await response.json()).apiKey;
return apiKey;
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API Call Failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
export async function expireAPIKey(apiPrefix: string) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey/expire`, {
method: 'POST',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
prefix: apiPrefix
})
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API test failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
</script>

View file

@ -1,15 +1,73 @@
<script lang="ts">
import { appSettings, persistentAppSettings } from '$lib/components/common/state.svelte';
import { fly } from 'svelte/transition';
import { getAPIKeys, rotateAPIKey } from './server-settings-functions.svelte';
let apiSecretHidden = $state(true); // for hiding or showing the API key
let rotateButtonDisabled = $state(false);
function rotateAPIKeyClick() {
rotateButtonDisabled = true;
rotateAPIKey().then(() => {
rotateButtonDisabled = false;
// console.log(appSettings.apiKeyList);
});
}
</script>
<div class="form-control">
<h1 class="bold mb-4 text-xl text-primary">Server Settings</h1>
<label class="mb-2 block font-bold text-secondary" for="url"> Headscale URL </label>
<input class="input input-sm input-bordered w-full" type="url" placeholder="https://hs.yourdomain.com.au" />
<label for="url" class="label">
<span class="label-text-alt">URL for your headscale server instance (does not need populating if it's on the same subdomain)</span>
</label>
<label class="mb-2 block font-bold text-secondary" for="password"> Headscale API Key </label>
<input class="input input-sm input-bordered w-full" minlength="54" maxlength="54" type="password" placeholder="******************" />
<label for="url" class="label">
<span class="label-text-alt">Generate an API key for your headscale instance and place it here.</span>
</label>
<button class="btn btn-sm btn-secondary w-16">save</button>
<form id="server-settings" onsubmit={getAPIKeys}>
<label class="mb-2 block font-bold text-secondary" for="headscaleURL"> Headscale URL </label>
<input id="headscaleURL" bind:value={persistentAppSettings.headscaleURL} class="input input-sm input-bordered w-full" type="url" placeholder="https://hs.yourdomain.com.au" />
<label for="headscaleURL" class="label">
<span class="label-text-alt">URL for your headscale server instance (does not need populating if it's on the same subdomain)</span>
</label>
<div class="relative flex">
<label class="mb-2 block font-bold text-secondary" for="headscaleKey"> Headscale API Key </label>
{#if appSettings.apiKeyExpiration != undefined}
<button type="button" disabled={rotateButtonDisabled} onclick={rotateAPIKeyClick} class="tooltip" data-tip="{appSettings.apiKeyExpiration} days left. Click to rotate key" aria-label="check time left"
><svg data-slot="icon" fill="none" class="-my-3 ml-2 h-5 w-5 {appSettings.apiKeyExpiration >= 30 ? "stroke-success" : "stroke-error"}" stroke-width="2.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"></path>
</svg></button
>
{/if}
</div>
<div class="relative flex">
<input id="headscaleKey" bind:value={persistentAppSettings.headscaleAPIKey} class="input input-sm input-bordered w-full" minlength="40" maxlength="40" type={apiSecretHidden ? 'password' : 'text'} required placeholder="******************" />
<button
type="button"
class="ml-2"
onclick={() => {
apiSecretHidden = !apiSecretHidden;
}}
>{#if apiSecretHidden}
<!-- eye off -->
<svg xmlns="http://www.w3.org/2000/svg" class="my-1.5 h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /></svg>
{:else}
<!-- eye on -->
<svg xmlns="http://www.w3.org/2000/svg" class="my-1.5 h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{/if}
</button>
</div>
<label for="headscaleKey" class="label">
<span class="label-text-alt">Generate an API key for your headscale instance and place it here.</span>
</label>
</form>
<span
><button form="server-settings" class="btn btn-secondary btn-sm w-24"> Test API</button>
{#if appSettings.apiTested}
<svg in:fly|global={{ x: 10, duration: 600 }} xmlns="http://www.w3.org/2000/svg" class="inline h-6 w-6 fill-none stroke-success" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{:else}
<svg in:fly|global={{ x: 10, duration: 600 }} data-slot="icon" fill="none" stroke-width="1.5" class="inline h-6 w-6 fill-none stroke-error" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></path>
</svg>
{/if}
</span>
</div>

View file

@ -1,3 +0,0 @@
export function testAPIConnectivity() {
return true;
}

View file

@ -1,7 +1,8 @@
<script lang="ts">
import { persistentAppSettings } from '$lib/components/common/state.svelte';
import { persistentAppSettings } from '$lib/components/common/state.svelte';
import Svelecte from 'svelecte';
let themeOptions = ['hsui', 'light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset'];
</script>
<h1 class="bold mb-4 mt-4 text-xl text-primary">Application Theme</h1>
<Svelecte name="daisyUITheme" options={['hsui', 'light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset']} bind:value={persistentAppSettings.daisyUITheme} />
<Svelecte name="daisyUITheme" options={themeOptions} bind:value={persistentAppSettings.daisyUITheme} />

View file

@ -1,9 +1,10 @@
<script lang="ts">
import { toastAlert, type PersistentAppSettingsObject } from '$lib/components/common/classes.svelte';
import Toast from '$lib/components/layout/toast.svelte';
import { type PersistentAppSettingsObject } from '$lib/components/common/classes.svelte';
import { appSettings, persistentAppSettings } from '$lib/components/common/state.svelte';
import Navbar from '$lib/components/layout/navbar.svelte';
import Sidebar from '$lib/components/layout/sidebar.svelte';
import { appSettings, persistentAppSettings } from '$lib/components/common/state.svelte';
import Toast from '$lib/components/layout/toast.svelte';
import { getAPIKeys } from '$lib/components/settings/server-settings-functions.svelte';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import '../app.css';
@ -20,24 +21,24 @@
localStorage.setItem('persistentAppSettings', JSON.stringify(persistentAppSettings));
});
// populate any settings being passed through by url params
const urlParams = new URLSearchParams(window.location.search);
let headscaleApiKeyParam = urlParams.get('apikey');
let headscaleUrlParam = urlParams.get('url');
if (headscaleApiKeyParam) {
persistentAppSettings.headscaleAPIKey = headscaleApiKeyParam;
}
if (headscaleUrlParam) {
persistentAppSettings.headscaleURL = headscaleUrlParam;
}
// perform an initial API test
getAPIKeys();
// delay load until page is hydrated
appSettings.appLoaded = true;
// alert test
// appSettings.toastAlerts.push(
// new toastAlert({
// message: 'this is a test message',
// id: crypto.randomUUID()
// })
// );
// appSettings.toastAlerts.push(
// new toastAlert({
// message: 'this is a test message too and super long and long and long',
// id: crypto.randomUUID()
// })
// );
});
});
</script>
{#if appSettings.appLoaded}
@ -47,9 +48,9 @@
<div class="drawer-content flex flex-col">
<!-- toast content -->
<div class="toast toast-center toast-top z-40">
{#each appSettings.toastAlerts as toast}
{#each appSettings.toastAlerts.entries() as [toastID, toastObject]}
<div transition:fade={{ duration: 200 }}>
<Toast toastAlert={toast}></Toast>
<Toast toast={toastObject}></Toast>
</div>
{/each}
</div>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { appSettings } from '$lib/components/common/state.svelte';
import ServerSettings from '$lib/components/settings/server-settings.svelte';
import ThemeSettings from '$lib/components/settings/theme-settings.svelte';
import { appSettings } from '$lib/components/common/state.svelte';
appSettings.navbarTitle = 'Settings';
appSettings.sidebarDrawerOpen = false;

View file

@ -20,7 +20,10 @@ const config = {
paths: {
base: '/web'
}
},
compilerOptions: {
runes: true
}
};
export default config;
export default config;

View file

@ -9,6 +9,7 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"allowImportingTsExtensions": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias

View file

@ -2,5 +2,8 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
server: {
allowedHosts: true
}
});