diff --git a/.cursor/settings.json b/.cursor/settings.json
new file mode 100644
index 0000000..6595b05
--- /dev/null
+++ b/.cursor/settings.json
@@ -0,0 +1,7 @@
+{
+ "plugins": {
+ "svelte": {
+ "enabled": true
+ }
+ }
+}
diff --git a/README.md b/README.md
index 54f4d45..932b6f7 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ docker compose logs -f gateway
| **[docs/API.md](docs/API.md)** | Контракт Telemt Control API (`/v1/…`) |
| **[docs/AGGREGATE.md](docs/AGGREGATE.md)** | Агрегирующие эндпоинты шлюза (`/api/agg/…`), CORS, кэш |
| **[docs/AGGREGATE_OPENAPI.yaml](docs/AGGREGATE_OPENAPI.yaml)** | OpenAPI 3 черновик для `/api/agg/*` (генерация типов для UI) |
+| **[web/README.md](web/README.md)** | Web UI (SvelteKit + shadcn-svelte): установка, `PUBLIC_TELEMT_GATEWAY_URL`, Docker |
| **[docs/GEOIP.md](docs/GEOIP.md)** | GeoLite2 City (страна/город) и опционально ASN (номер AS, организация) для IP в `unique-ips` |
## Сборка и тесты без Docker
diff --git a/config.compose.yaml b/config.compose.yaml
index 3b5f692..d9b2550 100644
--- a/config.compose.yaml
+++ b/config.compose.yaml
@@ -4,6 +4,10 @@ listen: ":8080"
allow_all: true
whitelist_cidrs: []
trusted_proxies: []
+# UI в Docker (сервис web): браузер на другом origin — нужен CORS
+cors_allowed_origins:
+ - "http://127.0.0.1:4173"
+ - "http://localhost:4173"
servers:
- alias: main_srv
base_url: http://host.docker.internal:9091
diff --git a/docker-compose.yml b/docker-compose.yml
index e345907..f7879ef 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,13 @@
services:
+ web:
+ build:
+ context: ./web
+ dockerfile: Dockerfile
+ args:
+ # URL шлюза в браузере (встраивается при сборке)
+ PUBLIC_TELEMT_GATEWAY_URL: http://127.0.0.1:8080
+ ports:
+ - "4173:4173"
gateway:
image: git.shts.su/denozord/telemt-api:latest
# Локальная сборка вместо pull: укажите build: . и image: telemt-api-gateway:local
diff --git a/internal/server/gateway.go b/internal/server/gateway.go
index e483902..502f78b 100644
--- a/internal/server/gateway.go
+++ b/internal/server/gateway.go
@@ -98,7 +98,7 @@ func (g *Gateway) withCORS(next http.Handler) http.Handler {
ok, allowOrigin := corsMatch(g.corsAllowed, origin)
if ok {
w.Header().Set("Access-Control-Allow-Origin", allowOrigin)
- w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id")
w.Header().Set("Access-Control-Max-Age", "86400")
}
diff --git a/web/.dockerignore b/web/.dockerignore
new file mode 100644
index 0000000..4860c11
--- /dev/null
+++ b/web/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+.svelte-kit
+build
+.env
+*.md
diff --git a/web/.env.example b/web/.env.example
new file mode 100644
index 0000000..0ac7de2
--- /dev/null
+++ b/web/.env.example
@@ -0,0 +1,2 @@
+# Базовый URL шлюза telemt-api (без завершающего слэша)
+PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080
diff --git a/web/.gitignore b/web/.gitignore
new file mode 100644
index 0000000..3b462cb
--- /dev/null
+++ b/web/.gitignore
@@ -0,0 +1,23 @@
+node_modules
+
+# Output
+.output
+.vercel
+.netlify
+.wrangler
+/.svelte-kit
+/build
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
diff --git a/web/.npmrc b/web/.npmrc
new file mode 100644
index 0000000..b6f27f1
--- /dev/null
+++ b/web/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/web/Dockerfile b/web/Dockerfile
new file mode 100644
index 0000000..960333e
--- /dev/null
+++ b/web/Dockerfile
@@ -0,0 +1,16 @@
+# Статическая сборка SvelteKit и раздача через serve
+FROM node:22-alpine AS build
+WORKDIR /app
+ARG PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080
+ENV PUBLIC_TELEMT_GATEWAY_URL=$PUBLIC_TELEMT_GATEWAY_URL
+COPY package.json package-lock.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM node:22-alpine
+RUN npm install -g serve@14
+WORKDIR /srv
+COPY --from=build /app/build ./build
+EXPOSE 4173
+CMD ["serve", "-s", "build", "-l", "4173"]
diff --git a/web/README.md b/web/README.md
new file mode 100644
index 0000000..b863184
--- /dev/null
+++ b/web/README.md
@@ -0,0 +1,56 @@
+# Telemt Panel (Web UI)
+
+SvelteKit + shadcn-svelte: обзор флота (`/api/agg/*`), пользователи и IP, панель по каждой ноде через прокси шлюза (`/api/{alias}/…`).
+
+## Переменные окружения
+
+| Переменная | Описание |
+| --- | --- |
+| `PUBLIC_TELEMT_GATEWAY_URL` | Базовый URL шлюза **telemt-api** без завершающего `/` (например `http://127.0.0.1:8080`). Встраивается в клиент при сборке. |
+
+Скопируйте [.env.example](.env.example) в `.env` и при необходимости измените URL.
+
+## Разработка
+
+```powershell
+cd web
+npm install
+npm run dev
+```
+
+Откройте в браузере адрес Vite (по умолчанию `http://localhost:5173`). В конфиге шлюза укажите CORS, например:
+
+```yaml
+cors_allowed_origins:
+ - "http://localhost:5173"
+```
+
+## Сборка
+
+```powershell
+npm run build
+npm run preview # проверка статики
+```
+
+## Типы из OpenAPI агрегатов
+
+После изменения [../docs/AGGREGATE_OPENAPI.yaml](../docs/AGGREGATE_OPENAPI.yaml):
+
+```powershell
+npm run gen:api
+```
+
+## Docker
+
+Сборка образа из каталога `web/`:
+
+```powershell
+docker build -t telemt-web:local --build-arg PUBLIC_TELEMT_GATEWAY_URL=http://127.0.0.1:8080 .
+```
+
+В [docker-compose.yml](../docker-compose.yml) сервис `web` отдаёт UI на порту **4173**. URL шлюза в образе задаётся **build-arg** (см. compose). Браузер на хосте обращается к шлюзу по `http://127.0.0.1:8080` — при другом адресе пересоберите образ с нужным `PUBLIC_TELEMT_GATEWAY_URL`.
+
+## Ограничения
+
+- Секреты upstream к Telemt задаются на шлюзе (`authorization_env`), не в браузере.
+- Mutating CORS: шлюз должен разрешать `POST`, `PATCH`, `DELETE` в preflight (в текущей версии репозитория заголовок `Access-Control-Allow-Methods` это учитывает).
diff --git a/web/components.json b/web/components.json
new file mode 100644
index 0000000..e4df3ca
--- /dev/null
+++ b/web/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://shadcn-svelte.com/schema.json",
+ "tailwind": {
+ "css": "src/routes/layout.css",
+ "baseColor": "zinc"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/utils",
+ "ui": "$lib/components/ui",
+ "hooks": "$lib/hooks",
+ "lib": "$lib"
+ },
+ "typescript": true,
+ "registry": "https://shadcn-svelte.com/registry",
+ "iconLibrary": "lucide"
+}
diff --git a/web/package-lock.json b/web/package-lock.json
new file mode 100644
index 0000000..ef752f8
--- /dev/null
+++ b/web/package-lock.json
@@ -0,0 +1,2701 @@
+{
+ "name": "web",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "web",
+ "version": "0.0.1",
+ "dependencies": {
+ "bits-ui": "^2.16.4",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^3.5.0",
+ "tailwind-variants": "^3.2.2"
+ },
+ "devDependencies": {
+ "@internationalized/date": "^3.12.0",
+ "@lucide/svelte": "^1.7.0",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.50.2",
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
+ "@tailwindcss/vite": "^4.1.18",
+ "openapi-typescript": "^7.13.0",
+ "svelte": "^5.54.0",
+ "svelte-check": "^4.4.2",
+ "tailwindcss": "^4.1.18",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "^5.9.3",
+ "vite": "^7.3.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
+ "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
+ "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
+ "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
+ "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
+ "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
+ "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
+ "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
+ "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
+ "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
+ "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
+ "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
+ "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
+ "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
+ "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
+ "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
+ "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
+ "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
+ "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
+ "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
+ "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
+ "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz",
+ "integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@lucide/svelte": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.7.0.tgz",
+ "integrity": "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "svelte": "^5"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js-replace": "^1.0.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/config": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
+ "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.34.11",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.11.tgz",
+ "integrity": "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/ajv": "8.11.2",
+ "@redocly/config": "0.22.0",
+ "colorette": "1.4.0",
+ "https-proxy-agent": "7.0.6",
+ "js-levenshtein": "1.1.6",
+ "js-yaml": "4.1.1",
+ "minimatch": "5.1.9",
+ "pluralize": "8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=18.17.0",
+ "npm": ">=9.5.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
+ "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz",
+ "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz",
+ "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz",
+ "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz",
+ "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz",
+ "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz",
+ "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz",
+ "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz",
+ "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz",
+ "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz",
+ "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz",
+ "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz",
+ "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz",
+ "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz",
+ "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz",
+ "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz",
+ "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz",
+ "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz",
+ "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz",
+ "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz",
+ "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz",
+ "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@sveltejs/acorn-typescript": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
+ "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^8.9.0"
+ }
+ },
+ "node_modules/@sveltejs/adapter-static": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz",
+ "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@sveltejs/kit": "^2.0.0"
+ }
+ },
+ "node_modules/@sveltejs/kit": {
+ "version": "2.55.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz",
+ "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/cookie": "^0.6.0",
+ "acorn": "^8.14.1",
+ "cookie": "^0.6.0",
+ "devalue": "^5.6.4",
+ "esm-env": "^1.2.2",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.5",
+ "mrmime": "^2.0.0",
+ "set-cookie-parser": "^3.0.0",
+ "sirv": "^3.0.0"
+ },
+ "bin": {
+ "svelte-kit": "svelte-kit.js"
+ },
+ "engines": {
+ "node": ">=18.13"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "6.2.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz",
+ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
+ "deepmerge": "^4.3.1",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.0",
+ "vitefu": "^1.1.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz",
+ "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "obug": "^2.1.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0",
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.20",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.20.tgz",
+ "integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
+ "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
+ "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-x64": "4.2.2",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.2",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
+ "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
+ "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
+ "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
+ "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
+ "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
+ "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
+ "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
+ "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
+ "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
+ "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
+ "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
+ "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
+ "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.2.2",
+ "@tailwindcss/oxide": "4.2.2",
+ "tailwindcss": "4.2.2"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
+ "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
+ "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bits-ui": {
+ "version": "2.16.4",
+ "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.16.4.tgz",
+ "integrity": "sha512-76dKbW0DGplG7qnVM3kljE2CmCXqh9tj9OfVo0DvC/ZGRkZNJ/auU/aQnMKBxDnSqRvsz2mZBccBkUKyF4/SoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.1",
+ "@floating-ui/dom": "^1.7.1",
+ "esm-env": "^1.1.2",
+ "runed": "^0.35.1",
+ "svelte-toolbelt": "^0.10.6",
+ "tabbable": "^6.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/huntabyte"
+ },
+ "peerDependencies": {
+ "@internationalized/date": "^3.8.1",
+ "svelte": "^5.33.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
+ "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/devalue": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
+ "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
+ "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.4",
+ "@esbuild/android-arm": "0.27.4",
+ "@esbuild/android-arm64": "0.27.4",
+ "@esbuild/android-x64": "0.27.4",
+ "@esbuild/darwin-arm64": "0.27.4",
+ "@esbuild/darwin-x64": "0.27.4",
+ "@esbuild/freebsd-arm64": "0.27.4",
+ "@esbuild/freebsd-x64": "0.27.4",
+ "@esbuild/linux-arm": "0.27.4",
+ "@esbuild/linux-arm64": "0.27.4",
+ "@esbuild/linux-ia32": "0.27.4",
+ "@esbuild/linux-loong64": "0.27.4",
+ "@esbuild/linux-mips64el": "0.27.4",
+ "@esbuild/linux-ppc64": "0.27.4",
+ "@esbuild/linux-riscv64": "0.27.4",
+ "@esbuild/linux-s390x": "0.27.4",
+ "@esbuild/linux-x64": "0.27.4",
+ "@esbuild/netbsd-arm64": "0.27.4",
+ "@esbuild/netbsd-x64": "0.27.4",
+ "@esbuild/openbsd-arm64": "0.27.4",
+ "@esbuild/openbsd-x64": "0.27.4",
+ "@esbuild/openharmony-arm64": "0.27.4",
+ "@esbuild/sunos-x64": "0.27.4",
+ "@esbuild/win32-arm64": "0.27.4",
+ "@esbuild/win32-ia32": "0.27.4",
+ "@esbuild/win32-x64": "0.27.4"
+ }
+ },
+ "node_modules/esm-env": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+ "license": "MIT"
+ },
+ "node_modules/esrap": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
+ "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@typescript-eslint/types": "^8.2.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/index-to-position": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
+ "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/is-reference": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "devOptional": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "license": "MIT"
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "license": "MIT",
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "devOptional": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/openapi-typescript": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
+ "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/openapi-core": "^1.34.6",
+ "ansi-colors": "^4.1.3",
+ "change-case": "^5.4.4",
+ "parse-json": "^8.3.0",
+ "supports-color": "^10.2.2",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "openapi-typescript": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.x"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
+ "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "index-to-position": "^1.1.0",
+ "type-fest": "^4.39.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
+ "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.0",
+ "@rollup/rollup-android-arm64": "4.60.0",
+ "@rollup/rollup-darwin-arm64": "4.60.0",
+ "@rollup/rollup-darwin-x64": "4.60.0",
+ "@rollup/rollup-freebsd-arm64": "4.60.0",
+ "@rollup/rollup-freebsd-x64": "4.60.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.0",
+ "@rollup/rollup-linux-arm64-musl": "4.60.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.0",
+ "@rollup/rollup-linux-loong64-musl": "4.60.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-musl": "4.60.0",
+ "@rollup/rollup-openbsd-x64": "4.60.0",
+ "@rollup/rollup-openharmony-arm64": "4.60.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.0",
+ "@rollup/rollup-win32-x64-gnu": "4.60.0",
+ "@rollup/rollup-win32-x64-msvc": "4.60.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/runed": {
+ "version": "0.35.1",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz",
+ "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3",
+ "esm-env": "^1.0.0",
+ "lz-string": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@sveltejs/kit": "^2.21.0",
+ "svelte": "^5.7.0"
+ },
+ "peerDependenciesMeta": {
+ "@sveltejs/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz",
+ "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "5.55.1",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.1.tgz",
+ "integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/estree": "^1.0.5",
+ "@types/trusted-types": "^2.0.7",
+ "acorn": "^8.12.1",
+ "aria-query": "5.3.1",
+ "axobject-query": "^4.1.0",
+ "clsx": "^2.1.1",
+ "devalue": "^5.6.4",
+ "esm-env": "^1.2.1",
+ "esrap": "^2.2.4",
+ "is-reference": "^3.0.3",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.11",
+ "zimmerframe": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.5.tgz",
+ "integrity": "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "chokidar": "^4.0.1",
+ "fdir": "^6.2.0",
+ "picocolors": "^1.0.0",
+ "sade": "^1.7.4"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/svelte-toolbelt": {
+ "version": "0.10.6",
+ "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz",
+ "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte"
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "runed": "^0.35.1",
+ "style-to-object": "^1.0.8"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8.7.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.30.2"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
+ "license": "MIT"
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwind-variants": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz",
+ "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.x",
+ "pnpm": ">=7.x"
+ },
+ "peerDependencies": {
+ "tailwind-merge": ">=3.0.0",
+ "tailwindcss": "*"
+ },
+ "peerDependenciesMeta": {
+ "tailwind-merge": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uri-js-replace": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
+ "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz",
+ "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==",
+ "devOptional": true,
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/zimmerframe": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/web/package.json b/web/package.json
new file mode 100644
index 0000000..c99807a
--- /dev/null
+++ b/web/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "web",
+ "private": true,
+ "version": "0.0.1",
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview",
+ "prepare": "svelte-kit sync || echo ''",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "gen:api": "openapi-typescript ../docs/AGGREGATE_OPENAPI.yaml -o src/lib/api/aggregate.gen.ts"
+ },
+ "devDependencies": {
+ "@internationalized/date": "^3.12.0",
+ "@lucide/svelte": "^1.7.0",
+ "@sveltejs/adapter-static": "^3.0.10",
+ "@sveltejs/kit": "^2.50.2",
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
+ "@tailwindcss/vite": "^4.1.18",
+ "openapi-typescript": "^7.13.0",
+ "svelte": "^5.54.0",
+ "svelte-check": "^4.4.2",
+ "tailwindcss": "^4.1.18",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "^5.9.3",
+ "vite": "^7.3.1"
+ },
+ "dependencies": {
+ "bits-ui": "^2.16.4",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^3.5.0",
+ "tailwind-variants": "^3.2.2"
+ }
+}
diff --git a/web/src/app.d.ts b/web/src/app.d.ts
new file mode 100644
index 0000000..da08e6d
--- /dev/null
+++ b/web/src/app.d.ts
@@ -0,0 +1,13 @@
+// See https://svelte.dev/docs/kit/types#app.d.ts
+// for information about these interfaces
+declare global {
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
+}
+
+export {};
diff --git a/web/src/app.html b/web/src/app.html
new file mode 100644
index 0000000..3f35530
--- /dev/null
+++ b/web/src/app.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/web/src/lib/api/aggregate.gen.ts b/web/src/lib/api/aggregate.gen.ts
new file mode 100644
index 0000000..efcef76
--- /dev/null
+++ b/web/src/lib/api/aggregate.gen.ts
@@ -0,0 +1,401 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/api/agg/summary": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Сводка флота и топы пользователей */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ top_n?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeSummary"];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/agg/traffic": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Трафик по пользователям и серверам */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeTrafficRows"];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/agg/unique-ips": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Уникальные IP с привязкой к серверам (и опционально GeoIP) */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ /** @description false — не обогащать GeoIP */
+ geo?: "false" | "true";
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeUniqueIPs"];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/agg/users": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Список пользователей с merge по серверам */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ include_links?: "true" | "false";
+ min_total_megabytes?: number;
+ min_total_octets?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeUsersRows"];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/agg/user/{username}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Один пользователь (тот же объект, что в users[]) */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ include_links?: "true" | "false";
+ };
+ header?: never;
+ path: {
+ username: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeUsersRow"];
+ };
+ };
+ /** @description Пользователь не найден ни на одном upstream */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/agg/fleet-status": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Health + system/info по всем выбранным серверам */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Список алиасов через запятую */
+ aliases?: components["parameters"]["aliases"];
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AggEnvelopeFleetStatus"];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record;
+export interface components {
+ schemas: {
+ AggSuccessBase: {
+ /** @enum {boolean} */
+ ok: true;
+ /** Format: date-time */
+ generated_at: string;
+ /** @description true если часть upstream недоступна */
+ partial?: boolean;
+ };
+ AggEnvelopeSummary: components["schemas"]["AggSuccessBase"] & {
+ /** @description SummaryData (см. реализацию / доку AGGREGATE.md) */
+ data?: Record;
+ };
+ AggEnvelopeTrafficRows: components["schemas"]["AggSuccessBase"] & {
+ data?: components["schemas"]["TrafficRow"][];
+ };
+ AggEnvelopeUniqueIPs: components["schemas"]["AggSuccessBase"] & {
+ data?: components["schemas"]["UniqueIPsRow"][];
+ };
+ AggEnvelopeUsersRows: components["schemas"]["AggSuccessBase"] & {
+ data?: components["schemas"]["UsersRow"][];
+ };
+ AggEnvelopeUsersRow: components["schemas"]["AggSuccessBase"] & {
+ data?: components["schemas"]["UsersRow"];
+ };
+ AggEnvelopeFleetStatus: components["schemas"]["AggSuccessBase"] & {
+ data?: components["schemas"]["FleetStatusData"];
+ };
+ TrafficRow: {
+ username?: string;
+ servers?: {
+ [key: string]: components["schemas"]["TrafficServerStats"];
+ };
+ };
+ TrafficServerStats: {
+ /** Format: float */
+ total_megabytes?: number;
+ /** Format: int64 */
+ current_connections?: number;
+ revision?: string;
+ };
+ UniqueIPsRow: {
+ username?: string;
+ ips?: components["schemas"]["IPAssignments"][];
+ };
+ IPAssignments: {
+ ip?: string;
+ active_on_servers?: string[];
+ recent_on_servers?: string[];
+ primary_server?: string | null;
+ country_code?: string | null;
+ country_name?: string | null;
+ city_name?: string | null;
+ /** Format: int64 */
+ asn?: number | null;
+ as_organization?: string | null;
+ };
+ UserLinks: {
+ classic?: string[];
+ secure?: string[];
+ tls?: string[];
+ };
+ UsersRow: {
+ username?: string;
+ /** Format: float */
+ total_megabytes?: number;
+ by_server?: {
+ [key: string]: components["schemas"]["TrafficServerStats"];
+ };
+ links?: components["schemas"]["UserLinks"];
+ /** Format: int64 */
+ active_unique_ips?: number;
+ /** Format: int64 */
+ recent_unique_ips?: number;
+ user_ad_tag?: string | null;
+ /** Format: int64 */
+ max_tcp_conns?: number | null;
+ expiration_rfc3339?: string | null;
+ /** Format: int64 */
+ data_quota_bytes?: number | null;
+ /** Format: int64 */
+ max_unique_ips?: number | null;
+ };
+ HealthData: {
+ status?: string;
+ read_only?: boolean;
+ };
+ SystemInfoData: {
+ version?: string;
+ target_arch?: string;
+ target_os?: string;
+ build_profile?: string;
+ git_commit?: string | null;
+ build_time_utc?: string | null;
+ rustc_version?: string | null;
+ /** Format: int64 */
+ process_started_at_epoch_secs?: number;
+ /** Format: float */
+ uptime_seconds?: number;
+ config_path?: string;
+ config_hash?: string;
+ /** Format: int64 */
+ config_reload_count?: number;
+ /** Format: int64 */
+ last_config_reload_epoch_secs?: number | null;
+ };
+ FleetServerStatus: {
+ alias?: string;
+ ok?: boolean;
+ health_ok?: boolean;
+ health_http_status?: number;
+ /** Format: int64 */
+ health_latency_ms?: number;
+ health_error?: string;
+ health_revision?: string;
+ health?: components["schemas"]["HealthData"];
+ system_info_ok?: boolean;
+ system_info_http_status?: number;
+ /** Format: int64 */
+ system_info_latency_ms?: number;
+ system_info_error?: string;
+ system_info_revision?: string;
+ system_info?: components["schemas"]["SystemInfoData"];
+ };
+ FleetStatusData: {
+ servers?: components["schemas"]["FleetServerStatus"][];
+ servers_total?: number;
+ servers_all_ok?: number;
+ servers_failed?: number;
+ };
+ };
+ responses: never;
+ parameters: {
+ /** @description Список алиасов через запятую */
+ aliases: string;
+ };
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record;
+export type operations = Record;
diff --git a/web/src/lib/api/client.ts b/web/src/lib/api/client.ts
new file mode 100644
index 0000000..8c4d4b4
--- /dev/null
+++ b/web/src/lib/api/client.ts
@@ -0,0 +1,235 @@
+import { PUBLIC_TELEMT_GATEWAY_URL } from '$env/static/public';
+import type { components } from './aggregate.gen.js';
+import type { SummaryData } from './summary-types.js';
+import type {
+ StatsSummaryData,
+ TelemtErrorBody,
+ TelemtSuccess,
+ UserInfo
+} from './telemt-v1.js';
+
+export function gatewayBase(): string {
+ const u = PUBLIC_TELEMT_GATEWAY_URL || '';
+ return u.replace(/\/$/, '');
+}
+
+export type AggEnvelope = {
+ ok: true;
+ generated_at: string;
+ partial?: boolean;
+ data: T;
+};
+
+export class ApiError extends Error {
+ constructor(
+ message: string,
+ readonly status?: number,
+ readonly body?: unknown
+ ) {
+ super(message);
+ this.name = 'ApiError';
+ }
+}
+
+async function parseJson(res: Response): Promise {
+ const text = await res.text();
+ if (!text) return null;
+ try {
+ return JSON.parse(text);
+ } catch {
+ throw new ApiError('Ответ не JSON', res.status, text);
+ }
+}
+
+export async function fetchAggSummary(params?: {
+ aliases?: string;
+ top_n?: number;
+}): Promise> {
+ const q = new URLSearchParams();
+ if (params?.aliases) q.set('aliases', params.aliases);
+ if (params?.top_n != null) q.set('top_n', String(params.top_n));
+ const url = `${gatewayBase()}/api/agg/summary${q.toString() ? `?${q}` : ''}`;
+ const res = await fetch(url);
+ const body = (await parseJson(res)) as Record | null;
+ if (!res.ok) {
+ throw new ApiError(`summary HTTP ${res.status}`, res.status, body);
+ }
+ if (!body || body.ok !== true) {
+ throw new ApiError('summary: ok !== true', res.status, body);
+ }
+ return body as AggEnvelope;
+}
+
+export async function fetchAggFleetStatus(params?: { aliases?: string }): Promise<
+ AggEnvelope
+> {
+ const q = new URLSearchParams();
+ if (params?.aliases) q.set('aliases', params.aliases);
+ const url = `${gatewayBase()}/api/agg/fleet-status${q.toString() ? `?${q}` : ''}`;
+ const res = await fetch(url);
+ const body = (await parseJson(res)) as Record | null;
+ if (!res.ok) throw new ApiError(`fleet-status HTTP ${res.status}`, res.status, body);
+ if (!body || body.ok !== true) throw new ApiError('fleet-status: ok !== true', res.status, body);
+ return body as AggEnvelope;
+}
+
+export async function fetchAggUniqueIps(params?: {
+ aliases?: string;
+ geo?: boolean;
+}): Promise> {
+ const q = new URLSearchParams();
+ if (params?.aliases) q.set('aliases', params.aliases);
+ if (params?.geo === false) q.set('geo', 'false');
+ const url = `${gatewayBase()}/api/agg/unique-ips${q.toString() ? `?${q}` : ''}`;
+ const res = await fetch(url);
+ const body = (await parseJson(res)) as Record | null;
+ if (!res.ok) throw new ApiError(`unique-ips HTTP ${res.status}`, res.status, body);
+ if (!body || body.ok !== true) throw new ApiError('unique-ips: ok !== true', res.status, body);
+ return body as AggEnvelope;
+}
+
+export async function fetchAggUsers(params?: {
+ aliases?: string;
+ include_links?: boolean;
+}): Promise> {
+ const q = new URLSearchParams();
+ if (params?.aliases) q.set('aliases', params.aliases);
+ if (params?.include_links) q.set('include_links', 'true');
+ const url = `${gatewayBase()}/api/agg/users${q.toString() ? `?${q}` : ''}`;
+ const res = await fetch(url);
+ const body = (await parseJson(res)) as Record | null;
+ if (!res.ok) throw new ApiError(`users HTTP ${res.status}`, res.status, body);
+ if (!body || body.ok !== true) throw new ApiError('users: ok !== true', res.status, body);
+ return body as AggEnvelope;
+}
+
+export async function fetchAggUser(
+ username: string,
+ params?: { aliases?: string; include_links?: boolean }
+): Promise> {
+ const q = new URLSearchParams();
+ if (params?.aliases) q.set('aliases', params.aliases);
+ if (params?.include_links) q.set('include_links', 'true');
+ const path = encodeURIComponent(username);
+ const url = `${gatewayBase()}/api/agg/user/${path}${q.toString() ? `?${q}` : ''}`;
+ const res = await fetch(url);
+ const body = (await parseJson(res)) as Record | null;
+ if (res.status === 404) throw new ApiError('Пользователь не найден', 404, body);
+ if (!res.ok) throw new ApiError(`user HTTP ${res.status}`, res.status, body);
+ if (!body || body.ok !== true) throw new ApiError('user: ok !== true', res.status, body);
+ return body as AggEnvelope;
+}
+
+/** Путь к upstream без префикса /v1 — шлюз сам добавляет path_prefix. */
+function apiUrl(alias: string, path: string): string {
+ const p = path.replace(/^\/+/, '');
+ return `${gatewayBase()}/api/${encodeURIComponent(alias)}/${p}`;
+}
+
+/** GET к Telemt через шлюз: путь без `/v1/` (например `health`, `stats/summary`). */
+export async function fetchTelemt(alias: string, path: string): Promise> {
+ const res = await fetch(apiUrl(alias, path));
+ const body = await parseJson(res);
+ if (!res.ok) {
+ const err = body as TelemtErrorBody | null;
+ const msg = err?.error?.message ?? `HTTP ${res.status}`;
+ throw new ApiError(msg, res.status, body);
+ }
+ if (!body || (body as TelemtSuccess).ok !== true) {
+ throw new ApiError('Telemt: ok !== true', res.status, body);
+ }
+ return body as TelemtSuccess;
+}
+
+export async function fetchStatsSummary(alias: string): Promise> {
+ return fetchTelemt(alias, 'stats/summary');
+}
+
+export async function fetchUsersList(alias: string): Promise> {
+ return fetchTelemt(alias, 'users');
+}
+
+export async function fetchUserOne(alias: string, username: string): Promise> {
+ const u = encodeURIComponent(username);
+ return fetchTelemt(alias, `users/${u}`);
+}
+
+export async function createUser(
+ alias: string,
+ body: {
+ username: string;
+ secret?: string;
+ user_ad_tag?: string;
+ max_tcp_conns?: number;
+ expiration_rfc3339?: string;
+ data_quota_bytes?: number;
+ max_unique_ips?: number;
+ }
+): Promise> {
+ const res = await fetch(apiUrl(alias, 'users'), {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body)
+ });
+ const parsed = await parseJson(res);
+ if (res.status !== 201) {
+ const err = parsed as TelemtErrorBody | null;
+ throw new ApiError(err?.error?.message ?? `HTTP ${res.status}`, res.status, parsed);
+ }
+ if (!parsed || (parsed as TelemtSuccess<{ user: UserInfo; secret: string }>).ok !== true) {
+ throw new ApiError('create: ok !== true', res.status, parsed);
+ }
+ return parsed as TelemtSuccess<{ user: UserInfo; secret: string }>;
+}
+
+export async function patchUser(
+ alias: string,
+ username: string,
+ body: {
+ secret?: string;
+ user_ad_tag?: string;
+ max_tcp_conns?: number;
+ expiration_rfc3339?: string | null;
+ data_quota_bytes?: number;
+ max_unique_ips?: number;
+ },
+ ifMatch?: string | null
+): Promise> {
+ const u = encodeURIComponent(username);
+ const headers: Record = { 'Content-Type': 'application/json' };
+ if (ifMatch) headers['If-Match'] = ifMatch;
+ const res = await fetch(apiUrl(alias, `users/${u}`), {
+ method: 'PATCH',
+ headers,
+ body: JSON.stringify(body)
+ });
+ const parsed = await parseJson(res);
+ if (!res.ok) {
+ const err = parsed as TelemtErrorBody | null;
+ throw new ApiError(err?.error?.message ?? `HTTP ${res.status}`, res.status, parsed);
+ }
+ if (!parsed || (parsed as TelemtSuccess).ok !== true) {
+ throw new ApiError('patch: ok !== true', res.status, parsed);
+ }
+ return parsed as TelemtSuccess;
+}
+
+export async function deleteUser(
+ alias: string,
+ username: string,
+ ifMatch?: string | null
+): Promise> {
+ const u = encodeURIComponent(username);
+ const headers: Record = {};
+ if (ifMatch) headers['If-Match'] = ifMatch;
+ const res = await fetch(apiUrl(alias, `users/${u}`), { method: 'DELETE', headers });
+ const parsed = await parseJson(res);
+ if (!res.ok) {
+ const err = parsed as TelemtErrorBody | null;
+ throw new ApiError(err?.error?.message ?? `HTTP ${res.status}`, res.status, parsed);
+ }
+ if (!parsed || (parsed as TelemtSuccess).ok !== true) {
+ throw new ApiError('delete: ok !== true', res.status, parsed);
+ }
+ return parsed as TelemtSuccess;
+}
diff --git a/web/src/lib/api/summary-types.ts b/web/src/lib/api/summary-types.ts
new file mode 100644
index 0000000..b63c373
--- /dev/null
+++ b/web/src/lib/api/summary-types.ts
@@ -0,0 +1,30 @@
+/** Соответствует internal/aggregate/types.go — SummaryData и опрос upstream. */
+export interface ServerFetchResultRow {
+ alias: string;
+ ok: boolean;
+ http_status?: number;
+ latency_ms?: number;
+ error?: string;
+ revision?: string;
+}
+
+export interface TopUser {
+ username: string;
+ total_megabytes: number;
+}
+
+export interface TopUserByUniqueIPs {
+ username: string;
+ unique_ips: number;
+}
+
+export interface SummaryData {
+ servers: ServerFetchResultRow[];
+ servers_total: number;
+ servers_ok: number;
+ servers_failed: number;
+ fleet_total_megabytes: number;
+ fleet_total_connections: number;
+ top_users: TopUser[];
+ top_users_by_unique_ips: TopUserByUniqueIPs[];
+}
diff --git a/web/src/lib/api/telemt-v1.ts b/web/src/lib/api/telemt-v1.ts
new file mode 100644
index 0000000..b61cff5
--- /dev/null
+++ b/web/src/lib/api/telemt-v1.ts
@@ -0,0 +1,91 @@
+/** Минимальные типы Telemt Control API (docs/API.md) для UI. */
+
+export type TelemtSuccess = { ok: true; data: T; revision?: string };
+export type TelemtErrorBody = {
+ ok: false;
+ error: { code: string; message: string };
+ request_id?: number;
+};
+
+export interface HealthData {
+ status: string;
+ read_only: boolean;
+}
+
+export interface StatsSummaryData {
+ uptime_seconds: number;
+ connections_total: number;
+ connections_bad_total: number;
+ handshake_timeouts_total: number;
+ configured_users: number;
+}
+
+export interface UserLinks {
+ classic?: string[];
+ secure?: string[];
+ tls?: string[];
+}
+
+export interface UserInfo {
+ username: string;
+ user_ad_tag?: string | null;
+ max_tcp_conns?: number | null;
+ expiration_rfc3339?: string | null;
+ data_quota_bytes?: number | null;
+ max_unique_ips?: number | null;
+ current_connections: number;
+ active_unique_ips: number;
+ active_unique_ips_list?: string[];
+ recent_unique_ips: number;
+ recent_unique_ips_list?: string[];
+ total_octets: number;
+ links?: UserLinks;
+}
+
+export interface SystemInfoData {
+ version: string;
+ target_arch: string;
+ target_os: string;
+ build_profile: string;
+ git_commit?: string | null;
+ build_time_utc?: string | null;
+ rustc_version?: string | null;
+ process_started_at_epoch_secs: number;
+ uptime_seconds: number;
+ config_path: string;
+ config_hash: string;
+ config_reload_count: number;
+ last_config_reload_epoch_secs?: number | null;
+}
+
+export interface RuntimeGatesData {
+ accepting_new_connections: boolean;
+ conditional_cast_enabled: boolean;
+ me_runtime_ready: boolean;
+ me2dc_fallback_enabled: boolean;
+ use_middle_proxy: boolean;
+ startup_status: string;
+ startup_stage: string;
+ startup_progress_pct: number;
+}
+
+export interface DcStatus {
+ dc: number;
+ rtt_ms?: number | null;
+ alive_writers: number;
+ required_writers: number;
+ coverage_pct: number;
+ load: number;
+}
+
+export interface DcStatusData {
+ middle_proxy_enabled: boolean;
+ reason?: string | null;
+ dcs?: DcStatus[];
+}
+
+export interface RuntimeMePoolStateData {
+ generations?: Record;
+ contour?: Record;
+ writers_health?: Record;
+}
diff --git a/web/src/lib/assets/favicon.svg b/web/src/lib/assets/favicon.svg
new file mode 100644
index 0000000..cc5dc66
--- /dev/null
+++ b/web/src/lib/assets/favicon.svg
@@ -0,0 +1 @@
+svelte-logo
\ No newline at end of file
diff --git a/web/src/lib/components/app-sidebar.svelte b/web/src/lib/components/app-sidebar.svelte
new file mode 100644
index 0000000..bc0f767
--- /dev/null
+++ b/web/src/lib/components/app-sidebar.svelte
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Флот
+
+
+
+ {#snippet child({ props })}
+
+
+ Обзор
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Пользователи
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Уникальные IP
+
+ {/snippet}
+
+
+
+
+ {#if serverAliases.length > 0}
+
+ Серверы
+
+ {#each serverAliases as a (a)}
+
+
+ {#snippet child({ props })}
+
+
+ {a}
+
+ {/snippet}
+
+
+ {/each}
+
+
+ {/if}
+ {#if serverPath}
+
+ Нода
+
+
+
+ {#snippet child({ props })}
+
+
+ Dashboard
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Runtime
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Users
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Security
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Upstreams & DCs
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Configuration
+
+ {/snippet}
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Update
+
+ {/snippet}
+
+
+
+
+ {/if}
+
+
+
diff --git a/web/src/lib/components/ui/alert/alert-action.svelte b/web/src/lib/components/ui/alert/alert-action.svelte
new file mode 100644
index 0000000..1877d38
--- /dev/null
+++ b/web/src/lib/components/ui/alert/alert-action.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert/alert-description.svelte b/web/src/lib/components/ui/alert/alert-description.svelte
new file mode 100644
index 0000000..7ee6039
--- /dev/null
+++ b/web/src/lib/components/ui/alert/alert-description.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert/alert-title.svelte b/web/src/lib/components/ui/alert/alert-title.svelte
new file mode 100644
index 0000000..3e339a3
--- /dev/null
+++ b/web/src/lib/components/ui/alert/alert-title.svelte
@@ -0,0 +1,23 @@
+
+
+svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert/alert.svelte b/web/src/lib/components/ui/alert/alert.svelte
new file mode 100644
index 0000000..abf7487
--- /dev/null
+++ b/web/src/lib/components/ui/alert/alert.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/alert/index.ts b/web/src/lib/components/ui/alert/index.ts
new file mode 100644
index 0000000..071b113
--- /dev/null
+++ b/web/src/lib/components/ui/alert/index.ts
@@ -0,0 +1,17 @@
+import Root from "./alert.svelte";
+import Description from "./alert-description.svelte";
+import Title from "./alert-title.svelte";
+import Action from "./alert-action.svelte";
+export { alertVariants, type AlertVariant } from "./alert.svelte";
+
+export {
+ Root,
+ Description,
+ Title,
+ Action,
+ //
+ Root as Alert,
+ Description as AlertDescription,
+ Title as AlertTitle,
+ Action as AlertAction,
+};
diff --git a/web/src/lib/components/ui/badge/badge.svelte b/web/src/lib/components/ui/badge/badge.svelte
new file mode 100644
index 0000000..51bbc23
--- /dev/null
+++ b/web/src/lib/components/ui/badge/badge.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/badge/index.ts b/web/src/lib/components/ui/badge/index.ts
new file mode 100644
index 0000000..64e0aa9
--- /dev/null
+++ b/web/src/lib/components/ui/badge/index.ts
@@ -0,0 +1,2 @@
+export { default as Badge } from "./badge.svelte";
+export { badgeVariants, type BadgeVariant } from "./badge.svelte";
diff --git a/web/src/lib/components/ui/button/button.svelte b/web/src/lib/components/ui/button/button.svelte
new file mode 100644
index 0000000..b6f80ef
--- /dev/null
+++ b/web/src/lib/components/ui/button/button.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+{#if href}
+
+ {@render children?.()}
+
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/web/src/lib/components/ui/button/index.ts b/web/src/lib/components/ui/button/index.ts
new file mode 100644
index 0000000..fb585d7
--- /dev/null
+++ b/web/src/lib/components/ui/button/index.ts
@@ -0,0 +1,17 @@
+import Root, {
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+ buttonVariants,
+} from "./button.svelte";
+
+export {
+ Root,
+ type ButtonProps as Props,
+ //
+ Root as Button,
+ buttonVariants,
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+};
diff --git a/web/src/lib/components/ui/card/card-action.svelte b/web/src/lib/components/ui/card/card-action.svelte
new file mode 100644
index 0000000..7c48844
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-action.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card-content.svelte b/web/src/lib/components/ui/card/card-content.svelte
new file mode 100644
index 0000000..4f60ee3
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-content.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card-description.svelte b/web/src/lib/components/ui/card/card-description.svelte
new file mode 100644
index 0000000..9b20ac7
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-description.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card-footer.svelte b/web/src/lib/components/ui/card/card-footer.svelte
new file mode 100644
index 0000000..1efff6d
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card-header.svelte b/web/src/lib/components/ui/card/card-header.svelte
new file mode 100644
index 0000000..7eb69e1
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-header.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card-title.svelte b/web/src/lib/components/ui/card/card-title.svelte
new file mode 100644
index 0000000..3f5261e
--- /dev/null
+++ b/web/src/lib/components/ui/card/card-title.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/card.svelte b/web/src/lib/components/ui/card/card.svelte
new file mode 100644
index 0000000..2b4f81a
--- /dev/null
+++ b/web/src/lib/components/ui/card/card.svelte
@@ -0,0 +1,22 @@
+
+
+img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
+ {...restProps}
+>
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/card/index.ts b/web/src/lib/components/ui/card/index.ts
new file mode 100644
index 0000000..4d3fce4
--- /dev/null
+++ b/web/src/lib/components/ui/card/index.ts
@@ -0,0 +1,25 @@
+import Root from "./card.svelte";
+import Content from "./card-content.svelte";
+import Description from "./card-description.svelte";
+import Footer from "./card-footer.svelte";
+import Header from "./card-header.svelte";
+import Title from "./card-title.svelte";
+import Action from "./card-action.svelte";
+
+export {
+ Root,
+ Content,
+ Description,
+ Footer,
+ Header,
+ Title,
+ Action,
+ //
+ Root as Card,
+ Content as CardContent,
+ Description as CardDescription,
+ Footer as CardFooter,
+ Header as CardHeader,
+ Title as CardTitle,
+ Action as CardAction,
+};
diff --git a/web/src/lib/components/ui/dialog/dialog-close.svelte b/web/src/lib/components/ui/dialog/dialog-close.svelte
new file mode 100644
index 0000000..de68f2f
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-close.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-content.svelte b/web/src/lib/components/ui/dialog/dialog-content.svelte
new file mode 100644
index 0000000..ff6837d
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-content.svelte
@@ -0,0 +1,48 @@
+
+
+
+
+
+ {@render children?.()}
+ {#if showCloseButton}
+
+ {#snippet child({ props })}
+
+
+ Close
+
+ {/snippet}
+
+ {/if}
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-description.svelte b/web/src/lib/components/ui/dialog/dialog-description.svelte
new file mode 100644
index 0000000..0102d91
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-description.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-footer.svelte b/web/src/lib/components/ui/dialog/dialog-footer.svelte
new file mode 100644
index 0000000..e23160a
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-footer.svelte
@@ -0,0 +1,32 @@
+
+
+
+ {@render children?.()}
+ {#if showCloseButton}
+
+ {#snippet child({ props })}
+ Close
+ {/snippet}
+
+ {/if}
+
diff --git a/web/src/lib/components/ui/dialog/dialog-header.svelte b/web/src/lib/components/ui/dialog/dialog-header.svelte
new file mode 100644
index 0000000..c3ce8a2
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/dialog/dialog-overlay.svelte b/web/src/lib/components/ui/dialog/dialog-overlay.svelte
new file mode 100644
index 0000000..19f69f0
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-overlay.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-portal.svelte b/web/src/lib/components/ui/dialog/dialog-portal.svelte
new file mode 100644
index 0000000..ccfa79c
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-title.svelte b/web/src/lib/components/ui/dialog/dialog-title.svelte
new file mode 100644
index 0000000..6ff1a4a
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-title.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog-trigger.svelte b/web/src/lib/components/ui/dialog/dialog-trigger.svelte
new file mode 100644
index 0000000..589ee0c
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog-trigger.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/dialog.svelte b/web/src/lib/components/ui/dialog/dialog.svelte
new file mode 100644
index 0000000..211672c
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/dialog.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dialog/index.ts b/web/src/lib/components/ui/dialog/index.ts
new file mode 100644
index 0000000..076cef5
--- /dev/null
+++ b/web/src/lib/components/ui/dialog/index.ts
@@ -0,0 +1,34 @@
+import Root from "./dialog.svelte";
+import Portal from "./dialog-portal.svelte";
+import Title from "./dialog-title.svelte";
+import Footer from "./dialog-footer.svelte";
+import Header from "./dialog-header.svelte";
+import Overlay from "./dialog-overlay.svelte";
+import Content from "./dialog-content.svelte";
+import Description from "./dialog-description.svelte";
+import Trigger from "./dialog-trigger.svelte";
+import Close from "./dialog-close.svelte";
+
+export {
+ Root,
+ Title,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ Close,
+ //
+ Root as Dialog,
+ Title as DialogTitle,
+ Portal as DialogPortal,
+ Footer as DialogFooter,
+ Header as DialogHeader,
+ Trigger as DialogTrigger,
+ Overlay as DialogOverlay,
+ Content as DialogContent,
+ Description as DialogDescription,
+ Close as DialogClose,
+};
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
new file mode 100644
index 0000000..e0e1971
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
new file mode 100644
index 0000000..a81b48d
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
@@ -0,0 +1,44 @@
+
+
+
+ {#snippet children({ checked, indeterminate })}
+
+ {#if indeterminate}
+
+ {:else if checked}
+
+ {/if}
+
+ {@render childrenProp?.()}
+ {/snippet}
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
new file mode 100644
index 0000000..261a5b0
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
new file mode 100644
index 0000000..433540f
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
new file mode 100644
index 0000000..aca1f7b
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
new file mode 100644
index 0000000..c425190
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
@@ -0,0 +1,27 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
new file mode 100644
index 0000000..e0c534f
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
new file mode 100644
index 0000000..274cfef
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
new file mode 100644
index 0000000..189aef4
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
new file mode 100644
index 0000000..c8aa07b
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
@@ -0,0 +1,34 @@
+
+
+
+ {#snippet children({ checked })}
+
+ {#if checked}
+
+ {/if}
+
+ {@render childrenProp?.({ checked })}
+ {/snippet}
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
new file mode 100644
index 0000000..90f1b6f
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
new file mode 100644
index 0000000..ed7cc85
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
new file mode 100644
index 0000000..b5750d4
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
new file mode 100644
index 0000000..fab0275
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
new file mode 100644
index 0000000..f044581
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
new file mode 100644
index 0000000..cb05344
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
new file mode 100644
index 0000000..cb4bc62
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/dropdown-menu/index.ts b/web/src/lib/components/ui/dropdown-menu/index.ts
new file mode 100644
index 0000000..7850c6a
--- /dev/null
+++ b/web/src/lib/components/ui/dropdown-menu/index.ts
@@ -0,0 +1,54 @@
+import Root from "./dropdown-menu.svelte";
+import Sub from "./dropdown-menu-sub.svelte";
+import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte";
+import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
+import Content from "./dropdown-menu-content.svelte";
+import Group from "./dropdown-menu-group.svelte";
+import Item from "./dropdown-menu-item.svelte";
+import Label from "./dropdown-menu-label.svelte";
+import RadioGroup from "./dropdown-menu-radio-group.svelte";
+import RadioItem from "./dropdown-menu-radio-item.svelte";
+import Separator from "./dropdown-menu-separator.svelte";
+import Shortcut from "./dropdown-menu-shortcut.svelte";
+import Trigger from "./dropdown-menu-trigger.svelte";
+import SubContent from "./dropdown-menu-sub-content.svelte";
+import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
+import GroupHeading from "./dropdown-menu-group-heading.svelte";
+import Portal from "./dropdown-menu-portal.svelte";
+
+export {
+ CheckboxGroup,
+ CheckboxItem,
+ Content,
+ Portal,
+ Root as DropdownMenu,
+ CheckboxGroup as DropdownMenuCheckboxGroup,
+ CheckboxItem as DropdownMenuCheckboxItem,
+ Content as DropdownMenuContent,
+ Portal as DropdownMenuPortal,
+ Group as DropdownMenuGroup,
+ Item as DropdownMenuItem,
+ Label as DropdownMenuLabel,
+ RadioGroup as DropdownMenuRadioGroup,
+ RadioItem as DropdownMenuRadioItem,
+ Separator as DropdownMenuSeparator,
+ Shortcut as DropdownMenuShortcut,
+ Sub as DropdownMenuSub,
+ SubContent as DropdownMenuSubContent,
+ SubTrigger as DropdownMenuSubTrigger,
+ Trigger as DropdownMenuTrigger,
+ GroupHeading as DropdownMenuGroupHeading,
+ Group,
+ GroupHeading,
+ Item,
+ Label,
+ RadioGroup,
+ RadioItem,
+ Root,
+ Separator,
+ Shortcut,
+ Sub,
+ SubContent,
+ SubTrigger,
+ Trigger,
+};
diff --git a/web/src/lib/components/ui/input/index.ts b/web/src/lib/components/ui/input/index.ts
new file mode 100644
index 0000000..f47b6d3
--- /dev/null
+++ b/web/src/lib/components/ui/input/index.ts
@@ -0,0 +1,7 @@
+import Root from "./input.svelte";
+
+export {
+ Root,
+ //
+ Root as Input,
+};
diff --git a/web/src/lib/components/ui/input/input.svelte b/web/src/lib/components/ui/input/input.svelte
new file mode 100644
index 0000000..fe7db38
--- /dev/null
+++ b/web/src/lib/components/ui/input/input.svelte
@@ -0,0 +1,48 @@
+
+
+{#if type === "file"}
+
+{:else}
+
+{/if}
diff --git a/web/src/lib/components/ui/label/index.ts b/web/src/lib/components/ui/label/index.ts
new file mode 100644
index 0000000..8bfca0b
--- /dev/null
+++ b/web/src/lib/components/ui/label/index.ts
@@ -0,0 +1,7 @@
+import Root from "./label.svelte";
+
+export {
+ Root,
+ //
+ Root as Label,
+};
diff --git a/web/src/lib/components/ui/label/label.svelte b/web/src/lib/components/ui/label/label.svelte
new file mode 100644
index 0000000..d5e3086
--- /dev/null
+++ b/web/src/lib/components/ui/label/label.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/web/src/lib/components/ui/scroll-area/index.ts b/web/src/lib/components/ui/scroll-area/index.ts
new file mode 100644
index 0000000..e86a25b
--- /dev/null
+++ b/web/src/lib/components/ui/scroll-area/index.ts
@@ -0,0 +1,10 @@
+import Scrollbar from "./scroll-area-scrollbar.svelte";
+import Root from "./scroll-area.svelte";
+
+export {
+ Root,
+ Scrollbar,
+ //,
+ Root as ScrollArea,
+ Scrollbar as ScrollAreaScrollbar,
+};
diff --git a/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte
new file mode 100644
index 0000000..b9518f3
--- /dev/null
+++ b/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte
@@ -0,0 +1,30 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/web/src/lib/components/ui/scroll-area/scroll-area.svelte b/web/src/lib/components/ui/scroll-area/scroll-area.svelte
new file mode 100644
index 0000000..d4d96d0
--- /dev/null
+++ b/web/src/lib/components/ui/scroll-area/scroll-area.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+ {@render children?.()}
+
+ {#if orientation === "vertical" || orientation === "both"}
+
+ {/if}
+ {#if orientation === "horizontal" || orientation === "both"}
+
+ {/if}
+
+
diff --git a/web/src/lib/components/ui/select/index.ts b/web/src/lib/components/ui/select/index.ts
new file mode 100644
index 0000000..4dec358
--- /dev/null
+++ b/web/src/lib/components/ui/select/index.ts
@@ -0,0 +1,37 @@
+import Root from "./select.svelte";
+import Group from "./select-group.svelte";
+import Label from "./select-label.svelte";
+import Item from "./select-item.svelte";
+import Content from "./select-content.svelte";
+import Trigger from "./select-trigger.svelte";
+import Separator from "./select-separator.svelte";
+import ScrollDownButton from "./select-scroll-down-button.svelte";
+import ScrollUpButton from "./select-scroll-up-button.svelte";
+import GroupHeading from "./select-group-heading.svelte";
+import Portal from "./select-portal.svelte";
+
+export {
+ Root,
+ Group,
+ Label,
+ Item,
+ Content,
+ Trigger,
+ Separator,
+ ScrollDownButton,
+ ScrollUpButton,
+ GroupHeading,
+ Portal,
+ //
+ Root as Select,
+ Group as SelectGroup,
+ Label as SelectLabel,
+ Item as SelectItem,
+ Content as SelectContent,
+ Trigger as SelectTrigger,
+ Separator as SelectSeparator,
+ ScrollDownButton as SelectScrollDownButton,
+ ScrollUpButton as SelectScrollUpButton,
+ GroupHeading as SelectGroupHeading,
+ Portal as SelectPortal,
+};
diff --git a/web/src/lib/components/ui/select/select-content.svelte b/web/src/lib/components/ui/select/select-content.svelte
new file mode 100644
index 0000000..bb1e2ac
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-content.svelte
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+
diff --git a/web/src/lib/components/ui/select/select-group-heading.svelte b/web/src/lib/components/ui/select/select-group-heading.svelte
new file mode 100644
index 0000000..1fab5f0
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-group-heading.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/select/select-group.svelte b/web/src/lib/components/ui/select/select-group.svelte
new file mode 100644
index 0000000..f666cb2
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-group.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/select/select-item.svelte b/web/src/lib/components/ui/select/select-item.svelte
new file mode 100644
index 0000000..32fd5ce
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-item.svelte
@@ -0,0 +1,38 @@
+
+
+
+ {#snippet children({ selected, highlighted })}
+
+ {#if selected}
+
+ {/if}
+
+ {#if childrenProp}
+ {@render childrenProp({ selected, highlighted })}
+ {:else}
+ {label || value}
+ {/if}
+ {/snippet}
+
diff --git a/web/src/lib/components/ui/select/select-label.svelte b/web/src/lib/components/ui/select/select-label.svelte
new file mode 100644
index 0000000..69bcfdf
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-label.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/select/select-portal.svelte b/web/src/lib/components/ui/select/select-portal.svelte
new file mode 100644
index 0000000..424bcdd
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/select/select-scroll-down-button.svelte b/web/src/lib/components/ui/select/select-scroll-down-button.svelte
new file mode 100644
index 0000000..94f41cd
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-scroll-down-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/web/src/lib/components/ui/select/select-scroll-up-button.svelte b/web/src/lib/components/ui/select/select-scroll-up-button.svelte
new file mode 100644
index 0000000..035ea09
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-scroll-up-button.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/web/src/lib/components/ui/select/select-separator.svelte b/web/src/lib/components/ui/select/select-separator.svelte
new file mode 100644
index 0000000..3b24bab
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-separator.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/web/src/lib/components/ui/select/select-trigger.svelte b/web/src/lib/components/ui/select/select-trigger.svelte
new file mode 100644
index 0000000..03d06d0
--- /dev/null
+++ b/web/src/lib/components/ui/select/select-trigger.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {@render children?.()}
+
+
diff --git a/web/src/lib/components/ui/select/select.svelte b/web/src/lib/components/ui/select/select.svelte
new file mode 100644
index 0000000..05eb663
--- /dev/null
+++ b/web/src/lib/components/ui/select/select.svelte
@@ -0,0 +1,11 @@
+
+
+
diff --git a/web/src/lib/components/ui/separator/index.ts b/web/src/lib/components/ui/separator/index.ts
new file mode 100644
index 0000000..82442d2
--- /dev/null
+++ b/web/src/lib/components/ui/separator/index.ts
@@ -0,0 +1,7 @@
+import Root from "./separator.svelte";
+
+export {
+ Root,
+ //
+ Root as Separator,
+};
diff --git a/web/src/lib/components/ui/separator/separator.svelte b/web/src/lib/components/ui/separator/separator.svelte
new file mode 100644
index 0000000..5fd8a42
--- /dev/null
+++ b/web/src/lib/components/ui/separator/separator.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/index.ts b/web/src/lib/components/ui/sheet/index.ts
new file mode 100644
index 0000000..28d7da1
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/index.ts
@@ -0,0 +1,34 @@
+import Root from "./sheet.svelte";
+import Portal from "./sheet-portal.svelte";
+import Trigger from "./sheet-trigger.svelte";
+import Close from "./sheet-close.svelte";
+import Overlay from "./sheet-overlay.svelte";
+import Content from "./sheet-content.svelte";
+import Header from "./sheet-header.svelte";
+import Footer from "./sheet-footer.svelte";
+import Title from "./sheet-title.svelte";
+import Description from "./sheet-description.svelte";
+
+export {
+ Root,
+ Close,
+ Trigger,
+ Portal,
+ Overlay,
+ Content,
+ Header,
+ Footer,
+ Title,
+ Description,
+ //
+ Root as Sheet,
+ Close as SheetClose,
+ Trigger as SheetTrigger,
+ Portal as SheetPortal,
+ Overlay as SheetOverlay,
+ Content as SheetContent,
+ Header as SheetHeader,
+ Footer as SheetFooter,
+ Title as SheetTitle,
+ Description as SheetDescription,
+};
diff --git a/web/src/lib/components/ui/sheet/sheet-close.svelte b/web/src/lib/components/ui/sheet/sheet-close.svelte
new file mode 100644
index 0000000..ae382c1
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-close.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-content.svelte b/web/src/lib/components/ui/sheet/sheet-content.svelte
new file mode 100644
index 0000000..439b4a4
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-content.svelte
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+ {@render children?.()}
+ {#if showCloseButton}
+
+ {#snippet child({ props })}
+
+
+ Close
+
+ {/snippet}
+
+ {/if}
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-description.svelte b/web/src/lib/components/ui/sheet/sheet-description.svelte
new file mode 100644
index 0000000..333b17a
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-description.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-footer.svelte b/web/src/lib/components/ui/sheet/sheet-footer.svelte
new file mode 100644
index 0000000..ad9e07b
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sheet/sheet-header.svelte b/web/src/lib/components/ui/sheet/sheet-header.svelte
new file mode 100644
index 0000000..504465f
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sheet/sheet-overlay.svelte b/web/src/lib/components/ui/sheet/sheet-overlay.svelte
new file mode 100644
index 0000000..8d33847
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-overlay.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-portal.svelte b/web/src/lib/components/ui/sheet/sheet-portal.svelte
new file mode 100644
index 0000000..f3085a3
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-title.svelte b/web/src/lib/components/ui/sheet/sheet-title.svelte
new file mode 100644
index 0000000..10c1717
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-title.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet-trigger.svelte b/web/src/lib/components/ui/sheet/sheet-trigger.svelte
new file mode 100644
index 0000000..e266975
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sheet/sheet.svelte b/web/src/lib/components/ui/sheet/sheet.svelte
new file mode 100644
index 0000000..5bf9783
--- /dev/null
+++ b/web/src/lib/components/ui/sheet/sheet.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/constants.ts b/web/src/lib/components/ui/sidebar/constants.ts
new file mode 100644
index 0000000..4de4435
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/constants.ts
@@ -0,0 +1,6 @@
+export const SIDEBAR_COOKIE_NAME = "sidebar:state";
+export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+export const SIDEBAR_WIDTH = "16rem";
+export const SIDEBAR_WIDTH_MOBILE = "18rem";
+export const SIDEBAR_WIDTH_ICON = "3rem";
+export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
diff --git a/web/src/lib/components/ui/sidebar/context.svelte.ts b/web/src/lib/components/ui/sidebar/context.svelte.ts
new file mode 100644
index 0000000..15248ad
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/context.svelte.ts
@@ -0,0 +1,81 @@
+import { IsMobile } from "$lib/hooks/is-mobile.svelte.js";
+import { getContext, setContext } from "svelte";
+import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js";
+
+type Getter = () => T;
+
+export type SidebarStateProps = {
+ /**
+ * A getter function that returns the current open state of the sidebar.
+ * We use a getter function here to support `bind:open` on the `Sidebar.Provider`
+ * component.
+ */
+ open: Getter;
+
+ /**
+ * A function that sets the open state of the sidebar. To support `bind:open`, we need
+ * a source of truth for changing the open state to ensure it will be synced throughout
+ * the sub-components and any `bind:` references.
+ */
+ setOpen: (open: boolean) => void;
+};
+
+class SidebarState {
+ readonly props: SidebarStateProps;
+ open = $derived.by(() => this.props.open());
+ openMobile = $state(false);
+ setOpen: SidebarStateProps["setOpen"];
+ #isMobile: IsMobile;
+ state = $derived.by(() => (this.open ? "expanded" : "collapsed"));
+
+ constructor(props: SidebarStateProps) {
+ this.setOpen = props.setOpen;
+ this.#isMobile = new IsMobile();
+ this.props = props;
+ }
+
+ // Convenience getter for checking if the sidebar is mobile
+ // without this, we would need to use `sidebar.isMobile.current` everywhere
+ get isMobile() {
+ return this.#isMobile.current;
+ }
+
+ // Event handler to apply to the ``
+ handleShortcutKeydown = (e: KeyboardEvent) => {
+ if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ this.toggle();
+ }
+ };
+
+ setOpenMobile = (value: boolean) => {
+ this.openMobile = value;
+ };
+
+ toggle = () => {
+ return this.#isMobile.current
+ ? (this.openMobile = !this.openMobile)
+ : this.setOpen(!this.open);
+ };
+}
+
+const SYMBOL_KEY = "scn-sidebar";
+
+/**
+ * Instantiates a new `SidebarState` instance and sets it in the context.
+ *
+ * @param props The constructor props for the `SidebarState` class.
+ * @returns The `SidebarState` instance.
+ */
+export function setSidebar(props: SidebarStateProps): SidebarState {
+ return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
+}
+
+/**
+ * Retrieves the `SidebarState` instance from the context. This is a class instance,
+ * so you cannot destructure it.
+ * @returns The `SidebarState` instance.
+ */
+export function useSidebar(): SidebarState {
+ return getContext(Symbol.for(SYMBOL_KEY));
+}
diff --git a/web/src/lib/components/ui/sidebar/index.ts b/web/src/lib/components/ui/sidebar/index.ts
new file mode 100644
index 0000000..318a341
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/index.ts
@@ -0,0 +1,75 @@
+import { useSidebar } from "./context.svelte.js";
+import Content from "./sidebar-content.svelte";
+import Footer from "./sidebar-footer.svelte";
+import GroupAction from "./sidebar-group-action.svelte";
+import GroupContent from "./sidebar-group-content.svelte";
+import GroupLabel from "./sidebar-group-label.svelte";
+import Group from "./sidebar-group.svelte";
+import Header from "./sidebar-header.svelte";
+import Input from "./sidebar-input.svelte";
+import Inset from "./sidebar-inset.svelte";
+import MenuAction from "./sidebar-menu-action.svelte";
+import MenuBadge from "./sidebar-menu-badge.svelte";
+import MenuButton from "./sidebar-menu-button.svelte";
+import MenuItem from "./sidebar-menu-item.svelte";
+import MenuSkeleton from "./sidebar-menu-skeleton.svelte";
+import MenuSubButton from "./sidebar-menu-sub-button.svelte";
+import MenuSubItem from "./sidebar-menu-sub-item.svelte";
+import MenuSub from "./sidebar-menu-sub.svelte";
+import Menu from "./sidebar-menu.svelte";
+import Provider from "./sidebar-provider.svelte";
+import Rail from "./sidebar-rail.svelte";
+import Separator from "./sidebar-separator.svelte";
+import Trigger from "./sidebar-trigger.svelte";
+import Root from "./sidebar.svelte";
+
+export {
+ Content,
+ Footer,
+ Group,
+ GroupAction,
+ GroupContent,
+ GroupLabel,
+ Header,
+ Input,
+ Inset,
+ Menu,
+ MenuAction,
+ MenuBadge,
+ MenuButton,
+ MenuItem,
+ MenuSkeleton,
+ MenuSub,
+ MenuSubButton,
+ MenuSubItem,
+ Provider,
+ Rail,
+ Root,
+ Separator,
+ //
+ Root as Sidebar,
+ Content as SidebarContent,
+ Footer as SidebarFooter,
+ Group as SidebarGroup,
+ GroupAction as SidebarGroupAction,
+ GroupContent as SidebarGroupContent,
+ GroupLabel as SidebarGroupLabel,
+ Header as SidebarHeader,
+ Input as SidebarInput,
+ Inset as SidebarInset,
+ Menu as SidebarMenu,
+ MenuAction as SidebarMenuAction,
+ MenuBadge as SidebarMenuBadge,
+ MenuButton as SidebarMenuButton,
+ MenuItem as SidebarMenuItem,
+ MenuSkeleton as SidebarMenuSkeleton,
+ MenuSub as SidebarMenuSub,
+ MenuSubButton as SidebarMenuSubButton,
+ MenuSubItem as SidebarMenuSubItem,
+ Provider as SidebarProvider,
+ Rail as SidebarRail,
+ Separator as SidebarSeparator,
+ Trigger as SidebarTrigger,
+ Trigger,
+ useSidebar,
+};
diff --git a/web/src/lib/components/ui/sidebar/sidebar-content.svelte b/web/src/lib/components/ui/sidebar/sidebar-content.svelte
new file mode 100644
index 0000000..2a8d7ea
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-content.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-footer.svelte b/web/src/lib/components/ui/sidebar/sidebar-footer.svelte
new file mode 100644
index 0000000..496c6c0
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-footer.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte
new file mode 100644
index 0000000..1d07b97
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte
@@ -0,0 +1,33 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte
new file mode 100644
index 0000000..7835b0e
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte
new file mode 100644
index 0000000..518216c
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte
@@ -0,0 +1,33 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/web/src/lib/components/ui/sidebar/sidebar-group.svelte b/web/src/lib/components/ui/sidebar/sidebar-group.svelte
new file mode 100644
index 0000000..b5880d7
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-group.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-header.svelte b/web/src/lib/components/ui/sidebar/sidebar-header.svelte
new file mode 100644
index 0000000..b54754a
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-header.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-input.svelte b/web/src/lib/components/ui/sidebar/sidebar-input.svelte
new file mode 100644
index 0000000..19b3666
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-input.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-inset.svelte b/web/src/lib/components/ui/sidebar/sidebar-inset.svelte
new file mode 100644
index 0000000..19a787c
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-inset.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
new file mode 100644
index 0000000..26c81ed
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
@@ -0,0 +1,37 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte
new file mode 100644
index 0000000..6cecdb4
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
new file mode 100644
index 0000000..8e1668b
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
@@ -0,0 +1,102 @@
+
+
+
+
+{#snippet Button({ props }: { props?: Record })}
+ {@const mergedProps = mergeProps(buttonProps, props)}
+ {#if child}
+ {@render child({ props: mergedProps })}
+ {:else}
+
+ {@render children?.()}
+
+ {/if}
+{/snippet}
+
+{#if !tooltipContent}
+ {@render Button({})}
+{:else}
+
+
+ {#snippet child({ props })}
+ {@render Button({ props })}
+ {/snippet}
+
+
+ {#if typeof tooltipContent === "string"}
+ {tooltipContent}
+ {:else if tooltipContent}
+ {@render tooltipContent()}
+ {/if}
+
+
+{/if}
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte
new file mode 100644
index 0000000..4db4453
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte
new file mode 100644
index 0000000..1e1249b
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte
@@ -0,0 +1,36 @@
+
+
+
+ {#if showIcon}
+
+ {/if}
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
new file mode 100644
index 0000000..09ff228
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
@@ -0,0 +1,39 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte
new file mode 100644
index 0000000..681d0f1
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte
new file mode 100644
index 0000000..29614ee
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-menu.svelte b/web/src/lib/components/ui/sidebar/sidebar-menu.svelte
new file mode 100644
index 0000000..a8cdefd
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-menu.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-provider.svelte b/web/src/lib/components/ui/sidebar/sidebar-provider.svelte
new file mode 100644
index 0000000..5b0d0aa
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-provider.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-rail.svelte b/web/src/lib/components/ui/sidebar/sidebar-rail.svelte
new file mode 100644
index 0000000..f65c869
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-rail.svelte
@@ -0,0 +1,36 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-separator.svelte b/web/src/lib/components/ui/sidebar/sidebar-separator.svelte
new file mode 100644
index 0000000..18d791b
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-separator.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte
new file mode 100644
index 0000000..dd2dc50
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte
@@ -0,0 +1,36 @@
+
+
+
diff --git a/web/src/lib/components/ui/sidebar/sidebar.svelte b/web/src/lib/components/ui/sidebar/sidebar.svelte
new file mode 100644
index 0000000..9c90d4f
--- /dev/null
+++ b/web/src/lib/components/ui/sidebar/sidebar.svelte
@@ -0,0 +1,108 @@
+
+
+{#if collapsible === "none"}
+
+ {@render children?.()}
+
+{:else if sidebar.isMobile}
+ sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
+ {...restProps}
+ >
+ button]:hidden",
+ className
+ )}
+ style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
+ {side}
+ >
+
+ Sidebar
+ Displays the mobile sidebar.
+
+
+ {@render children?.()}
+
+
+
+{:else}
+
+{/if}
diff --git a/web/src/lib/components/ui/skeleton/index.ts b/web/src/lib/components/ui/skeleton/index.ts
new file mode 100644
index 0000000..186db21
--- /dev/null
+++ b/web/src/lib/components/ui/skeleton/index.ts
@@ -0,0 +1,7 @@
+import Root from "./skeleton.svelte";
+
+export {
+ Root,
+ //
+ Root as Skeleton,
+};
diff --git a/web/src/lib/components/ui/skeleton/skeleton.svelte b/web/src/lib/components/ui/skeleton/skeleton.svelte
new file mode 100644
index 0000000..1a940ee
--- /dev/null
+++ b/web/src/lib/components/ui/skeleton/skeleton.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/web/src/lib/components/ui/table/index.ts b/web/src/lib/components/ui/table/index.ts
new file mode 100644
index 0000000..14695c8
--- /dev/null
+++ b/web/src/lib/components/ui/table/index.ts
@@ -0,0 +1,28 @@
+import Root from "./table.svelte";
+import Body from "./table-body.svelte";
+import Caption from "./table-caption.svelte";
+import Cell from "./table-cell.svelte";
+import Footer from "./table-footer.svelte";
+import Head from "./table-head.svelte";
+import Header from "./table-header.svelte";
+import Row from "./table-row.svelte";
+
+export {
+ Root,
+ Body,
+ Caption,
+ Cell,
+ Footer,
+ Head,
+ Header,
+ Row,
+ //
+ Root as Table,
+ Body as TableBody,
+ Caption as TableCaption,
+ Cell as TableCell,
+ Footer as TableFooter,
+ Head as TableHead,
+ Header as TableHeader,
+ Row as TableRow,
+};
diff --git a/web/src/lib/components/ui/table/table-body.svelte b/web/src/lib/components/ui/table/table-body.svelte
new file mode 100644
index 0000000..935feae
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-body.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-caption.svelte b/web/src/lib/components/ui/table/table-caption.svelte
new file mode 100644
index 0000000..4696cff
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-caption.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-cell.svelte b/web/src/lib/components/ui/table/table-cell.svelte
new file mode 100644
index 0000000..a998bf6
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-cell.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-footer.svelte b/web/src/lib/components/ui/table/table-footer.svelte
new file mode 100644
index 0000000..b9b14eb
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-footer.svelte
@@ -0,0 +1,20 @@
+
+
+tr]:last:border-b-0", className)}
+ {...restProps}
+>
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-head.svelte b/web/src/lib/components/ui/table/table-head.svelte
new file mode 100644
index 0000000..267c4e0
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-head.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-header.svelte b/web/src/lib/components/ui/table/table-header.svelte
new file mode 100644
index 0000000..f47d259
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table-row.svelte b/web/src/lib/components/ui/table/table-row.svelte
new file mode 100644
index 0000000..90b4e2a
--- /dev/null
+++ b/web/src/lib/components/ui/table/table-row.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/web/src/lib/components/ui/table/table.svelte b/web/src/lib/components/ui/table/table.svelte
new file mode 100644
index 0000000..d95a02e
--- /dev/null
+++ b/web/src/lib/components/ui/table/table.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+ {@render children?.()}
+
+
diff --git a/web/src/lib/components/ui/textarea/index.ts b/web/src/lib/components/ui/textarea/index.ts
new file mode 100644
index 0000000..ace797a
--- /dev/null
+++ b/web/src/lib/components/ui/textarea/index.ts
@@ -0,0 +1,7 @@
+import Root from "./textarea.svelte";
+
+export {
+ Root,
+ //
+ Root as Textarea,
+};
diff --git a/web/src/lib/components/ui/textarea/textarea.svelte b/web/src/lib/components/ui/textarea/textarea.svelte
new file mode 100644
index 0000000..2e779ff
--- /dev/null
+++ b/web/src/lib/components/ui/textarea/textarea.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/web/src/lib/components/ui/tooltip/index.ts b/web/src/lib/components/ui/tooltip/index.ts
new file mode 100644
index 0000000..1718604
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/index.ts
@@ -0,0 +1,19 @@
+import Root from "./tooltip.svelte";
+import Trigger from "./tooltip-trigger.svelte";
+import Content from "./tooltip-content.svelte";
+import Provider from "./tooltip-provider.svelte";
+import Portal from "./tooltip-portal.svelte";
+
+export {
+ Root,
+ Trigger,
+ Content,
+ Provider,
+ Portal,
+ //
+ Root as Tooltip,
+ Content as TooltipContent,
+ Trigger as TooltipTrigger,
+ Provider as TooltipProvider,
+ Portal as TooltipPortal,
+};
diff --git a/web/src/lib/components/ui/tooltip/tooltip-content.svelte b/web/src/lib/components/ui/tooltip/tooltip-content.svelte
new file mode 100644
index 0000000..0cf0694
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/tooltip-content.svelte
@@ -0,0 +1,52 @@
+
+
+
+
+ {@render children?.()}
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
diff --git a/web/src/lib/components/ui/tooltip/tooltip-portal.svelte b/web/src/lib/components/ui/tooltip/tooltip-portal.svelte
new file mode 100644
index 0000000..d234f7d
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/tooltip-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/tooltip/tooltip-provider.svelte b/web/src/lib/components/ui/tooltip/tooltip-provider.svelte
new file mode 100644
index 0000000..6dba9a6
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/tooltip-provider.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte
new file mode 100644
index 0000000..1acdaa4
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/web/src/lib/components/ui/tooltip/tooltip.svelte b/web/src/lib/components/ui/tooltip/tooltip.svelte
new file mode 100644
index 0000000..44bee9f
--- /dev/null
+++ b/web/src/lib/components/ui/tooltip/tooltip.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/web/src/lib/format.ts b/web/src/lib/format.ts
new file mode 100644
index 0000000..3a96661
--- /dev/null
+++ b/web/src/lib/format.ts
@@ -0,0 +1,34 @@
+export function formatBytes(octets: number): string {
+ if (octets < 1024) return `${octets} B`;
+ const units = ['KiB', 'MiB', 'GiB', 'TiB'];
+ let n = octets / 1024;
+ let i = 0;
+ while (n >= 1024 && i < units.length - 1) {
+ n /= 1024;
+ i++;
+ }
+ return `${n < 10 && i > 0 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
+}
+
+export function formatMiB(mib: number): string {
+ if (mib < 1024) return `${mib.toFixed(mib < 10 ? 2 : 1)} MiB`;
+ return `${(mib / 1024).toFixed(2)} GiB`;
+}
+
+export function formatDurationSeconds(sec: number): string {
+ if (sec < 60) return `${Math.round(sec)}s`;
+ if (sec < 3600) {
+ const m = Math.floor(sec / 60);
+ const s = Math.round(sec % 60);
+ return `${m}m ${s}s`;
+ }
+ const h = Math.floor(sec / 3600);
+ const m = Math.floor((sec % 3600) / 60);
+ return `${h}h ${m}m`;
+}
+
+export function flagEmoji(code: string | null | undefined): string {
+ if (!code || code.length !== 2) return '';
+ const u = code.toUpperCase();
+ return String.fromCodePoint(...[...u].map((c) => 127397 + c.charCodeAt(0)));
+}
diff --git a/web/src/lib/hooks/is-mobile.svelte.ts b/web/src/lib/hooks/is-mobile.svelte.ts
new file mode 100644
index 0000000..4829c00
--- /dev/null
+++ b/web/src/lib/hooks/is-mobile.svelte.ts
@@ -0,0 +1,9 @@
+import { MediaQuery } from "svelte/reactivity";
+
+const DEFAULT_MOBILE_BREAKPOINT = 768;
+
+export class IsMobile extends MediaQuery {
+ constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) {
+ super(`max-width: ${breakpoint - 1}px`);
+ }
+}
diff --git a/web/src/lib/index.ts b/web/src/lib/index.ts
new file mode 100644
index 0000000..856f2b6
--- /dev/null
+++ b/web/src/lib/index.ts
@@ -0,0 +1 @@
+// place files you want to import through the `$lib` alias in this folder.
diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts
new file mode 100644
index 0000000..d8411a4
--- /dev/null
+++ b/web/src/lib/utils.ts
@@ -0,0 +1,13 @@
+import { type ClassValue, clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+export type {
+ WithElementRef,
+ WithoutChildren,
+ WithoutChild,
+ WithChildren,
+ WithoutChildrenOrChild
+} from 'bits-ui';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
new file mode 100644
index 0000000..7b526ce
--- /dev/null
+++ b/web/src/routes/+layout.svelte
@@ -0,0 +1,57 @@
+
+
+
+
+ Telemt Panel
+
+
+
+
+
+
+
+ {@render children()}
+
+
+
diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts
new file mode 100644
index 0000000..a3d1578
--- /dev/null
+++ b/web/src/routes/+layout.ts
@@ -0,0 +1 @@
+export const ssr = false;
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
new file mode 100644
index 0000000..a090089
--- /dev/null
+++ b/web/src/routes/+page.svelte
@@ -0,0 +1,305 @@
+
+
+
+
+
+ Telemt multi-server overview
+
+
Обзор флота
+ {#if generatedAt}
+
+ Снимок: {new Date(generatedAt).toLocaleString()}
+
+ {/if}
+
+
+
+ Обновить
+
+
+
+{#if partial}
+
+
+ Частичные данные
+
+ Один или несколько upstream ответили с ошибкой — цифры могут быть неполными.
+
+
+{/if}
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading && !summary}
+ Загрузка…
+{:else if summary}
+
+
+
+ Ноды
+
+ {fleet?.servers_all_ok ?? summary.servers_ok}/{fleet?.servers_total ?? summary.servers_total}
+
+
+ успешный опрос
+
+
+
+ Соединения (флот)
+ {summary.fleet_total_connections}
+
+ текущие по stats/users
+
+
+
+ Bad connections
+ {totalBad}
+
+ сумма по нодам (stats/summary)
+
+
+
+ Уникальные IP
+ {uniqueIpCount}
+
+ по снимку unique-ips
+
+
+
+ Трафик флота
+ {formatMiB(summary.fleet_total_megabytes)}
+
+ сумма MiB
+
+
+
+ Read-only API
+ {readOnlyNodes}
+
+ нод с read_only
+
+
+
+
+
+ Активные IP (агрегат)
+ С GeoIP при включённой базе на шлюзе
+
+
+
+ {#each (uniqueIps ?? []).slice(0, 80) as row (row.username)}
+ {#each row.ips ?? [] as ip (`${row.username ?? ''}-${ip.ip ?? ''}`)}
+
+ {chipLabel(ip)}
+
+ {/each}
+ {/each}
+ {#if (uniqueIps?.length ?? 0) === 0}
+ Нет данных
+ {/if}
+
+
+
+
+
+
+ По нодам
+ Health, версия, аптайм, трафик ноды
+
+
+
+
+
+ Сервер
+ Статус
+ Latency
+ Версия
+ Аптайм
+ Bad conn.
+
+
+
+ {#each fleet?.servers ?? [] as srv (srv.alias)}
+
+
+
+ {srv.alias}
+
+
+
+ {#if srv.health_ok && srv.system_info_ok}
+ OK
+ {:else}
+ degraded
+ {/if}
+
+
+ {srv.health_latency_ms ?? '—'} ms
+
+
+ {srv.system_info?.version ?? '—'}
+
+
+ {srv.system_info && srv.system_info.uptime_seconds != null
+ ? formatDurationSeconds(srv.system_info.uptime_seconds)
+ : '—'}
+
+
+ {#if srv.alias}
+ {@const st = nodeStats[srv.alias]}
+ {#if !st}
+ —
+ {:else if 'ok' in st && st.ok === false}
+ —
+ {:else if st && 'bad' in st && st.ok}
+ {st.bad ?? '—'}
+ {:else}
+ —
+ {/if}
+ {:else}
+ —
+ {/if}
+
+
+ {/each}
+
+
+
+
+
+
+
+ Топ пользователей по трафику
+
+
+
+ {#each summary.top_users.slice(0, 10) as t (t.username)}
+
+ {t.username}
+ {formatMiB(t.total_megabytes)}
+
+ {/each}
+
+
+
+{/if}
diff --git a/web/src/routes/ips/+page.svelte b/web/src/routes/ips/+page.svelte
new file mode 100644
index 0000000..e4f16af
--- /dev/null
+++ b/web/src/routes/ips/+page.svelte
@@ -0,0 +1,115 @@
+
+
+
+
+
Уникальные IP
+
Снимок active/recent с шлюза; GeoIP из конфига шлюза.
+
+
+
+
+ GeoIP
+
+
+
+ Обновить
+
+
+
+
+{#if partial}
+
+ Частичные данные
+ Не все upstream успешны.
+
+{/if}
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading && rows.length === 0}
+ Загрузка…
+{:else}
+
+
+
+
+
+ Пользователь
+ IP
+ Geo
+ Серверы
+
+
+
+ {#each rows as ur (ur.username)}
+ {#each ur.ips ?? [] as ip (ur.username + (ip.ip ?? ''))}
+
+
+
+ {ur.username}
+
+
+ {ip.ip}
+
+ {flagEmoji(ip.country_code ?? undefined)}
+ {ip.country_code ?? '—'}
+ {#if ip.city_name}
+ · {ip.city_name}
+ {/if}
+ {#if ip.asn}
+ AS{ip.asn}
+ {/if}
+
+
+ active: {(ip.active_on_servers ?? []).join(', ') || '—'}
+ {#if (ip.recent_on_servers?.length ?? 0) > 0}
+ recent: {(ip.recent_on_servers ?? []).join(', ')}
+ {/if}
+ {#if ip.primary_server}
+ primary: {ip.primary_server}
+ {/if}
+
+
+ {/each}
+ {/each}
+
+
+
+
+{/if}
diff --git a/web/src/routes/layout.css b/web/src/routes/layout.css
new file mode 100644
index 0000000..d0f76b6
--- /dev/null
+++ b/web/src/routes/layout.css
@@ -0,0 +1,180 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant data-open {
+ &:where([data-state='open']), &:where([data-open]:not([data-open='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-closed {
+ &:where([data-state='closed']), &:where([data-closed]:not([data-closed='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-checked {
+ &:where([data-state='checked']), &:where([data-checked]:not([data-checked='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-unchecked {
+ &:where([data-state='unchecked']), &:where([data-unchecked]:not([data-unchecked='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-selected {
+ &:where([data-selected]) {
+ @slot;
+ }
+}
+
+@custom-variant data-disabled {
+ &:where([data-disabled='true']), &:where([data-disabled]:not([data-disabled='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-active {
+ &:where([data-state='active']), &:where([data-active]:not([data-active='false'])) {
+ @slot;
+ }
+}
+
+@custom-variant data-horizontal {
+ &:where([data-orientation='horizontal']) {
+ @slot;
+ }
+}
+
+@custom-variant data-vertical {
+ &:where([data-orientation='vertical']) {
+ @slot;
+ }
+}
+
+@utility no-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.141 0.005 285.823);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.141 0.005 285.823);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.141 0.005 285.823);
+ --primary: oklch(0.21 0.006 285.885);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.967 0.001 286.375);
+ --secondary-foreground: oklch(0.21 0.006 285.885);
+ --muted: oklch(0.967 0.001 286.375);
+ --muted-foreground: oklch(0.552 0.016 285.938);
+ --accent: oklch(0.967 0.001 286.375);
+ --accent-foreground: oklch(0.21 0.006 285.885);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.92 0.004 286.32);
+ --input: oklch(0.92 0.004 286.32);
+ --ring: oklch(0.705 0.015 286.067);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
+ --sidebar-primary: oklch(0.21 0.006 285.885);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.967 0.001 286.375);
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
+ --sidebar-border: oklch(0.92 0.004 286.32);
+ --sidebar-ring: oklch(0.705 0.015 286.067);
+}
+
+.dark {
+ --background: oklch(0.141 0.005 285.823);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.21 0.006 285.885);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.21 0.006 285.885);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.92 0.004 286.32);
+ --primary-foreground: oklch(0.21 0.006 285.885);
+ --secondary: oklch(0.274 0.006 286.033);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.274 0.006 286.033);
+ --muted-foreground: oklch(0.705 0.015 286.067);
+ --accent: oklch(0.274 0.006 286.033);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.552 0.016 285.938);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.21 0.006 285.885);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.274 0.006 286.033);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.552 0.016 285.938);
+}
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/web/src/routes/servers/[alias]/+page.svelte b/web/src/routes/servers/[alias]/+page.svelte
new file mode 100644
index 0000000..0ac4a8b
--- /dev/null
+++ b/web/src/routes/servers/[alias]/+page.svelte
@@ -0,0 +1,123 @@
+
+
+
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else}
+ {#if health?.status === 'ok'}
+
+ Telemt отвечает
+
+ read_only: {health.read_only ? 'да' : 'нет'}
+
+
+ {/if}
+
+
+
+
+ Аптайм
+
+ {sys ? formatDurationSeconds(sys.uptime_seconds) : '—'}
+
+
+
+
+
+ Соединения (всего)
+ {summary?.connections_total ?? '—'}
+
+
+
+
+ Bad connections
+ {summary?.connections_bad_total ?? '—'}
+
+
+
+
+ Пользователей в конфиге
+ {summary?.configured_users ?? '—'}
+
+
+
+
+ {#if sys}
+
+
+ Система
+
+
+ version {sys.version}
+ OS / arch {sys.target_os} / {sys.target_arch}
+ build {sys.build_profile}
+
+ config {sys.config_path}
+
+
+ hash {sys.config_hash}
+
+ reload count {sys.config_reload_count}
+
+
+ {/if}
+{/if}
diff --git a/web/src/routes/servers/[alias]/config/+page.svelte b/web/src/routes/servers/[alias]/config/+page.svelte
new file mode 100644
index 0000000..e975a00
--- /dev/null
+++ b/web/src/routes/servers/[alias]/config/+page.svelte
@@ -0,0 +1,25 @@
+
+
+
+
Configuration
+
+ Конфигурация Telemt на ноде {alias} не доступна через API
+ — правьте config.toml на сервере.
+
+
+
+
+ См. docs/API.md
+ (Telemt Control API).
+
+
diff --git a/web/src/routes/servers/[alias]/runtime/+page.svelte b/web/src/routes/servers/[alias]/runtime/+page.svelte
new file mode 100644
index 0000000..46a00f4
--- /dev/null
+++ b/web/src/routes/servers/[alias]/runtime/+page.svelte
@@ -0,0 +1,125 @@
+
+
+
+
Runtime
+
+
+ Обновить
+
+
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else}
+
+ {#if gates}
+ accepting: {onOff(gates.accepting_new_connections)}
+ conditional cast: {onOff(gates.conditional_cast_enabled)}
+ me2dc fallback: {onOff(gates.me2dc_fallback_enabled)}
+ ME ready: {onOff(gates.me_runtime_ready)}
+ middle proxy: {onOff(gates.use_middle_proxy)}
+ {/if}
+
+
+ {#if dcs && dcs.middle_proxy_enabled && dcs.dcs}
+
+
+ Datacenter status
+
+
+
+
+
+ DC
+ RTT
+ Writers
+ Coverage
+
+
+
+ {#each dcs.dcs as dc (dc.dc)}
+
+ DC {dc.dc}
+
+ {dc.rtt_ms != null ? `${dc.rtt_ms.toFixed(1)} ms` : '—'}
+
+
+ {dc.alive_writers} / {dc.required_writers}
+
+ {dc.coverage_pct.toFixed(1)}%
+
+ {/each}
+
+
+
+
+ {:else if dcs?.reason}
+
+ DC status: {dcs.reason}
+
+ {/if}
+
+
+
+ ME pool state
+ Сырой JSON
+
+
+ {JSON.stringify(
+ pool,
+ null,
+ 2
+ )}
+
+
+{/if}
diff --git a/web/src/routes/servers/[alias]/security/+page.svelte b/web/src/routes/servers/[alias]/security/+page.svelte
new file mode 100644
index 0000000..d70042a
--- /dev/null
+++ b/web/src/routes/servers/[alias]/security/+page.svelte
@@ -0,0 +1,57 @@
+
+
+
+
Security
+
+
+ Обновить
+
+
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else if data}
+
+
+ {JSON.stringify(
+ data,
+ null,
+ 2
+ )}
+
+
+{/if}
diff --git a/web/src/routes/servers/[alias]/update/+page.svelte b/web/src/routes/servers/[alias]/update/+page.svelte
new file mode 100644
index 0000000..0d68b0d
--- /dev/null
+++ b/web/src/routes/servers/[alias]/update/+page.svelte
@@ -0,0 +1,60 @@
+
+
+
+
Update
+
+
+ Обновить
+
+
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else if sys}
+
+
+ Версия бинарника
+
+
+ version {sys.version}
+ git {sys.git_commit ?? '—'}
+ build {sys.build_time_utc ?? '—'}
+ rustc {sys.rustc_version ?? '—'}
+
+
+{/if}
diff --git a/web/src/routes/servers/[alias]/upstreams/+page.svelte b/web/src/routes/servers/[alias]/upstreams/+page.svelte
new file mode 100644
index 0000000..0379e5b
--- /dev/null
+++ b/web/src/routes/servers/[alias]/upstreams/+page.svelte
@@ -0,0 +1,57 @@
+
+
+
+
Upstreams & DCs
+
+
+ Обновить
+
+
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else if data}
+
+
+ {JSON.stringify(
+ data,
+ null,
+ 2
+ )}
+
+
+{/if}
diff --git a/web/src/routes/servers/[alias]/users/+page.svelte b/web/src/routes/servers/[alias]/users/+page.svelte
new file mode 100644
index 0000000..f69d454
--- /dev/null
+++ b/web/src/routes/servers/[alias]/users/+page.svelte
@@ -0,0 +1,319 @@
+
+
+
+
Users
+
+
(createOpen = true)} disabled={readOnly}>
+
+ Создать
+
+
+
+ Обновить
+
+
+
+
+{#if readOnly}
+
+ API только для чтения
+ Изменения на этой ноде отключены.
+
+{/if}
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading && users.length === 0}
+ Загрузка…
+{:else}
+
+
+
+
+
+ Имя
+ Ссылки
+ Conn
+ IP
+ Трафик
+
+
+
+
+ {#each users as u (u.username)}
+
+ {u.username}
+
+
+ {#each u.links?.tls ?? [] as link (link)}
+ copy(link)}>
+ TLS
+
+ {/each}
+
+
+ {u.current_connections}
+
+ {u.active_unique_ips}
+ {#if u.max_unique_ips != null}
+ / {u.max_unique_ips}
+ {/if}
+
+ {formatBytes(u.total_octets)}
+
+ openEdit(u)}
+ >
+
+
+ (deleteUserRow = u)}
+ >
+
+
+
+
+ {/each}
+
+
+
+
+{/if}
+
+
+
+
+ Новый пользователь
+ POST /v1/users на ноде {alias}
+
+
+
+ Имя
+
+
+
+ Секрет (32 hex, опционально)
+
+
+ {#if formErr}
+
{formErr}
+ {/if}
+
+
+ (createOpen = false)}>Отмена
+ Создать
+
+
+
+
+ !o && (editUser = null)}>
+
+
+ Редактировать {editUser?.username}
+
+
+
+ (editUser = null)}>Отмена
+ Сохранить
+
+
+
+
+ !o && (deleteUserRow = null)}>
+
+
+ Удалить {deleteUserRow?.username}?
+ DELETE /v1/users на ноде {alias}
+
+ {#if formErr}
+ {formErr}
+ {/if}
+
+ (deleteUserRow = null)}>Отмена
+ Удалить
+
+
+
diff --git a/web/src/routes/users/+page.svelte b/web/src/routes/users/+page.svelte
new file mode 100644
index 0000000..09e9871
--- /dev/null
+++ b/web/src/routes/users/+page.svelte
@@ -0,0 +1,133 @@
+
+
+
+
+
Пользователи (флот)
+
+ Слияние по имени между серверами; одинаковое имя может быть разной учёткой.
+
+
+
+
+
+ Ссылки tg://proxy
+
+
+
+ Обновить
+
+
+
+
+{#if partial}
+
+ Частичные данные
+ Не все upstream ответили успешно.
+
+{/if}
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading && rows.length === 0}
+ Загрузка…
+{:else}
+
+
+
+
+
+ Имя
+ Ссылки
+ Трафик
+ IP
+ Квота
+ Истекает
+
+
+
+ {#each rows as row (row.username)}
+
+
+
+ {row.username}
+
+
+
+
+ {#each row.links?.tls ?? [] as link (link)}
+ copy(link)}>
+ TLS
+
+
+ {/each}
+ {#each row.links?.secure ?? [] as link (link)}
+ copy(link)}>
+ t.me
+
+
+ {/each}
+
+
+ {formatMiB(row.total_megabytes ?? 0)}
+
+ {row.active_unique_ips ?? 0}
+ {#if row.max_unique_ips != null}
+ / {row.max_unique_ips}
+ {/if}
+
+
+ {row.data_quota_bytes != null ? formatBytes(row.data_quota_bytes) : '—'}
+
+
+ {row.expiration_rfc3339
+ ? new Date(row.expiration_rfc3339).toLocaleString()
+ : '—'}
+
+
+ {/each}
+
+
+
+
+{/if}
diff --git a/web/src/routes/users/[username]/+page.svelte b/web/src/routes/users/[username]/+page.svelte
new file mode 100644
index 0000000..aa9f0b3
--- /dev/null
+++ b/web/src/routes/users/[username]/+page.svelte
@@ -0,0 +1,128 @@
+
+
+
+
Пользователь
+
{username}
+
+
+{#if partial}
+
+ Частичные данные
+
+{/if}
+
+{#if err}
+
+ Ошибка
+ {err}
+
+{:else if loading}
+ Загрузка…
+{:else if row}
+
+
+
+ Лимиты (смержены)
+
+
+
+ Трафик (сумма)
+ {formatMiB(row.total_megabytes ?? 0)}
+
+
+ Активных / недавних IP
+ {row.active_unique_ips ?? 0} / {row.recent_unique_ips ?? 0}
+
+
+ max_unique_ips
+ {row.max_unique_ips ?? '—'}
+
+
+ max_tcp_conns
+ {row.max_tcp_conns ?? '—'}
+
+
+ Квота
+ {row.data_quota_bytes != null ? formatBytes(row.data_quota_bytes) : '—'}
+
+
+ Истекает
+ {row.expiration_rfc3339 ?? '—'}
+
+
+
+
+
+ По серверам
+
+
+ {#each Object.entries(row.by_server ?? {}) as [alias, st] (alias)}
+
+ {alias}
+
+ {formatMiB(st.total_megabytes ?? 0)} · conn {st.current_connections ?? 0}
+
+
+ {/each}
+
+
+
+
+
+ Ссылки
+
+
+ {#each row.links?.tls ?? [] as link (link)}
+ copy(link)}>
+ TLS
+
+
+ {/each}
+ {#each row.links?.secure ?? [] as link (link)}
+ copy(link)}>
+ secure
+
+
+ {/each}
+
+
+{/if}
diff --git a/web/static/robots.txt b/web/static/robots.txt
new file mode 100644
index 0000000..b6dd667
--- /dev/null
+++ b/web/static/robots.txt
@@ -0,0 +1,3 @@
+# allow crawling everything by default
+User-agent: *
+Disallow:
diff --git a/web/svelte.config.js b/web/svelte.config.js
new file mode 100644
index 0000000..8adac92
--- /dev/null
+++ b/web/svelte.config.js
@@ -0,0 +1,23 @@
+import adapter from '@sveltejs/adapter-static';
+import { relative, sep } from 'node:path';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ compilerOptions: {
+ // defaults to rune mode for the project, execept for `node_modules`. Can be removed in svelte 6.
+ runes: ({ filename }) => {
+ const relativePath = relative(import.meta.dirname, filename);
+ const pathSegments = relativePath.toLowerCase().split(sep);
+ const isExternalLibrary = pathSegments.includes('node_modules');
+
+ return isExternalLibrary ? undefined : true;
+ }
+ },
+ kit: {
+ adapter: adapter({
+ fallback: 'index.html'
+ })
+ }
+};
+
+export default config;
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..2c2ed3c
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "rewriteRelativeImportExtensions": true,
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler"
+ }
+ // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
+ // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
+ //
+ // To make changes to top-level options such as include and exclude, we recommend extending
+ // the generated config; see https://svelte.dev/docs/kit/configuration#typescript
+}
diff --git a/web/vite.config.ts b/web/vite.config.ts
new file mode 100644
index 0000000..56f40c7
--- /dev/null
+++ b/web/vite.config.ts
@@ -0,0 +1,5 @@
+import tailwindcss from '@tailwindcss/vite';
+import { sveltekit } from '@sveltejs/kit/vite';
+import { defineConfig } from 'vite';
+
+export default defineConfig({ plugins: [tailwindcss(), sveltekit()] });