@wenathlan/saddle 2.0.151 → 2.0.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/biome.jsonc +12 -5
- package/dist/App.d.ts +3 -0
- package/dist/App.js +50 -0
- package/dist/Sol/Sol.d.ts +3 -0
- package/dist/Sol/Sol.js +46 -0
- package/dist/Sol/agentbrowser/agentbrowser.d.ts +9 -0
- package/dist/Sol/agentbrowser/agentbrowser.js +121 -0
- package/dist/Sol/architecture/architecture.d.ts +9 -0
- package/dist/Sol/architecture/architecture.js +93 -0
- package/dist/Sol/compute/compute.d.ts +9 -0
- package/dist/Sol/compute/compute.js +22 -0
- package/dist/Sol/console/console.d.ts +15 -0
- package/dist/Sol/console/console.js +292 -0
- package/dist/Sol/dashboard/dashboard.d.ts +10 -0
- package/dist/Sol/dashboard/dashboard.js +181 -0
- package/dist/Sol/docs/docs.d.ts +9 -0
- package/dist/Sol/docs/docs.js +28 -0
- package/dist/Sol/home/home.d.ts +35 -0
- package/dist/Sol/home/home.js +239 -0
- package/dist/Sol/integrations/integrations.d.ts +9 -0
- package/dist/Sol/integrations/integrations.js +83 -0
- package/dist/Sol/intro/intro.d.ts +7 -0
- package/dist/Sol/intro/intro.js +102 -0
- package/dist/Sol/login/login.d.ts +16 -0
- package/dist/Sol/login/login.js +49 -0
- package/dist/Sol/notfound/notfound.d.ts +9 -0
- package/dist/Sol/notfound/notfound.js +15 -0
- package/dist/Sol/onboarding/onboarding.d.ts +25 -0
- package/dist/Sol/onboarding/onboarding.js +60 -0
- package/dist/Sol/playground/playground.d.ts +9 -0
- package/dist/Sol/playground/playground.js +127 -0
- package/dist/Sol/register/register.d.ts +16 -0
- package/dist/Sol/register/register.js +193 -0
- package/dist/Sol/shell/Shell.d.ts +124 -0
- package/dist/Sol/shell/Shell.js +335 -0
- package/dist/Sol/wingets/ComputeBootladder.d.ts +6 -0
- package/dist/Sol/wingets/ComputeBootladder.js +71 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +7 -0
- package/dist/Sol/wingets/ComputeModalitycatalog.js +71 -0
- package/dist/Sol/wingets/ConsoleApibadge.d.ts +7 -0
- package/dist/Sol/wingets/ConsoleApibadge.js +5 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +6 -0
- package/dist/Sol/wingets/ConsoleEdgelaw.js +36 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +19 -0
- package/dist/Sol/wingets/ConsoleEventstimeline.js +12 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +8 -0
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +8 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +21 -0
- package/dist/Sol/wingets/ConsoleSpecpanel.js +75 -0
- package/dist/Sol/wingets/ConsoleTerminal.d.ts +22 -0
- package/dist/Sol/wingets/ConsoleTerminal.js +77 -0
- package/dist/Sol/wingets/DashboardAccountpanel.d.ts +18 -0
- package/dist/Sol/wingets/DashboardAccountpanel.js +65 -0
- package/dist/Sol/wingets/DashboardAdminpanel.d.ts +6 -0
- package/dist/Sol/wingets/DashboardAdminpanel.js +90 -0
- package/dist/Sol/wingets/DashboardAuditlog.d.ts +8 -0
- package/dist/Sol/wingets/DashboardAuditlog.js +50 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +10 -0
- package/dist/Sol/wingets/DashboardCreatesandboxform.js +71 -0
- package/dist/Sol/wingets/DashboardEventstimeline.d.ts +8 -0
- package/dist/Sol/wingets/DashboardEventstimeline.js +83 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +57 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardMeshnodestable.js +103 -0
- package/dist/Sol/wingets/DashboardSandboxlist.d.ts +10 -0
- package/dist/Sol/wingets/DashboardSandboxlist.js +163 -0
- package/dist/Sol/wingets/DashboardUserstable.d.ts +8 -0
- package/dist/Sol/wingets/DashboardUserstable.js +78 -0
- package/dist/Sol/wingets/HomeEntrygate.d.ts +23 -0
- package/dist/Sol/wingets/HomeEntrygate.js +73 -0
- package/dist/Sol/wingets/HomeMetricstrip.d.ts +9 -0
- package/dist/Sol/wingets/HomeMetricstrip.js +4 -0
- package/dist/Sol/wingets/HomeRuntimediagram.d.ts +1 -0
- package/dist/Sol/wingets/HomeRuntimediagram.js +22 -0
- package/dist/Sol/wingets/LoginForm.d.ts +19 -0
- package/dist/Sol/wingets/LoginForm.js +141 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +13 -0
- package/dist/Sol/wingets/RegisterPasswordconfirm.js +16 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +19 -0
- package/dist/Sol/wingets/RegisterPasswordmeter.js +65 -0
- package/dist/Sol/wingets/RegisterUsernamerules.d.ts +11 -0
- package/dist/Sol/wingets/RegisterUsernamerules.js +16 -0
- package/dist/Sol/wingets/index.d.ts +39 -0
- package/dist/Sol/wingets/index.js +39 -0
- package/dist/alternatives.d.ts +508 -0
- package/dist/alternatives.js +950 -0
- package/dist/api.d.ts +154 -0
- package/dist/api.js +174 -0
- package/dist/auth.d.ts +268 -0
- package/dist/auth.js +476 -0
- package/dist/automation.d.ts +425 -0
- package/dist/automation.js +566 -0
- package/dist/browser.d.ts +561 -0
- package/dist/browser.js +1058 -0
- package/dist/capacitor.config.d.ts +45 -0
- package/dist/capacitor.config.js +48 -0
- package/dist/catalog.d.ts +43 -0
- package/dist/catalog.js +44 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +168 -0
- package/dist/communication.d.ts +181 -0
- package/dist/communication.js +376 -0
- package/dist/compute.d.ts +1160 -0
- package/dist/compute.js +2272 -0
- package/dist/dashboardlib.d.ts +58 -0
- package/dist/dashboardlib.js +161 -0
- package/dist/db.d.ts +569 -0
- package/dist/db.js +1080 -0
- package/dist/distribution.d.ts +528 -0
- package/dist/distribution.js +810 -0
- package/dist/drizzle.config.d.ts +20 -0
- package/dist/drizzle.config.js +18 -0
- package/dist/edgelandes.d.ts +70 -0
- package/dist/edgelandes.host.d.ts +26 -0
- package/dist/edgelandes.host.js +1089 -0
- package/dist/edgelandes.inproc.d.ts +27 -0
- package/dist/edgelandes.inproc.js +952 -0
- package/dist/edgelandes.js +1133 -0
- package/dist/edgelandes.ninety.d.ts +31 -0
- package/dist/edgelandes.ninety.js +174 -0
- package/dist/edgelandes.virtual.d.ts +25 -0
- package/dist/edgelandes.virtual.js +629 -0
- package/dist/edgeplane.d.ts +114 -0
- package/dist/edgeplane.js +305 -0
- package/dist/examplelocaljob.d.ts +1 -0
- package/{docs/example-localjob.ts → dist/examplelocaljob.js} +0 -1
- package/dist/execution.d.ts +529 -0
- package/dist/execution.js +760 -0
- package/dist/family.d.ts +16 -0
- package/dist/family.js +42 -0
- package/dist/familyurl.d.ts +21 -0
- package/dist/familyurl.js +24 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +59 -0
- package/dist/foundation.d.ts +172 -0
- package/dist/foundation.js +312 -0
- package/dist/index.d.ts +735 -0
- package/dist/index.js +1020 -0
- package/dist/integration.d.ts +235 -0
- package/dist/integration.js +228 -0
- package/dist/intelligence.d.ts +93 -0
- package/dist/intelligence.js +144 -0
- package/dist/isolation.d.ts +128 -0
- package/dist/isolation.js +95 -0
- package/dist/labels.d.ts +665 -0
- package/dist/labels.js +751 -0
- package/dist/localauth.d.ts +127 -0
- package/dist/localauth.js +413 -0
- package/dist/media.d.ts +343 -0
- package/dist/media.js +985 -0
- package/dist/mesh.d.ts +177 -0
- package/dist/mesh.js +468 -0
- package/dist/modalityregistry.d.ts +109 -0
- package/dist/modalityregistry.js +1386 -0
- package/dist/modes.d.ts +358 -0
- package/dist/modes.js +133 -0
- package/dist/onboardingstate.d.ts +79 -0
- package/dist/onboardingstate.js +282 -0
- package/dist/orchestrator.d.ts +1338 -0
- package/dist/orchestrator.js +4310 -0
- package/dist/packager/targetcli.js +4 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +14 -0
- package/dist/performance.d.ts +415 -0
- package/dist/performance.js +826 -0
- package/dist/quantum.d.ts +809 -0
- package/dist/quantum.js +2072 -0
- package/dist/render.d.ts +380 -0
- package/dist/render.js +1072 -0
- package/dist/runall.d.ts +2 -0
- package/dist/runall.js +39 -0
- package/dist/runtimeprobe.d.ts +4 -0
- package/dist/runtimeprobe.js +8 -0
- package/dist/sandbox.d.ts +355 -0
- package/dist/sandbox.js +1721 -0
- package/dist/sandboxenvprofile.d.ts +29 -0
- package/dist/sandboxenvprofile.js +89 -0
- package/dist/sandboxprofile.d.ts +137 -0
- package/dist/sandboxprofile.js +241 -0
- package/dist/sandboxqueue.d.ts +108 -0
- package/dist/sandboxqueue.js +201 -0
- package/dist/sandboxquota.d.ts +121 -0
- package/dist/sandboxquota.js +236 -0
- package/dist/scheduler.d.ts +595 -0
- package/dist/scheduler.js +1258 -0
- package/dist/scriptlanes.d.ts +182 -0
- package/dist/scriptlanes.js +660 -0
- package/dist/scripts.buildengine.d.ts +1 -0
- package/dist/scripts.buildengine.js +39 -0
- package/dist/scripts.securitygate.d.ts +1 -0
- package/dist/scripts.securitygate.js +68 -0
- package/dist/scripts.validateflatnative.d.ts +1 -0
- package/dist/scripts.validateflatnative.js +72 -0
- package/dist/security.d.ts +500 -0
- package/dist/security.js +1169 -0
- package/dist/seedcatalog.d.ts +13 -0
- package/dist/seedcatalog.js +46 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +53 -0
- package/dist/speccatalog.d.ts +31 -0
- package/dist/speccatalog.js +163 -0
- package/dist/tiers.d.ts +1001 -0
- package/dist/tiers.js +2169 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/dist/vaulthandoff.d.ts +141 -0
- package/dist/vaulthandoff.js +444 -0
- package/dist/virtual.d.ts +640 -0
- package/dist/virtual.js +1284 -0
- package/dist/virtualcpu.d.ts +315 -0
- package/dist/virtualcpu.js +1180 -0
- package/dist/virtualgpu.d.ts +344 -0
- package/dist/virtualgpu.js +1000 -0
- package/dist/virtualization.d.ts +606 -0
- package/dist/virtualization.js +1186 -0
- package/dist/virtualmemory.d.ts +817 -0
- package/dist/virtualmemory.js +1795 -0
- package/dist/vite.config.d.ts +3 -0
- package/dist/vite.config.js +43 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +25 -0
- package/dist/webserver.d.ts +166 -0
- package/dist/webserver.js +2226 -0
- package/dist/workloadlaw.d.ts +133 -0
- package/dist/workloadlaw.js +193 -0
- package/docs/artifactavailability.md +2 -2
- package/docs/{containerplatforms-2.1.0.md → containerplatforms.2.1.0.md} +1 -1
- package/docs/{documentmap-1.8.12.md → documentmap.1.8.12.md} +1 -1
- package/docs/ninety.modalities.md +108 -0
- package/docs/planning.ninety.md +391 -0
- package/docs/{releasenotes-1.8.13.md → releasenotes.1.8.13.md} +2 -2
- package/docs/{releasenotes-1.8.14.md → releasenotes.1.8.14.md} +1 -1
- package/docs/{repo-synthesis-1.8.16.md → repo.synthesis.1.8.16.md} +1 -1
- package/docs/{todo-1.8.15.md → todo.1.8.15.md} +1 -1
- package/docs/{todo-1.8.16.md → todo.1.8.16.md} +5 -5
- package/docs/todo.md +2 -2
- package/docs/workloadlaw.md +104 -0
- package/index.ts +1 -1
- package/manifest.json +1 -1
- package/modalityregistry.ts +1371 -34
- package/orchestrator.ts +1 -1
- package/package.json +73 -33
- package/docs/CONVERSA.txt +0 -1523
- package/docs/architecture-1.8.19.png +0 -0
- package/docs/assets/architecture.svg +0 -45
- package/docs/assets/saddle-browser-trace.webp +0 -0
- package/docs/assets/saddle-hero-bridge.webp +0 -0
- package/docs/assets/saddle-mark.webp +0 -0
- package/docs/assets/saddle-runtime-map.webp +0 -0
- package/docs/assets/saddlemark.svg +0 -13
- package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
- /package/docs/{architecture-1.8.19.md → architecture.1.8.19.md} +0 -0
- /package/docs/{architecture-1.8.19.mmd → architecture.1.8.19.mmd} +0 -0
- /package/docs/{artifactresearch-1.8.9.md → artifactresearch.1.8.9.md} +0 -0
- /package/docs/{batches-1816-1820.md → batches.1816.to.1820.md} +0 -0
- /package/docs/{brancharchive-2026-08-18.md → brancharchive.2026.08.18.md} +0 -0
- /package/docs/{containerplatforms-1.8.17.md → containerplatforms.1.8.17.md} +0 -0
- /package/docs/{e2ugh-engine.md → e2ugh.engine.md} +0 -0
- /package/docs/{flatbuildresearch-1.8.11.md → flatbuildresearch.1.8.11.md} +0 -0
- /package/docs/{licenseaudit-1.8.12.md → licenseaudit.1.8.12.md} +0 -0
- /package/docs/{mobileconversionresearch-1.8.10.md → mobileconversionresearch.1.8.10.md} +0 -0
- /package/docs/{native-wrappers.md → native.wrappers.md} +0 -0
- /package/docs/{nativeidentityresearch-1.8.11.md → nativeidentityresearch.1.8.11.md} +0 -0
- /package/docs/{releasenotes-1.8.12.md → releasenotes.1.8.12.md} +0 -0
- /package/docs/{releasenotes-1.8.15.md → releasenotes.1.8.15.md} +0 -0
- /package/docs/{releasenotes-1.8.16.md → releasenotes.1.8.16.md} +0 -0
- /package/docs/{releasenotes-1.8.17.md → releasenotes.1.8.17.md} +0 -0
- /package/docs/{releasenotes-1.8.18.md → releasenotes.1.8.18.md} +0 -0
- /package/docs/{releasenotes-1.8.19.md → releasenotes.1.8.19.md} +0 -0
- /package/docs/{reorganization-1.8.8.md → reorganization.1.8.8.md} +0 -0
- /package/docs/{reorganization-1.8.9.md → reorganization.1.8.9.md} +0 -0
- /package/docs/{repo-analysis-1.8.21.json → repo.analysis.1.8.21.json} +0 -0
- /package/docs/{repo-candidates-1.8.21.jsonl → repo.candidates.1.8.21.jsonl} +0 -0
- /package/docs/{repo-research-1.8.16.md → repo.research.1.8.16.md} +0 -0
- /package/docs/{repo-research-1.8.21.md → repo.research.1.8.21.md} +0 -0
- /package/docs/{repo-selected-1.8.21.json → repo.selected.1.8.21.json} +0 -0
- /package/docs/{repo-synthesis-1.8.21.md → repo.synthesis.1.8.21.md} +0 -0
- /package/docs/{research-1.8.18-isolation.md → research.1.8.18.isolation.md} +0 -0
- /package/docs/{research-1.8.19-virtual-browser.md → research.1.8.19.virtual.browser.md} +0 -0
- /package/docs/{scanningresearch-1.8.11.md → scanningresearch.1.8.11.md} +0 -0
- /package/docs/{securityaudit-1.8.7.md → securityaudit.1.8.7.md} +0 -0
- /package/docs/{securityreferences-1.8.11.md → securityreferences.1.8.11.md} +0 -0
- /package/docs/{securityresearch-1.8.11.md → securityresearch.1.8.11.md} +0 -0
- /package/docs/{signingresearch-1.8.12.md → signingresearch.1.8.12.md} +0 -0
- /package/docs/{tauri-glib-risk-1.8.11.md → tauri.glib.risk.1.8.11.md} +0 -0
- /package/docs/{todo-1.8.18.md → todo.1.8.18.md} +0 -0
- /package/docs/{todo-1.8.19.md → todo.1.8.19.md} +0 -0
- /package/docs/{toolchainresearch-1.8.9.md → toolchainresearch.1.8.9.md} +0 -0
- /package/docs/{web-duplicatecleanup.md → web.duplicatecleanup.md} +0 -0
- /package/docs/{web-ideas.md → web.ideas.md} +0 -0
- /package/docs/{workflowimprovements-2026-08-19.md → workflowimprovements.2026.08.19.md} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type GlobalSandboxesTableProps = {
|
|
2
|
+
/** "api" lists /api/v1/admin/sandboxes; "local" shows the static note. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** bumped by the admin refresh-all action. */
|
|
5
|
+
reloadkey: number;
|
|
6
|
+
};
|
|
7
|
+
export default function GlobalSandboxesTable({ mode, reloadkey }: GlobalSandboxesTableProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the global sandbox table absorbed from dashboard.js —
|
|
3
|
+
// db rows (flat or enveloped) for the main node admin; on the static
|
|
4
|
+
// edge, the honest note pointing to the user view.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { apiresult, asarray, chipclass, fmtdate, pick } from '../../dashboardlib.ts';
|
|
7
|
+
import { label, labelfill } from '../../labels.ts';
|
|
8
|
+
export default function GlobalSandboxesTable({ mode, reloadkey }) {
|
|
9
|
+
const [items, setitems] = useState([]);
|
|
10
|
+
const [error, seterror] = useState('');
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (mode !== 'api') {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
let stopped = false;
|
|
16
|
+
(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const result = await apiresult('/api/v1/admin/sandboxes');
|
|
19
|
+
if (stopped) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
seterror(labelfill('dashboard.global.unavailable', { status: String(result.status) }));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
seterror('');
|
|
27
|
+
setitems(asarray(result.body, ['sandboxes', 'items', 'data', 'list']));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
if (!stopped) {
|
|
31
|
+
seterror(label('dashboard.global.unreachable'));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
return () => {
|
|
36
|
+
stopped = true;
|
|
37
|
+
};
|
|
38
|
+
}, [mode, reloadkey]);
|
|
39
|
+
if (mode === 'local') {
|
|
40
|
+
return _jsx("p", { className: "dash-empty", children: label('dashboard.global.localNote') });
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "dash-tablescroll",
|
|
43
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: deliberate wcag scrollable-region pattern: the tabIndex keeps the overflowing table keyboard-scrollable; the rows are display data and the interactive cells (none here) use real buttons
|
|
44
|
+
tabIndex: 0, role: "region", "aria-label": label('dashboard.global.tableAria'), children: _jsxs("table", { children: [items.length === 0 && error === '' && (_jsx("caption", { className: "dash-empty", children: label('dashboard.global.empty') })), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: label('dashboard.global.colId') }), _jsx("th", { scope: "col", children: label('dashboard.global.colOwner') }), _jsx("th", { scope: "col", children: label('dashboard.global.colState') }), _jsx("th", { scope: "col", children: label('dashboard.global.colSpec') }), _jsx("th", { scope: "col", children: label('dashboard.global.colCreated') }), _jsx("th", { scope: "col", children: label('dashboard.global.colExpires') })] }) }), _jsx("tbody", { children: items.map((record) => {
|
|
45
|
+
/* admin rows may be flat db rows or enveloped public views */
|
|
46
|
+
const specobj = pick(record, ['spec'], null);
|
|
47
|
+
const spec = specobj !== null && typeof specobj === 'object' ? specobj : record;
|
|
48
|
+
const owner = pick(record, ['owner', 'username', 'user', 'ownerUsername', 'userid', 'user_id'], '-');
|
|
49
|
+
const id = String(pick(record, ['id'], '?'));
|
|
50
|
+
return (_jsxs("tr", { children: [_jsx("td", { className: "wrapcell", children: id }), _jsx("td", { children: String(owner) }), _jsx("td", { children: _jsx("span", { className: chipclass(pick(record, ['state'], '?')), children: String(pick(record, ['state'], '?')) }) }), _jsx("td", { className: "wrapcell", children: [
|
|
51
|
+
String(pick(spec, ['model'], '?')),
|
|
52
|
+
`x${String(pick(spec, ['vcpus'], '?'))}`,
|
|
53
|
+
`${String(pick(spec, ['ramgb', 'ramGb', 'ram'], '?'))}gb`,
|
|
54
|
+
String(pick(spec, ['gpu'], '?')),
|
|
55
|
+
].join(' ') }), _jsx("td", { children: fmtdate(pick(record, ['createdAt', 'createdat', 'created_at'], null)) }), _jsx("td", { children: fmtdate(pick(record, ['expiresAt', 'expiresat', 'expires_at'], null)) })] }, `${id}|${String(owner)}`));
|
|
56
|
+
}) })] }) }), error !== '' && _jsx("p", { className: "dash-panelerror", children: error })] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type MeshNodesTableProps = {
|
|
2
|
+
/** "api" lists /api/v1/admin/nodes; "local" renders the browser row. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** bumped by the admin refresh-all action. */
|
|
5
|
+
reloadkey: number;
|
|
6
|
+
};
|
|
7
|
+
export default function MeshNodesTable({ mode, reloadkey }: MeshNodesTableProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the mesh nodes table absorbed from dashboard.js —
|
|
3
|
+
// ping through the signed /api/v1/mesh/ping route with fallback to the
|
|
4
|
+
// browser latency probe; on the static edge, the "this browser" row.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { apiresult, asarray, chipclass, fmtdate, pick } from '../../dashboardlib.ts';
|
|
7
|
+
import { label, labelfill } from '../../labels.ts';
|
|
8
|
+
/**
|
|
9
|
+
* pings one mesh node: the primary contract posts /api/v1/mesh/ping (the
|
|
10
|
+
* server-side signed mesh); when the node does not expose it the browser
|
|
11
|
+
* falls back to a latency probe of the node health endpoint.
|
|
12
|
+
*/
|
|
13
|
+
async function meshping(url) {
|
|
14
|
+
try {
|
|
15
|
+
const post = await apiresult('/api/v1/mesh/ping', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: JSON.stringify({ url }),
|
|
18
|
+
});
|
|
19
|
+
if (post.ok) {
|
|
20
|
+
const rtt = pick(post.body, ['rttMs', 'rtt', 'latencyMs', 'latency'], null);
|
|
21
|
+
return rtt !== null
|
|
22
|
+
? labelfill('dashboard.mesh.pongMesh', { rtt: String(rtt) })
|
|
23
|
+
: label('dashboard.mesh.pongMeshPlain');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* fall through to the client probe */
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
controller.abort();
|
|
33
|
+
}, 4000);
|
|
34
|
+
const started = performance.now();
|
|
35
|
+
await fetch(`${String(url).replace(/\/+$/, '')}/api/v1/health`, {
|
|
36
|
+
mode: 'no-cors',
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
return labelfill('dashboard.mesh.pongProbe', {
|
|
41
|
+
ms: String(Math.max(1, Math.round(performance.now() - started))),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return label('dashboard.mesh.unreachable');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export default function MeshNodesTable({ mode, reloadkey }) {
|
|
49
|
+
const [nodes, setnodes] = useState([]);
|
|
50
|
+
const [error, seterror] = useState('');
|
|
51
|
+
const [pings, setpings] = useState({});
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (mode !== 'api') {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
let stopped = false;
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const result = await apiresult('/api/v1/admin/nodes');
|
|
60
|
+
if (stopped) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
setnodes([]);
|
|
65
|
+
seterror(labelfill('dashboard.mesh.nodeUnavailable', { status: String(result.status) }));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
seterror('');
|
|
69
|
+
setnodes(asarray(result.body, ['nodes', 'items', 'data', 'list']));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
if (!stopped) {
|
|
73
|
+
seterror(label('dashboard.mesh.nodesUnreachable'));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
return () => {
|
|
78
|
+
stopped = true;
|
|
79
|
+
};
|
|
80
|
+
}, [mode, reloadkey]);
|
|
81
|
+
/** runs one ping and paints the result cell beside the button. */
|
|
82
|
+
function runping(url) {
|
|
83
|
+
setpings((previous) => ({
|
|
84
|
+
...previous,
|
|
85
|
+
[url]: { busy: true, result: label('dashboard.mesh.pinging') },
|
|
86
|
+
}));
|
|
87
|
+
void meshping(url).then((result) => {
|
|
88
|
+
setpings((previous) => ({ ...previous, [url]: { busy: false, result } }));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return (_jsxs("div", { children: [_jsx("p", { className: "dash-statusnote", children: label('dashboard.mesh.pollNote') }), _jsx("div", { className: "dash-tablescroll",
|
|
92
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: deliberate wcag scrollable-region pattern: the tabIndex keeps the overflowing table keyboard-scrollable; the interactive cells are real buttons, the region itself only scrolls
|
|
93
|
+
tabIndex: 0, role: "region", "aria-label": label('dashboard.mesh.tableAria'), children: _jsxs("table", { children: [mode === 'api' && nodes.length === 0 && error === '' && (_jsx("caption", { className: "dash-empty", children: label('dashboard.mesh.empty') })), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: label('dashboard.mesh.colUrl') }), _jsx("th", { scope: "col", children: label('dashboard.mesh.colRegion') }), _jsx("th", { scope: "col", children: label('dashboard.mesh.colStatus') }), _jsx("th", { scope: "col", children: label('dashboard.mesh.colHeartbeat') }), _jsx("th", { scope: "col", children: label('dashboard.mesh.colPing') })] }) }), _jsx("tbody", { children: mode === 'local' ? (_jsxs("tr", { children: [_jsx("td", { children: label('dashboard.mesh.localNode') }), _jsx("td", { children: label('dashboard.mesh.localRegion') }), _jsx("td", { children: label('dashboard.mesh.localStatus') }), _jsx("td", { children: label('dashboard.mesh.na') })] })) : (nodes.map((node) => {
|
|
94
|
+
const url = String(pick(node, ['url', 'endpoint', 'address', 'host'], '?'));
|
|
95
|
+
const region = String(pick(node, ['region', 'location', 'zone'], '-'));
|
|
96
|
+
const status = String(pick(node, ['status', 'state'], '?'));
|
|
97
|
+
const heartbeat = pick(node, ['lastHeartbeat', 'lastHeartbeatAt', 'lastSeen', 'last_heartbeat'], null);
|
|
98
|
+
const pingstate = pings[url];
|
|
99
|
+
return (_jsxs("tr", { children: [_jsx("td", { className: "wrapcell", children: url }), _jsx("td", { children: region }), _jsx("td", { children: _jsx("span", { className: chipclass(status), children: status }) }), _jsx("td", { children: fmtdate(heartbeat) }), _jsxs("td", { children: [_jsx("button", { type: "button", className: "dash-btn mini", "aria-label": labelfill('dashboard.mesh.pingAria', { url }), "aria-busy": pingstate?.busy === true ? 'true' : undefined, disabled: pingstate?.busy === true, onClick: () => {
|
|
100
|
+
runping(url);
|
|
101
|
+
}, children: label('dashboard.mesh.ping') }), ' ', _jsx("span", { className: "wrapcell", role: "status", "aria-live": "polite", children: pingstate === undefined ? label('dashboard.mesh.nopong') : pingstate.result })] })] }, url));
|
|
102
|
+
})) })] }) }), error !== '' && _jsx("p", { className: "dash-panelerror", children: error })] }));
|
|
103
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type SandboxListProps = {
|
|
2
|
+
/** "api" polls the shelf endpoint; "local" renders the sessionStorage shelf. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** bumped by the orchestrator to force a skeleton reload. */
|
|
5
|
+
reloadkey: number;
|
|
6
|
+
/** false while the session bootstrap runs (avoids a premature 401). */
|
|
7
|
+
ready: boolean;
|
|
8
|
+
};
|
|
9
|
+
export default function SandboxList({ mode, reloadkey, ready }: SandboxListProps): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the sandbox list absorbed from dashboard.js — state
|
|
3
|
+
// chips, skeletons, 30 s refresh, open/delete actions (api) and stop
|
|
4
|
+
// (browser-local engine).
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { Link } from 'wouter';
|
|
7
|
+
import { apiresult, asarray, chipclass, errormessage, fmtdate, pick, readlocalsandboxes, writelocalsandboxes, } from '../../dashboardlib.ts';
|
|
8
|
+
import { label, labelfill } from '../../labels.ts';
|
|
9
|
+
/** truncates one sandbox id for display (the full id stays in the links). */
|
|
10
|
+
function shortid(id) {
|
|
11
|
+
return id.slice(0, 13) + (id.length > 13 ? '...' : '');
|
|
12
|
+
}
|
|
13
|
+
export default function SandboxList({ mode, reloadkey, ready }) {
|
|
14
|
+
const [loading, setloading] = useState(true);
|
|
15
|
+
const [items, setitems] = useState([]);
|
|
16
|
+
const [error, seterror] = useState('');
|
|
17
|
+
const [notlive, setnotlive] = useState(false);
|
|
18
|
+
const [deleting, setdeleting] = useState(null);
|
|
19
|
+
const [locals, setlocals] = useState([]);
|
|
20
|
+
/** loads the authenticated user's shelf exactly like the original page. */
|
|
21
|
+
const load = useCallback(async (showskeleton) => {
|
|
22
|
+
if (showskeleton) {
|
|
23
|
+
setloading(true);
|
|
24
|
+
setitems([]);
|
|
25
|
+
}
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = await apiresult('/api/v1/sandboxes');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
setloading(false);
|
|
32
|
+
seterror(label('dashboard.list.unreachable'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (result.status === 401 || result.status === 403) {
|
|
36
|
+
setloading(false);
|
|
37
|
+
seterror(label('dashboard.list.sessionExpired'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setloading(false);
|
|
41
|
+
seterror('');
|
|
42
|
+
if (result.status === 404) {
|
|
43
|
+
/* the list endpoint is not live on this node yet (v7 rollout) */
|
|
44
|
+
setitems([]);
|
|
45
|
+
setnotlive(true);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setnotlive(false);
|
|
49
|
+
const rows = asarray(result.body, ['sandboxes', 'items', 'data', 'list']);
|
|
50
|
+
setitems(rows);
|
|
51
|
+
}, []);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!ready) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (mode === 'local') {
|
|
57
|
+
setloading(false);
|
|
58
|
+
seterror('');
|
|
59
|
+
setlocals(readlocalsandboxes());
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
void load(true);
|
|
63
|
+
}, [mode, reloadkey, ready, load]);
|
|
64
|
+
/* the 30 s shelf poll: only while the tab is visible (api mode). */
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (mode !== 'api' || !ready) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const timer = setInterval(() => {
|
|
70
|
+
if (document.visibilityState !== 'visible') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
void load(false);
|
|
74
|
+
}, 30000);
|
|
75
|
+
return () => clearInterval(timer);
|
|
76
|
+
}, [mode, ready, load]);
|
|
77
|
+
/** deletes one sandbox (the manual purge of the shelf contract). */
|
|
78
|
+
async function destroy(id) {
|
|
79
|
+
setdeleting(id);
|
|
80
|
+
try {
|
|
81
|
+
const result = await apiresult(`/api/v1/sandboxes/${encodeURIComponent(id)}`, {
|
|
82
|
+
method: 'DELETE',
|
|
83
|
+
});
|
|
84
|
+
if (result.ok) {
|
|
85
|
+
await load(true);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
seterror(errormessage(result, labelfill('dashboard.list.deleteFailed', { status: String(result.status) })));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
seterror(label('dashboard.list.deleteUnreachable'));
|
|
93
|
+
}
|
|
94
|
+
setdeleting(null);
|
|
95
|
+
}
|
|
96
|
+
/** stops one browser-local sandbox (removes it from the shelf). */
|
|
97
|
+
function stop(id) {
|
|
98
|
+
const remaining = readlocalsandboxes().filter((entry) => entry.id !== id);
|
|
99
|
+
writelocalsandboxes(remaining);
|
|
100
|
+
setlocals(remaining);
|
|
101
|
+
}
|
|
102
|
+
const emptyvisible = !loading && error === '' && (mode === 'local' ? locals.length === 0 : items.length === 0);
|
|
103
|
+
const listvisible = mode === 'local' ? locals.length > 0 : items.length > 0 && !loading;
|
|
104
|
+
return (_jsxs("div", { children: [_jsx("h3", { children: label('dashboard.list.title') }), loading && (_jsxs("div", { "aria-hidden": "true", children: [_jsx("div", { className: "dash-skeleton" }), _jsx("div", { className: "dash-skeleton", style: { width: '88%' } }), _jsx("div", { className: "dash-skeleton", style: { width: '70%' } })] })), listvisible && (_jsx("ul", { className: "dash-boxlist", "aria-live": "polite", "aria-label": label('dashboard.list.aria'), children: mode === 'local'
|
|
105
|
+
? locals.map((record) => {
|
|
106
|
+
const spectext = [
|
|
107
|
+
record.spec.model,
|
|
108
|
+
labelfill('dashboard.list.specVcpus', {
|
|
109
|
+
vcpus: String(record.spec.vcpus),
|
|
110
|
+
}),
|
|
111
|
+
labelfill('dashboard.list.specRam', { ram: String(record.spec.ramgb) }),
|
|
112
|
+
record.spec.gpu,
|
|
113
|
+
record.spec.mig && record.spec.mig !== 'off'
|
|
114
|
+
? labelfill('dashboard.list.specMig', { mig: record.spec.mig })
|
|
115
|
+
: '',
|
|
116
|
+
]
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join(' / ');
|
|
119
|
+
return (_jsxs("li", { children: [_jsxs("div", { className: "rowtop", children: [_jsx("span", { className: "id", children: shortid(record.id) }), _jsx("span", { className: chipclass(record.state ?? 'created'), children: record.state ?? 'created' }), _jsx("span", { className: "rowactions", children: _jsx("button", { type: "button", className: "danger mini", "aria-label": labelfill('dashboard.list.stopAria', { id: record.id }), onClick: () => stop(record.id), children: label('dashboard.list.stop') }) })] }), _jsx("span", { className: "specs", children: spectext }), _jsx("span", { className: "meta", children: labelfill('dashboard.list.metaCreated', {
|
|
120
|
+
date: fmtdate(record.createdat),
|
|
121
|
+
}) })] }, record.id));
|
|
122
|
+
})
|
|
123
|
+
: items.map((record) => {
|
|
124
|
+
const id = String(pick(record, ['id'], ''));
|
|
125
|
+
const state = String(pick(record, ['state'], '?'));
|
|
126
|
+
const specobj = pick(record, ['spec'], null);
|
|
127
|
+
const spec = specobj !== null && typeof specobj === 'object' ? specobj : record;
|
|
128
|
+
const spectext = [
|
|
129
|
+
String(pick(spec, ['model'], '?')),
|
|
130
|
+
labelfill('dashboard.list.specVcpus', {
|
|
131
|
+
vcpus: String(pick(spec, ['vcpus'], '?')),
|
|
132
|
+
}),
|
|
133
|
+
labelfill('dashboard.list.specRam', {
|
|
134
|
+
ram: String(pick(spec, ['ramgb', 'ramGb', 'ram'], '?')),
|
|
135
|
+
}),
|
|
136
|
+
String(pick(spec, ['gpu'], '?')),
|
|
137
|
+
labelfill('dashboard.list.specMig', {
|
|
138
|
+
mig: String(pick(spec, ['mig'], 'off')),
|
|
139
|
+
}),
|
|
140
|
+
].join(' / ');
|
|
141
|
+
const files = pick(record, ['files'], null);
|
|
142
|
+
const metafrags = [
|
|
143
|
+
labelfill('dashboard.list.metaCreated', {
|
|
144
|
+
date: fmtdate(pick(record, ['createdAt', 'createdat', 'created_at'], null)),
|
|
145
|
+
}),
|
|
146
|
+
labelfill('dashboard.list.metaExpires', {
|
|
147
|
+
date: fmtdate(pick(record, ['expiresAt', 'expiresat', 'expires_at'], null)),
|
|
148
|
+
}),
|
|
149
|
+
labelfill('dashboard.list.metaExecs', {
|
|
150
|
+
count: String(pick(record, ['execCount', 'execcount'], 0)),
|
|
151
|
+
}),
|
|
152
|
+
];
|
|
153
|
+
if (files !== null && files !== undefined) {
|
|
154
|
+
metafrags.push(labelfill('dashboard.list.metaFiles', { count: String(files) }));
|
|
155
|
+
}
|
|
156
|
+
const meta = metafrags.join(' / ');
|
|
157
|
+
return (_jsxs("li", { children: [_jsxs("div", { className: "rowtop", children: [_jsx("span", { className: "id", children: shortid(id) }), _jsx("span", { className: chipclass(state), children: state }), _jsxs("span", { className: "rowactions", children: [_jsx(Link, { className: "dash-btn mini", href: `/console?sandbox=${encodeURIComponent(id)}`, "aria-label": labelfill('dashboard.list.openAria', { id }), children: label('dashboard.list.open') }), _jsx("button", { type: "button", className: "danger mini", "aria-label": labelfill('dashboard.list.deleteAria', { id }), disabled: deleting === id, onClick: () => {
|
|
158
|
+
void destroy(id);
|
|
159
|
+
}, children: label('dashboard.list.delete') })] })] }), _jsx("span", { className: "specs", children: spectext }), _jsx("span", { className: "meta", children: meta })] }, id));
|
|
160
|
+
}) })), emptyvisible && (_jsxs("p", { className: "dash-empty", children: [notlive
|
|
161
|
+
? label('dashboard.list.emptyNotLive')
|
|
162
|
+
: label('dashboard.list.empty'), !notlive && _jsx(Link, { href: "/console", children: label('dashboard.list.consoleLink') }), !notlive && label('dashboard.list.emptyPeriod')] })), error !== '' && _jsx("p", { className: "dash-panelerror", children: error })] }));
|
|
163
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type UsersTableProps = {
|
|
2
|
+
/** "api" lists /api/v1/admin/users; "local" the browser account registry. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** bumped by the admin refresh-all action. */
|
|
5
|
+
reloadkey: number;
|
|
6
|
+
};
|
|
7
|
+
export default function UsersTable({ mode, reloadkey }: UsersTableProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the users table absorbed from dashboard.js — client-side
|
|
3
|
+
// search by username, account fields only (hash and salt never leave the
|
|
4
|
+
// api); on the static edge, the browser-local registry.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { apiresult, asarray, chipclass, fmtdate, pick } from '../../dashboardlib.ts';
|
|
7
|
+
import { label, labelfill } from '../../labels.ts';
|
|
8
|
+
import { available, roleof } from '../../localauth.ts';
|
|
9
|
+
/** reads the local account names (defensive; the api mode never lands here). */
|
|
10
|
+
function localaccountnames() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = window.localStorage.getItem('saddle_local_users');
|
|
13
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
14
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
15
|
+
return Object.keys(parsed);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* no registry or unreadable storage: the empty table applies */
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
export default function UsersTable({ mode, reloadkey }) {
|
|
24
|
+
const [users, setusers] = useState([]);
|
|
25
|
+
const [error, seterror] = useState('');
|
|
26
|
+
const [query, setquery] = useState('');
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (mode === 'local') {
|
|
29
|
+
seterror('');
|
|
30
|
+
setusers(available() ? localaccountnames() : []);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let stopped = false;
|
|
34
|
+
(async () => {
|
|
35
|
+
try {
|
|
36
|
+
const result = await apiresult('/api/v1/admin/users');
|
|
37
|
+
if (stopped) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!result.ok) {
|
|
41
|
+
seterror(labelfill('dashboard.users.unavailable', { status: String(result.status) }));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
seterror('');
|
|
45
|
+
setusers(asarray(result.body, ['users', 'items', 'data', 'list']));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
if (!stopped) {
|
|
49
|
+
seterror(label('dashboard.users.unreachable'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
return () => {
|
|
54
|
+
stopped = true;
|
|
55
|
+
};
|
|
56
|
+
}, [mode, reloadkey]);
|
|
57
|
+
const needle = query.trim().toLowerCase();
|
|
58
|
+
const rows = users.filter((user) => {
|
|
59
|
+
const username = mode === 'local' ? String(user) : String(pick(user, ['username', 'name', 'user'], ''));
|
|
60
|
+
return needle === '' || username.toLowerCase().includes(needle);
|
|
61
|
+
});
|
|
62
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "dash-field", style: { maxWidth: '340px' }, children: [_jsx("label", { htmlFor: "usersearch", children: label('dashboard.users.searchLabel') }), _jsx("input", { type: "search", id: "usersearch", placeholder: label('dashboard.users.searchPlaceholder'), "aria-label": label('dashboard.users.searchAria'), value: query, onChange: (event) => {
|
|
63
|
+
setquery(event.target.value);
|
|
64
|
+
} })] }), _jsx("div", { className: "dash-tablescroll",
|
|
65
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: deliberate wcag scrollable-region pattern: the tabIndex keeps the overflowing table keyboard-scrollable; the rows are display data and the interactive cells (none here) use real buttons
|
|
66
|
+
tabIndex: 0, role: "region", "aria-label": label('dashboard.users.tableAria'), children: _jsxs("table", { children: [rows.length === 0 && error === '' && (_jsx("caption", { className: "dash-empty", children: needle === ''
|
|
67
|
+
? mode === 'local'
|
|
68
|
+
? label('dashboard.users.emptyLocal')
|
|
69
|
+
: label('dashboard.users.empty')
|
|
70
|
+
: labelfill('dashboard.users.noMatch', { needle }) })), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: label('dashboard.users.colUsername') }), _jsx("th", { scope: "col", children: label('dashboard.users.colRole') }), _jsx("th", { scope: "col", children: label('dashboard.users.colLastLogin') }), _jsx("th", { scope: "col", children: label('dashboard.users.colCreated') })] }) }), _jsx("tbody", { children: mode === 'local'
|
|
71
|
+
? rows.map((name) => (_jsxs("tr", { children: [_jsx("td", { children: String(name) }), _jsx("td", { children: roleof(String(name)) }), _jsx("td", { children: label('dashboard.users.localCell') })] }, String(name))))
|
|
72
|
+
: rows.map((user) => {
|
|
73
|
+
const username = String(pick(user, ['username', 'name', 'user'], '?'));
|
|
74
|
+
const role = String(pick(user, ['role'], 'user'));
|
|
75
|
+
/* account fields only: hash and salt keys are ignored */
|
|
76
|
+
return (_jsxs("tr", { children: [_jsx("td", { children: username }), _jsx("td", { children: _jsx("span", { className: chipclass(role), children: role }) }), _jsx("td", { children: fmtdate(pick(user, ['lastLoginAt', 'last_login_at', 'lastlogin'], null)) }), _jsx("td", { children: fmtdate(pick(user, ['createdAt', 'created_at', 'createdat'], null)) })] }, username));
|
|
77
|
+
}) })] }) }), mode !== 'local' && (_jsx("p", { className: "dash-statusnote", children: label('dashboard.users.hashesNote') })), error !== '' && _jsx("p", { className: "dash-panelerror", children: error })] }));
|
|
78
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entrygate.tsx — the entry gate of the application (the flow law: intro →
|
|
3
|
+
* onboarding → the platform). The home anchor mounts it on the "/" route:
|
|
4
|
+
* the synchronous first verdict answers from the offline layer (the cache
|
|
5
|
+
* mirror) so a returning visitor never flashes, and while the state is
|
|
6
|
+
* still unknown the gate renders null — the stage never shows behind the
|
|
7
|
+
* gate. An unseen intro replaces the route with /intro; an unseen
|
|
8
|
+
* onboarding replaces it with /onboarding; only a visitor that finished the
|
|
9
|
+
* flow ever sees the home. The gate only reads — marking the stages seen
|
|
10
|
+
* belongs to the pages that show them (onboardingstate.ts is the one
|
|
11
|
+
* writer).
|
|
12
|
+
*/
|
|
13
|
+
import { type ReactNode } from 'react';
|
|
14
|
+
/**
|
|
15
|
+
* The entry gate wrapping the platform home.
|
|
16
|
+
*
|
|
17
|
+
* @param children the home surface revealed when the flow is through.
|
|
18
|
+
* @returns the gated element.
|
|
19
|
+
*/
|
|
20
|
+
export declare function EntryGate({ children }: {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): ReactNode;
|
|
23
|
+
export default EntryGate;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entrygate.tsx — the entry gate of the application (the flow law: intro →
|
|
3
|
+
* onboarding → the platform). The home anchor mounts it on the "/" route:
|
|
4
|
+
* the synchronous first verdict answers from the offline layer (the cache
|
|
5
|
+
* mirror) so a returning visitor never flashes, and while the state is
|
|
6
|
+
* still unknown the gate renders null — the stage never shows behind the
|
|
7
|
+
* gate. An unseen intro replaces the route with /intro; an unseen
|
|
8
|
+
* onboarding replaces it with /onboarding; only a visitor that finished the
|
|
9
|
+
* flow ever sees the home. The gate only reads — marking the stages seen
|
|
10
|
+
* belongs to the pages that show them (onboardingstate.ts is the one
|
|
11
|
+
* writer).
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useState } from 'react';
|
|
14
|
+
import { useLocation } from 'wouter';
|
|
15
|
+
import { loadOnboardingState, readOnboardingState, } from '../../onboardingstate.ts';
|
|
16
|
+
/**
|
|
17
|
+
* Reads the gate verdict from a seen-state snapshot (the flow order: intro
|
|
18
|
+
* first, then onboarding, then the home).
|
|
19
|
+
*
|
|
20
|
+
* @param state the resolved flags.
|
|
21
|
+
* @returns true when the visitor finished the flow.
|
|
22
|
+
*/
|
|
23
|
+
function flowthrough(state) {
|
|
24
|
+
return state.introseen && state.onboardingseen;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The entry gate wrapping the platform home.
|
|
28
|
+
*
|
|
29
|
+
* @param children the home surface revealed when the flow is through.
|
|
30
|
+
* @returns the gated element.
|
|
31
|
+
*/
|
|
32
|
+
export function EntryGate({ children }) {
|
|
33
|
+
// the embedded pass is the bare stage: inside the os window the first-run
|
|
34
|
+
// flow belongs to the shell, so the frame lands straight home — the frame
|
|
35
|
+
// of a mount never changes, the hooks stay unconditional for the lint
|
|
36
|
+
const embedded = typeof window !== 'undefined' && window.self !== window.top;
|
|
37
|
+
const [, navigate] = useLocation();
|
|
38
|
+
const [phase, setPhase] = useState(() => {
|
|
39
|
+
if (embedded)
|
|
40
|
+
return 'through';
|
|
41
|
+
const known = readOnboardingState();
|
|
42
|
+
return known !== null && flowthrough(known) ? 'through' : 'resolving';
|
|
43
|
+
});
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (embedded)
|
|
46
|
+
return;
|
|
47
|
+
// the fast path: the cache already knows a missing stage — replace at once
|
|
48
|
+
const known = readOnboardingState();
|
|
49
|
+
if (known && !flowthrough(known)) {
|
|
50
|
+
if (!known.introseen)
|
|
51
|
+
navigate('/intro', { replace: true });
|
|
52
|
+
else
|
|
53
|
+
navigate('/onboarding', { replace: true });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let live = true;
|
|
57
|
+
loadOnboardingState().then((state) => {
|
|
58
|
+
if (!live)
|
|
59
|
+
return;
|
|
60
|
+
if (!state.introseen)
|
|
61
|
+
navigate('/intro', { replace: true });
|
|
62
|
+
else if (!state.onboardingseen)
|
|
63
|
+
navigate('/onboarding', { replace: true });
|
|
64
|
+
else
|
|
65
|
+
setPhase('through');
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
live = false;
|
|
69
|
+
};
|
|
70
|
+
}, [embedded, navigate]);
|
|
71
|
+
return phase === 'through' ? children : null;
|
|
72
|
+
}
|
|
73
|
+
export default EntryGate;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export default function MetricStrip({ metrics }) {
|
|
3
|
+
return (_jsx("div", { className: "metric-strip", children: metrics.map((metric) => (_jsxs("div", { className: "metric", children: [_jsx("span", { className: "metric-value", children: metric.value }), _jsx("span", { className: "metric-label", children: metric.label }), _jsx("span", { className: "metric-detail", children: metric.detail })] }, metric.label))) }));
|
|
4
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function RuntimeDiagram(): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the runtime chain of repo, runner and pages as one
|
|
3
|
+
// execution model. Page-owned copy of the runtime diagram: lives inside the
|
|
4
|
+
// home folder that renders it. The topline states the chain it draws — no
|
|
5
|
+
// boot timer, no live indicator: the diagram is the model, never a claim
|
|
6
|
+
// that a runner is running. the copy rides the label store (labels.ts).
|
|
7
|
+
import { ArrowRightRegular as ArrowRight, DatabaseRegular as Database, BranchForkRegular as GitBranch, GlobeRegular as Globe2, ServerRegular as Server, } from '@fluentui/react-icons';
|
|
8
|
+
import { label } from '../../labels.ts';
|
|
9
|
+
/** the four nodes of the chain: the icon and the tone stay data, the words
|
|
10
|
+
* ride the store. */
|
|
11
|
+
const nodes = [
|
|
12
|
+
{ icon: GitBranch, labelkey: 'home.diagram.repo', detailkey: 'home.diagram.repoDetail', tone: 'ink' },
|
|
13
|
+
{ icon: Server, labelkey: 'home.diagram.runner', detailkey: 'home.diagram.runnerDetail', tone: 'ember' },
|
|
14
|
+
{ icon: Database, labelkey: 'home.diagram.memory', detailkey: 'home.diagram.memoryDetail', tone: 'blue' },
|
|
15
|
+
{ icon: Globe2, labelkey: 'home.diagram.surface', detailkey: 'home.diagram.surfaceDetail', tone: 'sage' },
|
|
16
|
+
];
|
|
17
|
+
export default function RuntimeDiagram() {
|
|
18
|
+
return (_jsxs("div", { className: "runtime-diagram", role: "group", "aria-label": label('home.diagram.aria'), children: [_jsxs("div", { className: "diagram-topline", children: [_jsx("span", { children: label('home.diagram.topline') }), _jsx("span", { children: label('home.diagram.chain') })] }), _jsx("div", { className: "diagram-nodes", children: nodes.map((node, index) => {
|
|
19
|
+
const Icon = node.icon;
|
|
20
|
+
return (_jsxs("div", { className: "diagram-node-wrap", children: [_jsxs("div", { className: `diagram-node tone-${node.tone}`, children: [_jsx(Icon, { fontSize: 19, strokeWidth: 1.7 }), _jsxs("div", { children: [_jsx("strong", { children: label(node.labelkey) }), _jsx("small", { children: label(node.detailkey) })] })] }), index < nodes.length - 1 && _jsx(ArrowRight, { className: "diagram-arrow", fontSize: 18 })] }, label(node.labelkey)));
|
|
21
|
+
}) }), _jsxs("div", { className: "diagram-footer", children: [_jsxs("span", { children: [label('home.diagram.usageFlag'), _jsx("b", { children: label('home.diagram.usageFlagValue') })] }), _jsxs("span", { children: [label('home.diagram.latencyBudget'), _jsx("b", { children: label('home.diagram.latencyBudgetValue') })] })] })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* only same-origin page-relative targets are honored; absolute urls,
|
|
3
|
+
* protocol relative (//host), backslash (\host), scheme (javascript:,
|
|
4
|
+
* data:), control characters and path traversal are all rejected.
|
|
5
|
+
* targets stay page-relative (no leading slash) so the redirect works
|
|
6
|
+
* both on the self-hosted node (/, /login) and inside sub-path static
|
|
7
|
+
* hosting such as github pages (/saddle/login). the redirect target
|
|
8
|
+
* never depends on an unvalidated user value. legacy ".html" suffixes
|
|
9
|
+
* from the absorbed static pages map to the spa route of the same name.
|
|
10
|
+
*
|
|
11
|
+
* @returns the sanitized page-relative target.
|
|
12
|
+
*/
|
|
13
|
+
export declare function safenext(): string;
|
|
14
|
+
/**
|
|
15
|
+
* the sign-in form: username + password only, generic error messages on
|
|
16
|
+
* purpose (no user enumeration, no cause leak), aria-invalid feedback and
|
|
17
|
+
* loading states while the submission is in flight.
|
|
18
|
+
*/
|
|
19
|
+
export default function LoginForm(): import("react").JSX.Element;
|