Initial project snapshot
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Platform Owner Control Panel
|
||||
|
||||
Primary Web/Admin shell for Secure Access Fabric platform-owner operations.
|
||||
|
||||
This UI is for product owner / platform owner scope. It is not the future
|
||||
organization admin panel and must not expose full internal mesh topology to
|
||||
organization users.
|
||||
|
||||
Architecture boundary:
|
||||
|
||||
- WEB is HTTP/HTTPS ingress and presentation.
|
||||
- Cluster configuration belongs to Control Plane APIs.
|
||||
- PostgreSQL remains the source of truth.
|
||||
- Dynamic admin pages must be safe, scoped, schema-driven projections.
|
||||
- Secrets, internal topology, peer caches, route caches, and raw credentials
|
||||
must not be embedded in pages.
|
||||
|
||||
See:
|
||||
|
||||
- `docs/architecture/WEB_INGRESS_AND_ADMIN_UI_MODEL.md`
|
||||
- `docs/architecture/CLUSTER_NODE_ADMIN_FOUNDATION.md`
|
||||
|
||||
## Current Scope
|
||||
|
||||
Implemented platform-owner sections:
|
||||
|
||||
- command overview
|
||||
- installation status and first-owner bootstrap screen backed by signed
|
||||
Product Root activation manifests
|
||||
- multi-cluster health posture
|
||||
- cluster creation
|
||||
- cluster authority/mutation guard with cluster key fingerprint visibility
|
||||
- node inventory
|
||||
- node membership disable and identity revoke boundaries
|
||||
- join token creation with signed authority envelope visibility
|
||||
- join request approve/reject with signed approval envelope visibility
|
||||
- role assignment
|
||||
- workload desired-state setting
|
||||
- workload status readout
|
||||
- mesh preparation observations
|
||||
- node-scoped synthetic mesh config visibility, including endpoint candidate,
|
||||
peer directory, recovery seed, rendezvous lease counts, and C17X advisory
|
||||
scoring boundary, plus cluster authority signature status
|
||||
- C17Z-C17Z18 production `fabric.control` forwarding/config/runtime,
|
||||
rendezvous/relay control-plane boundary, rendezvous lease telemetry, and
|
||||
lease refresh plus relay replacement policy, route/path decision visibility,
|
||||
route generation tracker status visibility, and synthetic route-health
|
||||
effective-path visibility
|
||||
- QoS foundation readout
|
||||
- VPN/IP tunnel control-plane desired state
|
||||
- stale VPN lease cleanup action
|
||||
- organization-safe summary preview
|
||||
- cluster audit
|
||||
|
||||
Not implemented:
|
||||
|
||||
- organization admin UI as a separate tenant product surface
|
||||
- production mesh runtime
|
||||
- VPN/IP tunnel runtime
|
||||
- TUN/TAP, route, DNS, firewall, or QoS execution
|
||||
- RDP runtime changes
|
||||
- arbitrary plugin JavaScript or dynamic executable pages
|
||||
- direct database mutation from WEB
|
||||
|
||||
## Build
|
||||
|
||||
```powershell
|
||||
cd web-admin
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Run Locally
|
||||
|
||||
```powershell
|
||||
cd web-admin
|
||||
npm run dev -- --port 5173
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:5173
|
||||
```
|
||||
|
||||
The admin console should run on a dedicated admin host/port. It is not intended
|
||||
to be the public product landing page on generic `80/443` web ingress.
|
||||
|
||||
Default backend API inside the panel:
|
||||
|
||||
```text
|
||||
/api/v1
|
||||
```
|
||||
|
||||
The local Vite dev server proxies `/api` to the remote test backend
|
||||
`http://192.168.200.61:8080` by default, avoiding browser CORS issues while
|
||||
keeping the Control Plane API unchanged. Override the proxy target when needed:
|
||||
|
||||
```powershell
|
||||
$env:RAP_ADMIN_API_PROXY = "http://192.168.200.61:8080"
|
||||
npm run dev -- --port 5173
|
||||
```
|
||||
|
||||
The start screen shows installation authority status. If the backend reports an
|
||||
unbootstrapped installation, it switches to the first-owner form and accepts a
|
||||
signed activation manifest plus signature. Otherwise it shows only login and
|
||||
password fields; it does not expose API URLs or language/settings to
|
||||
unauthenticated users.
|
||||
|
||||
After authentication the panel verifies platform-owner/platform-admin access
|
||||
through Control Plane APIs before opening the console. Users without product-owner
|
||||
scope must not see this panel. Organization admins and organization users require
|
||||
separate scoped panels.
|
||||
|
||||
Language selection is available only after login in the profile area. It is stored
|
||||
as a user-scoped browser preference for this MVP. Backend user-profile persistence
|
||||
for language/locale is a later Control Plane profile setting.
|
||||
|
||||
The panel shows real Control Plane data only. If cluster counts are zero, the
|
||||
cluster has no approved node-agent nodes, roles, workloads, VPN records, or mesh
|
||||
observations yet.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- The console is platform-owner/platform-admin only.
|
||||
- Capabilities are technical facts only.
|
||||
- Roles are explicit policy assignments.
|
||||
- Organization topology is intentionally not shown.
|
||||
- VPN records are desired state only; no runtime tunnel is started here.
|
||||
- Synthetic mesh config visibility is platform-owner scope only. Endpoint
|
||||
candidates and health-aware scoring remain advisory and do not start
|
||||
service traffic forwarding.
|
||||
- Rendezvous leases are control-plane-only route metadata for relay health
|
||||
paths; they do not enable payload forwarding.
|
||||
- Relay replacement policy summaries are platform-owner control-plane
|
||||
visibility only; they do not publish organization topology or start payload
|
||||
forwarding.
|
||||
- Route/path decision summaries are also platform-owner control-plane
|
||||
visibility only in synthetic config and recent heartbeat tables; effective
|
||||
hops are planning metadata, not payload routing.
|
||||
- Route-health rows in Fabric links show synthetic control-plane probes,
|
||||
selected relay, expected/observed hops, and drift status. They are not
|
||||
service payload routes.
|
||||
- C17Z production forwarding is limited to route-bound `fabric.control` direct
|
||||
next-hop delivery behind an explicit node-agent gate. RDP, VPN, file, video,
|
||||
and service workload payloads remain out of scope.
|
||||
- High-risk actions should require backend authorization, audit, and future
|
||||
step-up authentication.
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Панель Secure Access Fabric</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+962
@@ -0,0 +1,962 @@
|
||||
{
|
||||
"name": "@rap/web-admin",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@rap/web-admin",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"typescript": "latest",
|
||||
"vite": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "latest",
|
||||
"@types/react-dom": "latest"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.127.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
||||
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.7",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
|
||||
"integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
|
||||
"integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "1.0.0-rc.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@rolldown/plugin-babel": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"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/lightningcss": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"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"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"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/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"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/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.12",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
|
||||
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
|
||||
"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/react": {
|
||||
"version": "19.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
||||
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
|
||||
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.127.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.17"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
||||
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
||||
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.10",
|
||||
"rolldown": "1.0.0-rc.17",
|
||||
"tinyglobby": "^0.2.16"
|
||||
},
|
||||
"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",
|
||||
"@vitejs/devtools": "^0.1.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.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
|
||||
},
|
||||
"@vitejs/devtools": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@rap/web-admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
"typescript": "latest",
|
||||
"vite": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "latest",
|
||||
"@types/react-dom": "latest"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,609 @@
|
||||
import type {
|
||||
AuditEvent,
|
||||
AuthResult,
|
||||
BootstrapOwnerResult,
|
||||
Cluster,
|
||||
ClusterNodeGroup,
|
||||
ClusterAdminSummary,
|
||||
ClusterAuthorityState,
|
||||
ClusterNode,
|
||||
CreatedJoinToken,
|
||||
FabricEntryPoint,
|
||||
FabricEntryPointNode,
|
||||
FabricEgressPool,
|
||||
FabricEgressPoolNode,
|
||||
FabricTestingFlag,
|
||||
InstallationStatus,
|
||||
JoinRequest,
|
||||
MeshLink,
|
||||
NodeHeartbeat,
|
||||
NodeSyntheticMeshConfig,
|
||||
NodeTelemetryObservation,
|
||||
NodeWorkloadDesiredState,
|
||||
OrganizationAdminSummary,
|
||||
QoSPolicy,
|
||||
RoleAssignment,
|
||||
VPNConnection,
|
||||
VPNConnectionLease,
|
||||
WorkloadStatus,
|
||||
} from "../types";
|
||||
|
||||
export type AdminClientConfig = {
|
||||
baseUrl: string;
|
||||
actorUserId: string;
|
||||
};
|
||||
|
||||
type ApiErrorPayload = {
|
||||
error?: {
|
||||
code?: string;
|
||||
message_key?: string;
|
||||
fallback_message?: string;
|
||||
trace_id?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CreateClusterPayload = {
|
||||
slug: string;
|
||||
name: string;
|
||||
region?: string | null;
|
||||
};
|
||||
|
||||
export type UpdateClusterPayload = {
|
||||
name: string;
|
||||
status: string;
|
||||
region?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type CreateVPNConnectionPayload = {
|
||||
organizationId: string;
|
||||
name: string;
|
||||
protocolFamily: string;
|
||||
targetEndpoint: Record<string, unknown>;
|
||||
credentialRef?: string | null;
|
||||
desiredState: string;
|
||||
allowedNodePolicy: Record<string, unknown>;
|
||||
routingUsage: unknown[];
|
||||
routePolicy: Record<string, unknown>;
|
||||
qosPolicy: Record<string, unknown>;
|
||||
placementPolicy: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type CreateFabricEntryPointPayload = {
|
||||
name: string;
|
||||
endpointType: string;
|
||||
publicEndpoint?: string | null;
|
||||
policy?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type CreateFabricEgressPoolPayload = {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
routeScope?: Record<string, unknown>;
|
||||
policy?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type SetFabricEndpointNodePayload = {
|
||||
status?: string;
|
||||
priority?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export class AdminApiClient {
|
||||
private readonly baseUrl: string;
|
||||
private readonly actorUserId: string;
|
||||
|
||||
constructor(config: AdminClientConfig) {
|
||||
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
||||
this.actorUserId = config.actorUserId.trim();
|
||||
}
|
||||
|
||||
async login(input: { email: string; password: string; deviceLabel: string; trustDevice: boolean }): Promise<AuthResult> {
|
||||
return this.post<AuthResult>("/auth/login", {
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
device_fingerprint: browserDeviceFingerprint(),
|
||||
device_label: input.deviceLabel,
|
||||
trust_device: input.trustDevice,
|
||||
});
|
||||
}
|
||||
|
||||
async getInstallationStatus(): Promise<InstallationStatus> {
|
||||
const payload = await this.request<{ installation: InstallationStatus }>("/installation/status", {
|
||||
method: "GET",
|
||||
});
|
||||
return payload.installation;
|
||||
}
|
||||
|
||||
async bootstrapOwner(input: {
|
||||
email: string;
|
||||
password: string;
|
||||
activationPayload?: unknown;
|
||||
activationSignature?: string;
|
||||
}): Promise<BootstrapOwnerResult> {
|
||||
return this.post<BootstrapOwnerResult>("/installation/bootstrap-owner", {
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
activation_payload: input.activationPayload,
|
||||
activation_signature: input.activationSignature || "",
|
||||
});
|
||||
}
|
||||
|
||||
async revokeAuthSession(input: { userId: string; authSessionId: string; reason: string }): Promise<void> {
|
||||
await this.post("/auth/sessions/revoke", {
|
||||
user_id: input.userId,
|
||||
auth_session_id: input.authSessionId,
|
||||
reason: input.reason,
|
||||
});
|
||||
}
|
||||
|
||||
async listClusters(): Promise<Cluster[]> {
|
||||
const payload = await this.get<{ clusters: Cluster[] }>("/clusters");
|
||||
return payload.clusters ?? [];
|
||||
}
|
||||
|
||||
async createCluster(input: CreateClusterPayload): Promise<Cluster> {
|
||||
const payload = await this.post<{ cluster: Cluster }>("/clusters/", {
|
||||
actor_user_id: this.actorUserId,
|
||||
slug: input.slug,
|
||||
name: input.name,
|
||||
region: input.region || null,
|
||||
metadata: {},
|
||||
});
|
||||
return payload.cluster;
|
||||
}
|
||||
|
||||
async updateCluster(clusterId: string, input: UpdateClusterPayload): Promise<Cluster> {
|
||||
const payload = await this.put<{ cluster: Cluster }>(`/clusters/${clusterId}`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
name: input.name,
|
||||
status: input.status,
|
||||
region: input.region || null,
|
||||
metadata: input.metadata || {},
|
||||
});
|
||||
return payload.cluster;
|
||||
}
|
||||
|
||||
async listClusterSummaries(): Promise<ClusterAdminSummary[]> {
|
||||
const payload = await this.get<{ cluster_summaries: ClusterAdminSummary[] }>("/cluster-admin-summaries");
|
||||
return payload.cluster_summaries ?? [];
|
||||
}
|
||||
|
||||
async getClusterAuthority(clusterId: string): Promise<ClusterAuthorityState> {
|
||||
const payload = await this.get<{ authority_state: ClusterAuthorityState }>(`/clusters/${clusterId}/authority`);
|
||||
return payload.authority_state;
|
||||
}
|
||||
|
||||
async updateClusterAuthority(
|
||||
clusterId: string,
|
||||
input: { authorityState: string; mutationMode: string; notes?: string | null },
|
||||
): Promise<ClusterAuthorityState> {
|
||||
const payload = await this.put<{ authority_state: ClusterAuthorityState }>(`/clusters/${clusterId}/authority`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
authority_state: input.authorityState,
|
||||
mutation_mode: input.mutationMode,
|
||||
notes: input.notes || null,
|
||||
});
|
||||
return payload.authority_state;
|
||||
}
|
||||
|
||||
async listNodes(clusterId: string): Promise<ClusterNode[]> {
|
||||
const payload = await this.get<{ nodes: ClusterNode[] }>(`/clusters/${clusterId}/nodes`);
|
||||
return payload.nodes ?? [];
|
||||
}
|
||||
|
||||
async listNodeGroups(clusterId: string): Promise<ClusterNodeGroup[]> {
|
||||
const payload = await this.get<{ node_groups: ClusterNodeGroup[] }>(`/clusters/${clusterId}/node-groups`);
|
||||
return payload.node_groups ?? [];
|
||||
}
|
||||
|
||||
async createNodeGroup(
|
||||
clusterId: string,
|
||||
input: { name: string; parentGroupId?: string | null; description?: string | null; sortOrder?: number },
|
||||
): Promise<ClusterNodeGroup> {
|
||||
const payload = await this.post<{ node_group: ClusterNodeGroup }>(`/clusters/${clusterId}/node-groups`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
parent_group_id: input.parentGroupId || null,
|
||||
name: input.name,
|
||||
description: input.description || null,
|
||||
sort_order: input.sortOrder || 0,
|
||||
metadata: {},
|
||||
});
|
||||
return payload.node_group;
|
||||
}
|
||||
|
||||
async assignNodeGroup(clusterId: string, nodeId: string, groupId?: string | null): Promise<ClusterNode> {
|
||||
const payload = await this.put<{ node: ClusterNode }>(`/clusters/${clusterId}/nodes/${nodeId}/group`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
group_id: groupId || null,
|
||||
});
|
||||
return payload.node;
|
||||
}
|
||||
|
||||
async disableMembership(clusterId: string, nodeId: string, reason: string): Promise<void> {
|
||||
await this.post(`/clusters/${clusterId}/nodes/${nodeId}/membership/disable`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
async attachExistingNode(clusterId: string, nodeId: string, roles: string[]): Promise<ClusterNode> {
|
||||
const payload = await this.post<{ node: ClusterNode }>(`/clusters/${clusterId}/nodes/${nodeId}/membership/attach`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
roles,
|
||||
});
|
||||
return payload.node;
|
||||
}
|
||||
|
||||
async revokeNodeIdentity(clusterId: string, nodeId: string, reason: string): Promise<void> {
|
||||
await this.post(`/clusters/${clusterId}/nodes/${nodeId}/identity/revoke`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
async listJoinRequests(clusterId: string): Promise<JoinRequest[]> {
|
||||
const payload = await this.get<{ join_requests: JoinRequest[] }>(`/clusters/${clusterId}/join-requests`);
|
||||
return payload.join_requests ?? [];
|
||||
}
|
||||
|
||||
async createJoinToken(clusterId: string, input: { maxUses: number; ttlHours: number; scope: Record<string, unknown> }): Promise<CreatedJoinToken> {
|
||||
const expiresAt = new Date(Date.now() + Math.max(input.ttlHours, 1) * 60 * 60 * 1000).toISOString();
|
||||
const payload = await this.post<{ join_token: CreatedJoinToken }>(`/clusters/${clusterId}/join-tokens`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
scope: input.scope,
|
||||
expires_at: expiresAt,
|
||||
max_uses: Math.max(input.maxUses, 1),
|
||||
});
|
||||
return payload.join_token;
|
||||
}
|
||||
|
||||
async approveJoinRequest(clusterId: string, requestId: string): Promise<void> {
|
||||
await this.post(`/clusters/${clusterId}/join-requests/${requestId}/approve`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
ownership_type: "platform_managed",
|
||||
});
|
||||
}
|
||||
|
||||
async rejectJoinRequest(clusterId: string, requestId: string, reason: string): Promise<void> {
|
||||
await this.post(`/clusters/${clusterId}/join-requests/${requestId}/reject`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
async listNodeRoles(clusterId: string, nodeId: string): Promise<RoleAssignment[]> {
|
||||
const payload = await this.get<{ role_assignments: RoleAssignment[] }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/roles`,
|
||||
);
|
||||
return payload.role_assignments ?? [];
|
||||
}
|
||||
|
||||
async assignRole(clusterId: string, nodeId: string, role: string, organizationId?: string): Promise<void> {
|
||||
await this.setRoleStatus(clusterId, nodeId, role, "active", organizationId);
|
||||
}
|
||||
|
||||
async setRoleStatus(clusterId: string, nodeId: string, role: string, status: "active" | "disabled" | "revoked", organizationId?: string): Promise<void> {
|
||||
await this.post(`/clusters/${clusterId}/nodes/${nodeId}/roles`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
organization_id: organizationId || null,
|
||||
role,
|
||||
status,
|
||||
policy: {},
|
||||
});
|
||||
}
|
||||
|
||||
async listWorkloadStatuses(clusterId: string, nodeId: string): Promise<WorkloadStatus[]> {
|
||||
const payload = await this.get<{ workload_statuses: WorkloadStatus[] }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/workloads/status`,
|
||||
);
|
||||
return payload.workload_statuses ?? [];
|
||||
}
|
||||
|
||||
async listDesiredWorkloads(clusterId: string, nodeId: string): Promise<NodeWorkloadDesiredState[]> {
|
||||
const payload = await this.get<{ desired_workloads: NodeWorkloadDesiredState[] }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/workloads/desired`,
|
||||
);
|
||||
return payload.desired_workloads ?? [];
|
||||
}
|
||||
|
||||
async listNodeHeartbeats(clusterId: string, nodeId: string, limit = 100): Promise<NodeHeartbeat[]> {
|
||||
const payload = await this.get<{ heartbeats: NodeHeartbeat[] }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/heartbeats?limit=${limit}`,
|
||||
);
|
||||
return payload.heartbeats ?? [];
|
||||
}
|
||||
|
||||
async listNodeTelemetry(clusterId: string, nodeId: string, limit = 120): Promise<NodeTelemetryObservation[]> {
|
||||
const payload = await this.get<{ telemetry: NodeTelemetryObservation[] }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/telemetry?limit=${limit}`,
|
||||
);
|
||||
return payload.telemetry ?? [];
|
||||
}
|
||||
|
||||
async listFabricTestingFlags(): Promise<FabricTestingFlag[]> {
|
||||
const payload = await this.get<{ testing_flags: FabricTestingFlag[] }>("/fabric/testing-flags");
|
||||
return payload.testing_flags ?? [];
|
||||
}
|
||||
|
||||
async updateFabricTestingFlag(input: {
|
||||
scopeType: string;
|
||||
scopeId?: string | null;
|
||||
clusterId?: string | null;
|
||||
enabled: boolean;
|
||||
telemetryEnabled: boolean;
|
||||
syntheticLinksEnabled: boolean;
|
||||
historyRetentionHours: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
}): Promise<FabricTestingFlag> {
|
||||
const payload = await this.put<{ testing_flag: FabricTestingFlag }>("/fabric/testing-flags", {
|
||||
actor_user_id: this.actorUserId,
|
||||
scope_type: input.scopeType,
|
||||
scope_id: input.scopeId || null,
|
||||
cluster_id: input.clusterId || null,
|
||||
enabled: input.enabled,
|
||||
telemetry_enabled: input.telemetryEnabled,
|
||||
synthetic_links_enabled: input.syntheticLinksEnabled,
|
||||
history_retention_hours: input.historyRetentionHours,
|
||||
metadata: input.metadata || {},
|
||||
});
|
||||
return payload.testing_flag;
|
||||
}
|
||||
|
||||
async setDesiredWorkload(
|
||||
clusterId: string,
|
||||
nodeId: string,
|
||||
serviceType: string,
|
||||
input: { desiredState: string; runtimeMode: string; version?: string; config: Record<string, unknown>; environment: Record<string, unknown> },
|
||||
): Promise<void> {
|
||||
await this.put(`/clusters/${clusterId}/nodes/${nodeId}/workloads/${serviceType}/desired`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
desired_state: input.desiredState,
|
||||
version: input.version || null,
|
||||
runtime_mode: input.runtimeMode,
|
||||
artifact_ref: null,
|
||||
config: input.config,
|
||||
environment: input.environment,
|
||||
});
|
||||
}
|
||||
|
||||
async listMeshLinks(clusterId: string): Promise<MeshLink[]> {
|
||||
const payload = await this.get<{ mesh_links: MeshLink[] }>(`/clusters/${clusterId}/mesh/links`);
|
||||
return payload.mesh_links ?? [];
|
||||
}
|
||||
|
||||
async getNodeSyntheticMeshConfig(clusterId: string, nodeId: string): Promise<NodeSyntheticMeshConfig> {
|
||||
const payload = await this.get<{ synthetic_mesh_config: NodeSyntheticMeshConfig }>(
|
||||
`/clusters/${clusterId}/nodes/${nodeId}/mesh/synthetic-config`,
|
||||
);
|
||||
return payload.synthetic_mesh_config;
|
||||
}
|
||||
|
||||
async listQoSPolicies(clusterId: string): Promise<QoSPolicy[]> {
|
||||
const payload = await this.get<{ qos_policies: QoSPolicy[] }>(`/clusters/${clusterId}/mesh/qos-policies`);
|
||||
return payload.qos_policies ?? [];
|
||||
}
|
||||
|
||||
async listFabricEntryPoints(clusterId: string): Promise<FabricEntryPoint[]> {
|
||||
const payload = await this.get<{ entry_points: FabricEntryPoint[] }>(`/clusters/${clusterId}/fabric/entry-points`);
|
||||
return payload.entry_points ?? [];
|
||||
}
|
||||
|
||||
async createFabricEntryPoint(clusterId: string, input: CreateFabricEntryPointPayload): Promise<FabricEntryPoint> {
|
||||
const payload = await this.post<{ entry_point: FabricEntryPoint }>(`/clusters/${clusterId}/fabric/entry-points`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
name: input.name,
|
||||
status: "active",
|
||||
endpoint_type: input.endpointType || "client_access",
|
||||
public_endpoint: input.publicEndpoint || null,
|
||||
policy: input.policy || {},
|
||||
metadata: input.metadata || {},
|
||||
});
|
||||
return payload.entry_point;
|
||||
}
|
||||
|
||||
async listFabricEntryPointNodes(clusterId: string, entryPointId: string): Promise<FabricEntryPointNode[]> {
|
||||
const payload = await this.get<{ entry_point_nodes: FabricEntryPointNode[] }>(
|
||||
`/clusters/${clusterId}/fabric/entry-points/${entryPointId}/nodes`,
|
||||
);
|
||||
return payload.entry_point_nodes ?? [];
|
||||
}
|
||||
|
||||
async setFabricEntryPointNode(
|
||||
clusterId: string,
|
||||
entryPointId: string,
|
||||
nodeId: string,
|
||||
input: SetFabricEndpointNodePayload = {},
|
||||
): Promise<FabricEntryPointNode> {
|
||||
const payload = await this.put<{ entry_point_node: FabricEntryPointNode }>(
|
||||
`/clusters/${clusterId}/fabric/entry-points/${entryPointId}/nodes/${nodeId}`,
|
||||
{
|
||||
actor_user_id: this.actorUserId,
|
||||
status: input.status || "active",
|
||||
priority: input.priority || 100,
|
||||
metadata: input.metadata || {},
|
||||
},
|
||||
);
|
||||
return payload.entry_point_node;
|
||||
}
|
||||
|
||||
async listFabricEgressPools(clusterId: string): Promise<FabricEgressPool[]> {
|
||||
const payload = await this.get<{ egress_pools: FabricEgressPool[] }>(`/clusters/${clusterId}/fabric/egress-pools`);
|
||||
return payload.egress_pools ?? [];
|
||||
}
|
||||
|
||||
async createFabricEgressPool(clusterId: string, input: CreateFabricEgressPoolPayload): Promise<FabricEgressPool> {
|
||||
const payload = await this.post<{ egress_pool: FabricEgressPool }>(`/clusters/${clusterId}/fabric/egress-pools`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
name: input.name,
|
||||
status: "active",
|
||||
description: input.description || null,
|
||||
route_scope: input.routeScope || {},
|
||||
policy: input.policy || {},
|
||||
metadata: input.metadata || {},
|
||||
});
|
||||
return payload.egress_pool;
|
||||
}
|
||||
|
||||
async listFabricEgressPoolNodes(clusterId: string, egressPoolId: string): Promise<FabricEgressPoolNode[]> {
|
||||
const payload = await this.get<{ egress_pool_nodes: FabricEgressPoolNode[] }>(
|
||||
`/clusters/${clusterId}/fabric/egress-pools/${egressPoolId}/nodes`,
|
||||
);
|
||||
return payload.egress_pool_nodes ?? [];
|
||||
}
|
||||
|
||||
async setFabricEgressPoolNode(
|
||||
clusterId: string,
|
||||
egressPoolId: string,
|
||||
nodeId: string,
|
||||
input: SetFabricEndpointNodePayload = {},
|
||||
): Promise<FabricEgressPoolNode> {
|
||||
const payload = await this.put<{ egress_pool_node: FabricEgressPoolNode }>(
|
||||
`/clusters/${clusterId}/fabric/egress-pools/${egressPoolId}/nodes/${nodeId}`,
|
||||
{
|
||||
actor_user_id: this.actorUserId,
|
||||
status: input.status || "active",
|
||||
priority: input.priority || 100,
|
||||
metadata: input.metadata || {},
|
||||
},
|
||||
);
|
||||
return payload.egress_pool_node;
|
||||
}
|
||||
|
||||
async listVPNConnections(clusterId: string): Promise<VPNConnection[]> {
|
||||
const payload = await this.get<{ vpn_connections: VPNConnection[] }>(`/clusters/${clusterId}/vpn-connections`);
|
||||
return payload.vpn_connections ?? [];
|
||||
}
|
||||
|
||||
async createVPNConnection(clusterId: string, input: CreateVPNConnectionPayload): Promise<VPNConnection> {
|
||||
const payload = await this.post<{ vpn_connection: VPNConnection }>(`/clusters/${clusterId}/vpn-connections`, {
|
||||
actor_user_id: this.actorUserId,
|
||||
organization_id: input.organizationId,
|
||||
name: input.name,
|
||||
target_endpoint: input.targetEndpoint,
|
||||
protocol_family: input.protocolFamily,
|
||||
credential_ref: input.credentialRef || null,
|
||||
mode: "single_active",
|
||||
desired_state: input.desiredState,
|
||||
allowed_node_policy: input.allowedNodePolicy,
|
||||
routing_usage: input.routingUsage,
|
||||
route_policy: input.routePolicy,
|
||||
qos_policy: input.qosPolicy,
|
||||
placement_policy: input.placementPolicy,
|
||||
metadata: {},
|
||||
});
|
||||
return payload.vpn_connection;
|
||||
}
|
||||
|
||||
async updateVPNConnectionDesiredState(clusterId: string, vpnConnectionId: string, desiredState: string): Promise<VPNConnection> {
|
||||
const payload = await this.put<{ vpn_connection: VPNConnection }>(
|
||||
`/clusters/${clusterId}/vpn-connections/${vpnConnectionId}/desired-state`,
|
||||
{
|
||||
actor_user_id: this.actorUserId,
|
||||
desired_state: desiredState,
|
||||
},
|
||||
);
|
||||
return payload.vpn_connection;
|
||||
}
|
||||
|
||||
async getActiveVPNLease(clusterId: string, vpnConnectionId: string): Promise<VPNConnectionLease | null> {
|
||||
try {
|
||||
const payload = await this.get<{ lease: VPNConnectionLease }>(
|
||||
`/clusters/${clusterId}/vpn-connections/${vpnConnectionId}/leases/active`,
|
||||
);
|
||||
return payload.lease;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async expireStaleVPNLeases(clusterId: string): Promise<VPNConnectionLease[]> {
|
||||
const payload = await this.post<{ expired_leases: VPNConnectionLease[] }>(
|
||||
`/clusters/${clusterId}/vpn-connection-leases/expire-stale`,
|
||||
{
|
||||
actor_user_id: this.actorUserId,
|
||||
},
|
||||
);
|
||||
return payload.expired_leases ?? [];
|
||||
}
|
||||
|
||||
async listAudit(clusterId: string): Promise<AuditEvent[]> {
|
||||
const payload = await this.get<{ audit_events: AuditEvent[] }>(`/clusters/${clusterId}/audit?limit=100`);
|
||||
return payload.audit_events ?? [];
|
||||
}
|
||||
|
||||
async getOrganizationAdminSummary(organizationId: string): Promise<OrganizationAdminSummary> {
|
||||
const payload = await this.get<{ admin_summary: OrganizationAdminSummary }>(
|
||||
`/organizations/${organizationId}/admin-summary`,
|
||||
);
|
||||
return payload.admin_summary;
|
||||
}
|
||||
|
||||
private async get<T>(path: string): Promise<T> {
|
||||
const separator = path.includes("?") ? "&" : "?";
|
||||
return this.request<T>(`${path}${separator}actor_user_id=${encodeURIComponent(this.actorUserId)}`, {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
private async post<T>(path: string, body: unknown): Promise<T> {
|
||||
return this.request<T>(path, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
private async put<T>(path: string, body: unknown): Promise<T> {
|
||||
return this.request<T>(path, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
private async request<T>(path: string, init: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${this.baseUrl}${path}`, init);
|
||||
if (!response.ok) {
|
||||
let message = `Запрос завершился ошибкой HTTP ${response.status}`;
|
||||
try {
|
||||
const payload = (await response.json()) as ApiErrorPayload;
|
||||
message = payload.error?.fallback_message || payload.error?.code || message;
|
||||
} catch {
|
||||
// Keep generic HTTP message if backend did not return JSON.
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
}
|
||||
|
||||
function browserDeviceFingerprint(): string {
|
||||
const key = "rap.webAdmin.deviceFingerprint";
|
||||
const existing = localStorage.getItem(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const value = `web-admin-${createBrowserIdentifier()}`;
|
||||
localStorage.setItem(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
function createBrowserIdentifier(): string {
|
||||
if (typeof globalThis.crypto?.randomUUID === "function") {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
|
||||
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
||||
const bytes = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(bytes);
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,529 @@
|
||||
export type Cluster = {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
status: string;
|
||||
region?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type AuthUser = {
|
||||
ID?: string;
|
||||
id?: string;
|
||||
Email?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export type AuthSession = {
|
||||
ID?: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type AuthTokens = {
|
||||
access_token: string;
|
||||
access_token_expires_at: string;
|
||||
refresh_token: string;
|
||||
refresh_token_expires_at: string;
|
||||
};
|
||||
|
||||
export type AuthResult = {
|
||||
user: AuthUser;
|
||||
auth_session: AuthSession;
|
||||
tokens: AuthTokens;
|
||||
};
|
||||
|
||||
export type InstallationStatus = {
|
||||
bootstrapped: boolean;
|
||||
authority_state: string;
|
||||
install_id?: string;
|
||||
bootstrapped_owner_email?: string;
|
||||
bootstrapped_at?: string;
|
||||
authority_mode: string;
|
||||
strict_authority: boolean;
|
||||
root_fingerprint?: string;
|
||||
insecure_bootstrap_allowed: boolean;
|
||||
};
|
||||
|
||||
export type BootstrapOwnerResult = {
|
||||
installation: InstallationStatus;
|
||||
user: AuthUser;
|
||||
platform_role: string;
|
||||
};
|
||||
|
||||
export type ClusterAuthorityState = {
|
||||
cluster_id: string;
|
||||
authority_state: string;
|
||||
mutation_mode: string;
|
||||
term: number;
|
||||
notes?: string | null;
|
||||
updated_by_user_id?: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type ClusterNode = {
|
||||
id: string;
|
||||
owner_organization_id?: string | null;
|
||||
node_key: string;
|
||||
name: string;
|
||||
ownership_type: string;
|
||||
registration_status: string;
|
||||
health_status: string;
|
||||
version_state: string;
|
||||
partition_state: string;
|
||||
reported_version?: string | null;
|
||||
last_seen_at?: string | null;
|
||||
membership_status: string;
|
||||
membership_metadata?: Record<string, unknown>;
|
||||
node_group_id?: string | null;
|
||||
node_group_name?: string | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type ClusterNodeGroup = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
parent_group_id?: string | null;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
sort_order: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
created_by_user_id?: string | null;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type JoinRequest = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
node_name: string;
|
||||
node_fingerprint: string;
|
||||
public_key?: string;
|
||||
reported_capabilities: Record<string, unknown>;
|
||||
reported_facts: Record<string, unknown>;
|
||||
requested_roles: unknown[];
|
||||
status: string;
|
||||
reviewed_at?: string | null;
|
||||
approved_node_id?: string | null;
|
||||
rejection_reason?: string | null;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
approval_payload?: Record<string, unknown>;
|
||||
approval_signature?: ClusterSignature;
|
||||
};
|
||||
|
||||
export type ClusterSignature = {
|
||||
schema_version: string;
|
||||
algorithm: string;
|
||||
key_fingerprint: string;
|
||||
signature: string;
|
||||
signed_at: string;
|
||||
};
|
||||
|
||||
export type ClusterAuthorityDescriptor = {
|
||||
schema_version: string;
|
||||
cluster_id: string;
|
||||
authority_state: string;
|
||||
key_algorithm: string;
|
||||
public_key: string;
|
||||
public_key_fingerprint: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type CreatedJoinToken = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
scope: Record<string, unknown>;
|
||||
expires_at: string;
|
||||
max_uses: number;
|
||||
used_count: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
revoked_at?: string | null;
|
||||
authority_payload?: Record<string, unknown>;
|
||||
authority_signature?: ClusterSignature;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type RoleAssignment = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
organization_id?: string | null;
|
||||
role: string;
|
||||
status: string;
|
||||
policy?: Record<string, unknown>;
|
||||
assigned_at: string;
|
||||
revoked_at?: string | null;
|
||||
};
|
||||
|
||||
export type AuditEvent = {
|
||||
id: string;
|
||||
cluster_id?: string | null;
|
||||
actor_user_id?: string | null;
|
||||
event_type: string;
|
||||
target_type: string;
|
||||
target_id?: string | null;
|
||||
payload: Record<string, unknown>;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type WorkloadStatus = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
service_type: string;
|
||||
reported_state: string;
|
||||
runtime_mode: string;
|
||||
version?: string | null;
|
||||
status_payload?: Record<string, unknown>;
|
||||
observed_at: string;
|
||||
};
|
||||
|
||||
export type NodeWorkloadDesiredState = {
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
service_type: string;
|
||||
desired_state: string;
|
||||
version?: string | null;
|
||||
runtime_mode: string;
|
||||
artifact_ref?: string | null;
|
||||
config?: Record<string, unknown>;
|
||||
environment?: Record<string, unknown>;
|
||||
updated_by_user_id?: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type NodeHeartbeat = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
health_status: string;
|
||||
reported_version?: string | null;
|
||||
capabilities?: Record<string, unknown>;
|
||||
service_states?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
observed_at: string;
|
||||
};
|
||||
|
||||
export type FabricTestingFlag = {
|
||||
id: string;
|
||||
scope_type: string;
|
||||
scope_id?: string | null;
|
||||
cluster_id?: string | null;
|
||||
enabled: boolean;
|
||||
telemetry_enabled: boolean;
|
||||
synthetic_links_enabled: boolean;
|
||||
history_retention_hours: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
updated_by_user_id?: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type NodeTelemetryObservation = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
cpu_percent?: number | null;
|
||||
memory_used_bytes?: number | null;
|
||||
memory_total_bytes?: number | null;
|
||||
disk_used_bytes?: number | null;
|
||||
disk_total_bytes?: number | null;
|
||||
network_rx_bytes?: number | null;
|
||||
network_tx_bytes?: number | null;
|
||||
process_count?: number | null;
|
||||
payload?: Record<string, unknown>;
|
||||
observed_at: string;
|
||||
};
|
||||
|
||||
export type MeshLink = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
source_node_id: string;
|
||||
target_node_id: string;
|
||||
link_status: string;
|
||||
latency_ms?: number | null;
|
||||
quality_score?: number | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
observed_at: string;
|
||||
};
|
||||
|
||||
export type PeerEndpointCandidate = {
|
||||
endpoint_id: string;
|
||||
node_id: string;
|
||||
transport: string;
|
||||
address: string;
|
||||
address_family?: string | null;
|
||||
reachability: string;
|
||||
nat_type?: string | null;
|
||||
connectivity_mode: string;
|
||||
region?: string | null;
|
||||
priority: number;
|
||||
policy_tags?: string[];
|
||||
last_verified_at?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type PeerDirectoryEntry = {
|
||||
node_id: string;
|
||||
route_ids?: string[];
|
||||
endpoint_count: number;
|
||||
candidate_count: number;
|
||||
connectivity_modes?: string[];
|
||||
recovery_seed: boolean;
|
||||
};
|
||||
|
||||
export type PeerRecoverySeed = {
|
||||
node_id: string;
|
||||
endpoint: string;
|
||||
transport: string;
|
||||
connectivity_mode?: string | null;
|
||||
region?: string | null;
|
||||
priority: number;
|
||||
last_verified_at?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type PeerRendezvousLease = {
|
||||
lease_id: string;
|
||||
peer_node_id: string;
|
||||
relay_node_id: string;
|
||||
relay_endpoint: string;
|
||||
transport: string;
|
||||
connectivity_mode?: string | null;
|
||||
route_ids?: string[];
|
||||
allowed_channels?: string[];
|
||||
priority: number;
|
||||
control_plane_only: boolean;
|
||||
issued_at: string;
|
||||
expires_at: string;
|
||||
reason?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type RendezvousRelayPolicyDecision = {
|
||||
route_id?: string;
|
||||
peer_node_id: string;
|
||||
withdrawn_lease_id?: string;
|
||||
stale_relay_node_id?: string;
|
||||
selected_relay_id?: string;
|
||||
selected_endpoint?: string;
|
||||
score?: number;
|
||||
reason: string;
|
||||
score_reasons?: string[];
|
||||
reporter_node_id?: string;
|
||||
};
|
||||
|
||||
export type RendezvousRelayPolicyReport = {
|
||||
schema_version: string;
|
||||
scoring_mode: string;
|
||||
feedback_max_age_seconds: number;
|
||||
stale_relay_count: number;
|
||||
withdrawn_lease_count: number;
|
||||
replacement_lease_count: number;
|
||||
decisions?: RendezvousRelayPolicyDecision[];
|
||||
};
|
||||
|
||||
export type RoutePathDecision = {
|
||||
decision_id: string;
|
||||
route_id: string;
|
||||
cluster_id: string;
|
||||
local_node_id: string;
|
||||
source_node_id: string;
|
||||
destination_node_id: string;
|
||||
original_hops: string[];
|
||||
effective_hops: string[];
|
||||
previous_hop_id?: string;
|
||||
next_hop_id?: string;
|
||||
local_role: string;
|
||||
selected_relay_id?: string;
|
||||
selected_relay_endpoint?: string;
|
||||
stale_relay_node_id?: string;
|
||||
rendezvous_lease_id?: string;
|
||||
rendezvous_lease_reason?: string;
|
||||
decision_source: string;
|
||||
generation: string;
|
||||
path_score?: number;
|
||||
score_reasons?: string[];
|
||||
control_plane_only: boolean;
|
||||
production_forwarding: boolean;
|
||||
expires_at: string;
|
||||
};
|
||||
|
||||
export type RoutePathDecisionReport = {
|
||||
schema_version: string;
|
||||
decision_mode: string;
|
||||
generation: string;
|
||||
decision_count: number;
|
||||
replacement_decision_count: number;
|
||||
control_plane_only: boolean;
|
||||
production_forwarding: boolean;
|
||||
decisions?: RoutePathDecision[];
|
||||
};
|
||||
|
||||
export type SyntheticMeshRoute = {
|
||||
route_id: string;
|
||||
cluster_id: string;
|
||||
source_node_id: string;
|
||||
destination_node_id: string;
|
||||
hops: string[];
|
||||
allowed_channels: string[];
|
||||
expires_at: string;
|
||||
max_ttl: number;
|
||||
max_hops: number;
|
||||
route_version?: string;
|
||||
policy_version?: string;
|
||||
peer_directory_version?: string;
|
||||
};
|
||||
|
||||
export type NodeSyntheticMeshConfig = {
|
||||
enabled: boolean;
|
||||
schema_version: string;
|
||||
cluster_id: string;
|
||||
local_node_id: string;
|
||||
authority_required: boolean;
|
||||
cluster_authority?: ClusterAuthorityDescriptor;
|
||||
authority_payload?: Record<string, unknown>;
|
||||
authority_signature?: ClusterSignature;
|
||||
config_version?: string;
|
||||
peer_directory_version?: string;
|
||||
policy_version?: string;
|
||||
peer_endpoints: Record<string, string>;
|
||||
peer_endpoint_candidates?: Record<string, PeerEndpointCandidate[]>;
|
||||
peer_directory?: PeerDirectoryEntry[];
|
||||
recovery_seeds?: PeerRecoverySeed[];
|
||||
rendezvous_leases?: PeerRendezvousLease[];
|
||||
rendezvous_relay_policy?: RendezvousRelayPolicyReport;
|
||||
route_path_decisions?: RoutePathDecisionReport;
|
||||
routes: SyntheticMeshRoute[];
|
||||
production_forwarding: boolean;
|
||||
};
|
||||
|
||||
export type FabricEntryPoint = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
endpoint_type: string;
|
||||
public_endpoint?: string | null;
|
||||
policy?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
created_by_user_id?: string | null;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type FabricEntryPointNode = {
|
||||
entry_point_id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
added_by_user_id?: string | null;
|
||||
added_at: string;
|
||||
};
|
||||
|
||||
export type FabricEgressPool = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
description?: string | null;
|
||||
route_scope?: Record<string, unknown>;
|
||||
policy?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
created_by_user_id?: string | null;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type FabricEgressPoolNode = {
|
||||
egress_pool_id: string;
|
||||
cluster_id: string;
|
||||
node_id: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
added_by_user_id?: string | null;
|
||||
added_at: string;
|
||||
};
|
||||
|
||||
export type QoSPolicy = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
service_class: string;
|
||||
priority: number;
|
||||
reliability_mode: string;
|
||||
drop_policy: string;
|
||||
bandwidth_policy?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type ClusterAdminSummary = {
|
||||
cluster_id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
status: string;
|
||||
region?: string | null;
|
||||
authority_state: string;
|
||||
mutation_mode: string;
|
||||
cluster_key_algorithm?: string | null;
|
||||
cluster_key_fingerprint?: string | null;
|
||||
node_count: number;
|
||||
healthy_node_count: number;
|
||||
pending_join_count: number;
|
||||
active_role_assignment_count: number;
|
||||
last_node_seen_at?: string | null;
|
||||
};
|
||||
|
||||
export type OrganizationAdminSummary = {
|
||||
organization_id: string;
|
||||
resource_count: number;
|
||||
active_session_count: number;
|
||||
service_endpoints: Array<{ protocol: string; count: number }>;
|
||||
connector_status: Record<string, unknown>;
|
||||
topology_exposure: string;
|
||||
};
|
||||
|
||||
export type VPNConnection = {
|
||||
id: string;
|
||||
cluster_id: string;
|
||||
organization_id: string;
|
||||
name: string;
|
||||
target_endpoint: Record<string, unknown>;
|
||||
protocol_family: string;
|
||||
credential_ref?: string | null;
|
||||
mode: string;
|
||||
desired_state: string;
|
||||
allowed_node_policy: Record<string, unknown>;
|
||||
routing_usage: unknown[];
|
||||
route_policy: Record<string, unknown>;
|
||||
qos_policy: Record<string, unknown>;
|
||||
placement_policy: Record<string, unknown>;
|
||||
status: string;
|
||||
metadata: Record<string, unknown>;
|
||||
created_by_user_id?: string | null;
|
||||
updated_by_user_id?: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type VPNConnectionLease = {
|
||||
id: string;
|
||||
vpn_connection_id: string;
|
||||
cluster_id: string;
|
||||
owner_node_id: string;
|
||||
lease_generation: number;
|
||||
fencing_token: string;
|
||||
status: string;
|
||||
acquired_at: string;
|
||||
renewed_at: string;
|
||||
expires_at: string;
|
||||
released_at?: string | null;
|
||||
fenced_at?: string | null;
|
||||
metadata: Record<string, unknown>;
|
||||
};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/api/client.ts"],"version":"6.0.3"}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
const backendTarget = process.env.RAP_ADMIN_API_PROXY || "http://192.168.200.61:8080";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: backendTarget,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user