@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,21 @@
|
|
|
1
|
+
/** the sandbox specification selection held by the console page. */
|
|
2
|
+
export type SandboxSpecSelection = {
|
|
3
|
+
model: string;
|
|
4
|
+
quotamb: number;
|
|
5
|
+
vcpus: number;
|
|
6
|
+
ramgb: number;
|
|
7
|
+
gpu: string;
|
|
8
|
+
mig: string;
|
|
9
|
+
};
|
|
10
|
+
type SpecPanelProps = {
|
|
11
|
+
spec: SandboxSpecSelection;
|
|
12
|
+
onChange: (patch: Partial<SandboxSpecSelection>) => void;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* the sandbox specification panel: every control keeps the 44px touch
|
|
17
|
+
* target of the absorbed static page and disables while a sandbox is
|
|
18
|
+
* booting or running.
|
|
19
|
+
*/
|
|
20
|
+
export default function SpecPanel({ spec, onChange, disabled }: SpecPanelProps): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Spec panel: faithful absorption of the console.html spec controls — cpu
|
|
3
|
+
// model, workspace quota, vcpus slider, ram slider + exact numeric field,
|
|
4
|
+
// gpu model and mig profile, each with the live catalog hints.
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { label, labelfill } from '../../labels.ts';
|
|
7
|
+
import { cpudata, gpudata } from '../../sandbox.ts';
|
|
8
|
+
/** workspace quota options in MiB (16 selected by default). */
|
|
9
|
+
const quotaoptions = [4, 16, 64, 256];
|
|
10
|
+
/** the mig slicing profile values of the 96 gb-class virtual device. */
|
|
11
|
+
const migvalues = ['off', '1g.24gb', '2g.48gb', '4g.96gb'];
|
|
12
|
+
/** the mig profile labels of the store, keyed by the profile value. */
|
|
13
|
+
const miglabels = {
|
|
14
|
+
off: 'console.spec.migOff',
|
|
15
|
+
'1g.24gb': 'console.spec.mig1g',
|
|
16
|
+
'2g.48gb': 'console.spec.mig2g',
|
|
17
|
+
'4g.96gb': 'console.spec.mig4g',
|
|
18
|
+
};
|
|
19
|
+
/** the mig profile options: the value stays data, the label rides the store. */
|
|
20
|
+
function migoptions() {
|
|
21
|
+
return migvalues.map((value) => ({ value, label: label(miglabels[value]) }));
|
|
22
|
+
}
|
|
23
|
+
/** ram ceiling shared by the slider and the exact numeric field. */
|
|
24
|
+
const ramceiling = 18432;
|
|
25
|
+
/**
|
|
26
|
+
* the sandbox specification panel: every control keeps the 44px touch
|
|
27
|
+
* target of the absorbed static page and disables while a sandbox is
|
|
28
|
+
* booting or running.
|
|
29
|
+
*/
|
|
30
|
+
export default function SpecPanel({ spec, onChange, disabled }) {
|
|
31
|
+
const ramnumref = useRef(null);
|
|
32
|
+
const [ramdraft, setRamdraft] = useState(String(spec.ramgb));
|
|
33
|
+
/** the numeric field follows the slider unless the operator is typing. */
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (document.activeElement !== ramnumref.current) {
|
|
36
|
+
setRamdraft(String(spec.ramgb));
|
|
37
|
+
}
|
|
38
|
+
}, [spec.ramgb]);
|
|
39
|
+
/** the exact ram field clamps to the 1..18432 gb plan range. */
|
|
40
|
+
const onramnuminput = (raw) => {
|
|
41
|
+
setRamdraft(raw);
|
|
42
|
+
const clamped = Math.max(1, Math.min(ramceiling, Number(raw) || 1));
|
|
43
|
+
onChange({ ramgb: clamped });
|
|
44
|
+
};
|
|
45
|
+
const cpu = cpudata.find((entry) => entry.model === spec.model);
|
|
46
|
+
const gpu = gpudata.find((entry) => entry.id === spec.gpu);
|
|
47
|
+
const cpuhint = cpu
|
|
48
|
+
? labelfill('console.spec.cpuHint', {
|
|
49
|
+
arch: cpu.microarch,
|
|
50
|
+
memory: cpu.memorytype,
|
|
51
|
+
max: String(cpu.maxmemorygb),
|
|
52
|
+
watts: String(cpu.tdpwatts),
|
|
53
|
+
})
|
|
54
|
+
: '';
|
|
55
|
+
const gpuhint = gpu
|
|
56
|
+
? labelfill('console.spec.gpuHint', {
|
|
57
|
+
arch: gpu.arch,
|
|
58
|
+
bandwidth: String(gpu.bandwidthgbs),
|
|
59
|
+
mig: gpu.mig ? label('console.spec.migSupported') : label('console.spec.migEmulated'),
|
|
60
|
+
})
|
|
61
|
+
: '';
|
|
62
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "spec-field", children: [_jsx("label", { htmlFor: "cpusel", children: label('console.spec.cpuLabel') }), _jsx("select", { className: "spec-select", id: "cpusel", disabled: disabled, "aria-label": label('console.spec.cpuAria'), value: spec.model, onChange: (event) => onChange({ model: event.target.value }), children: cpudata.map((entry) => (_jsx("option", { value: entry.model, children: labelfill('console.spec.cpuOption', {
|
|
63
|
+
model: entry.model,
|
|
64
|
+
cores: String(entry.cores),
|
|
65
|
+
threads: String(entry.threads),
|
|
66
|
+
}) }, entry.model))) }), _jsxs("div", { className: "spec-field", children: [_jsx("label", { htmlFor: "quotasel", children: label('console.spec.quotaLabel') }), _jsx("select", { className: "spec-select", id: "quotasel", disabled: disabled, "aria-label": label('console.spec.quotaAria'), value: String(spec.quotamb), onChange: (event) => onChange({ quotamb: Number(event.target.value) }), children: quotaoptions.map((quota) => (_jsx("option", { value: String(quota), children: labelfill('console.spec.quotaOption', { quota: String(quota) }) }, quota))) })] }), _jsx("p", { className: "spec-hint", children: cpuhint })] }), _jsxs("div", { className: "spec-field", children: [_jsxs("label", { htmlFor: "vcpurange", children: [label('console.spec.vcpusLabel'), " ", _jsx("span", { className: "spec-val", children: spec.vcpus })] }), _jsx("input", { className: "spec-range", id: "vcpurange", type: "range", min: 1, max: 192, value: spec.vcpus, disabled: disabled, "aria-label": label('console.spec.vcpusAria'), style: {
|
|
67
|
+
'--r3-meter': `${Math.max(2, Math.min(100, (spec.vcpus / 192) * 100))}%`,
|
|
68
|
+
}, onChange: (event) => onChange({ vcpus: Number(event.target.value) }) })] }), _jsxs("div", { className: "spec-field", children: [_jsxs("label", { htmlFor: "ramrange", children: [label('console.spec.ramLabel'), " ", _jsx("span", { className: "spec-val", children: spec.ramgb })] }), _jsx("input", { className: "spec-range", id: "ramrange", type: "range", min: 1, max: ramceiling, value: spec.ramgb, disabled: disabled, "aria-label": label('console.spec.ramAria'), style: {
|
|
69
|
+
'--r3-meter': `${Math.max(2, Math.min(100, (spec.ramgb / ramceiling) * 100))}%`,
|
|
70
|
+
}, onChange: (event) => onChange({ ramgb: Number(event.target.value) }) }), _jsx("label", { htmlFor: "ramnum", children: label('console.spec.ramExactLabel') }), _jsx("input", { ref: ramnumref, className: "spec-select spec-number", id: "ramnum", type: "number", min: 1, max: ramceiling, value: ramdraft, disabled: disabled, "aria-label": label('console.spec.ramExactAria'), onChange: (event) => onramnuminput(event.target.value) })] }), _jsxs("div", { className: "spec-field", children: [_jsx("label", { htmlFor: "gpusel", children: label('console.spec.gpuLabel') }), _jsx("select", { className: "spec-select", id: "gpusel", disabled: disabled, "aria-label": label('console.spec.gpuAria'), value: spec.gpu, onChange: (event) => onChange({ gpu: event.target.value }), children: gpudata.map((entry) => (_jsx("option", { value: entry.id, children: labelfill('console.spec.gpuOption', {
|
|
71
|
+
name: entry.name,
|
|
72
|
+
memtype: entry.memtype,
|
|
73
|
+
gb: String(entry.vrammib / 1024),
|
|
74
|
+
}) }, entry.id))) }), _jsx("p", { className: "spec-hint", children: gpuhint })] }), _jsxs("div", { className: "spec-field", children: [_jsx("label", { htmlFor: "migsel", children: label('console.spec.migLabel') }), _jsx("select", { className: "spec-select", id: "migsel", disabled: disabled, "aria-label": label('console.spec.migAria'), value: spec.mig, onChange: (event) => onChange({ mig: event.target.value }), children: migoptions().map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) })] })] }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** one rendered output row; cls mirrors the static page row classes. */
|
|
2
|
+
export type TerminalRow = {
|
|
3
|
+
id: number;
|
|
4
|
+
text: string;
|
|
5
|
+
cls: string;
|
|
6
|
+
};
|
|
7
|
+
/** the terminal lifecycle label shown in the bar. */
|
|
8
|
+
export type TerminalState = 'halted' | 'booting' | 'running' | 'stopped';
|
|
9
|
+
type TerminalProps = {
|
|
10
|
+
rows: TerminalRow[];
|
|
11
|
+
prompt: string;
|
|
12
|
+
state: TerminalState;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
oncommand: (command: string) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* the virtual container terminal: a dark log pane (role=log, polite
|
|
18
|
+
* aria-live), a command input with history browsing and tab completion,
|
|
19
|
+
* and the lifecycle label in the bar.
|
|
20
|
+
*/
|
|
21
|
+
export default function Terminal({ rows, prompt, state, enabled, oncommand }: TerminalProps): import("react").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Terminal: faithful absorption of the console.html terminal — animated boot
|
|
3
|
+
// output (rows appended by the console page), command history with the arrow
|
|
4
|
+
// keys, tab completion over the sandbox command list, the live prompt and
|
|
5
|
+
// the focus-on-click output pane.
|
|
6
|
+
import { useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { label } from '../../labels.ts';
|
|
8
|
+
import { commands } from '../../sandbox.ts';
|
|
9
|
+
/** the lifecycle state tokens answer their store labels (data stays data,
|
|
10
|
+
* the rendered words ride the label store). */
|
|
11
|
+
const statelabels = {
|
|
12
|
+
halted: 'console.terminal.state.halted',
|
|
13
|
+
booting: 'console.terminal.state.booting',
|
|
14
|
+
running: 'console.terminal.state.running',
|
|
15
|
+
stopped: 'console.terminal.state.stopped',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* the virtual container terminal: a dark log pane (role=log, polite
|
|
19
|
+
* aria-live), a command input with history browsing and tab completion,
|
|
20
|
+
* and the lifecycle label in the bar.
|
|
21
|
+
*/
|
|
22
|
+
export default function Terminal({ rows, prompt, state, enabled, oncommand }) {
|
|
23
|
+
const outref = useRef(null);
|
|
24
|
+
const inputref = useRef(null);
|
|
25
|
+
const [value, setValue] = useState('');
|
|
26
|
+
const historyref = useRef([]);
|
|
27
|
+
const historyindex = useRef(-1);
|
|
28
|
+
/** keep the output pinned to the newest row, like scrollterm(). */
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const out = outref.current;
|
|
31
|
+
if (out)
|
|
32
|
+
out.scrollTop = out.scrollHeight;
|
|
33
|
+
}, [rows]);
|
|
34
|
+
/** focus the command input once the sandbox is running. */
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (enabled)
|
|
37
|
+
inputref.current?.focus();
|
|
38
|
+
}, [enabled]);
|
|
39
|
+
const onsubmit = (event) => {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
if (!enabled)
|
|
42
|
+
return;
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
setValue('');
|
|
45
|
+
if (trimmed.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
historyref.current.push(trimmed);
|
|
48
|
+
historyindex.current = historyref.current.length;
|
|
49
|
+
oncommand(trimmed);
|
|
50
|
+
};
|
|
51
|
+
const onkeydown = (event) => {
|
|
52
|
+
if (event.key === 'ArrowUp') {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (historyref.current.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
historyindex.current = Math.max(0, historyindex.current - 1);
|
|
57
|
+
setValue(historyref.current[historyindex.current] ?? '');
|
|
58
|
+
}
|
|
59
|
+
else if (event.key === 'ArrowDown') {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
if (historyref.current.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
historyindex.current = Math.min(historyref.current.length, historyindex.current + 1);
|
|
64
|
+
setValue(historyref.current[historyindex.current] ?? '');
|
|
65
|
+
}
|
|
66
|
+
else if (event.key === 'Tab') {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
const prefix = value.trim();
|
|
69
|
+
if (prefix.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
const match = commands.find((entry) => entry.startsWith(prefix));
|
|
72
|
+
if (match !== undefined)
|
|
73
|
+
setValue(match);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return (_jsxs("section", { className: "term", "data-state": state, "aria-labelledby": "termtitle", children: [_jsxs("div", { className: "term-bar", children: [_jsx("span", { id: "termtitle", children: label('console.terminal.title') }), _jsx("span", { className: "term-state", "aria-live": "polite", children: label(statelabels[state]) })] }), _jsx("pre", { className: "term-out", role: "log", "aria-live": "polite", "aria-label": label('console.terminal.outputAria'), ref: outref, onClick: () => inputref.current?.focus(), children: rows.map((row) => (_jsxs("span", { className: row.cls === '' ? undefined : row.cls, children: [row.text, '\n'] }, row.id))) }), _jsxs("form", { className: enabled ? 'term-in' : 'term-in off', onSubmit: onsubmit, children: [_jsx("label", { className: "term-prompt", htmlFor: "termin", children: prompt }), _jsx("input", { id: "termin", ref: inputref, className: "term-input", autoComplete: "off", spellCheck: false, "aria-label": label('console.terminal.inputAria'), disabled: !enabled, value: value, onChange: (event) => setValue(event.target.value), onKeyDown: onkeydown })] })] }));
|
|
77
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SessionUser } from '../../dashboardlib.ts';
|
|
2
|
+
/** the local browser session normalized at boot (defensive read). */
|
|
3
|
+
export type LocalSessionView = {
|
|
4
|
+
user: string;
|
|
5
|
+
role: string;
|
|
6
|
+
issuedat: number;
|
|
7
|
+
expiresat: number;
|
|
8
|
+
};
|
|
9
|
+
type AccountPanelProps = {
|
|
10
|
+
/** "api" renders the /auth/me account; "local" the browser session. */
|
|
11
|
+
mode: 'api' | 'local';
|
|
12
|
+
/** the normalized account (api mode; null while the session boots). */
|
|
13
|
+
user: SessionUser | null;
|
|
14
|
+
/** the normalized local session (local mode). */
|
|
15
|
+
local: LocalSessionView | null;
|
|
16
|
+
};
|
|
17
|
+
export default function AccountPanel({ mode, user, local }: AccountPanelProps): import("react").JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the account panel absorbed from dashboard.js — dl of
|
|
3
|
+
// session fields (api) or the browser-local account (static edge),
|
|
4
|
+
// with the backup/restore keyfile covering a full storage wipe. the widget
|
|
5
|
+
// owns its states: the loading skeleton before the session lands, the busy
|
|
6
|
+
// flag on the backup controls and the inline status line instead of a
|
|
7
|
+
// browser dialog.
|
|
8
|
+
import { useRef, useState } from 'react';
|
|
9
|
+
import { fmtdate } from '../../dashboardlib.ts';
|
|
10
|
+
import { label, labelfill } from '../../labels.ts';
|
|
11
|
+
import { adminusers, exportaccounts, importaccounts } from '../../localauth.ts';
|
|
12
|
+
export default function AccountPanel({ mode, user, local }) {
|
|
13
|
+
const fileref = useRef(null);
|
|
14
|
+
const [busy, setbusy] = useState(false);
|
|
15
|
+
const [status, setstatus] = useState('');
|
|
16
|
+
/** downloads the accounts keyfile (salt + hash only, never passwords). */
|
|
17
|
+
async function backup() {
|
|
18
|
+
setbusy(true);
|
|
19
|
+
setstatus('');
|
|
20
|
+
try {
|
|
21
|
+
const count = await exportaccounts();
|
|
22
|
+
setstatus(count > 0
|
|
23
|
+
? labelfill('dashboard.account.backupDone', { count: String(count) })
|
|
24
|
+
: label('dashboard.account.backupEmpty'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
setstatus(label('dashboard.account.backupFailed'));
|
|
28
|
+
}
|
|
29
|
+
setbusy(false);
|
|
30
|
+
}
|
|
31
|
+
/** imports accounts from the chosen keyfile (merge, never overwrite). */
|
|
32
|
+
async function restore(file) {
|
|
33
|
+
setbusy(true);
|
|
34
|
+
setstatus('');
|
|
35
|
+
try {
|
|
36
|
+
const imported = await importaccounts(file);
|
|
37
|
+
setstatus(imported > 0
|
|
38
|
+
? labelfill('dashboard.account.restoreDone', { count: String(imported) })
|
|
39
|
+
: label('dashboard.account.restoreEmpty'));
|
|
40
|
+
window.location.reload();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error && error.message ? error.message : label('dashboard.account.importFailed');
|
|
44
|
+
setstatus(message);
|
|
45
|
+
setbusy(false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (mode === 'local' && local !== null) {
|
|
49
|
+
const isadmin = local.role === 'admin';
|
|
50
|
+
return (_jsxs("div", { children: [_jsxs("dl", { className: "dash-kv", children: [_jsx("dt", { children: label('dashboard.account.username') }), _jsx("dd", { children: local.user }), _jsx("dt", { children: label('dashboard.account.role') }), _jsx("dd", { children: isadmin
|
|
51
|
+
? labelfill('dashboard.account.adminRole', { list: adminusers.join(', ') })
|
|
52
|
+
: local.role }), _jsx("dt", { children: label('dashboard.account.mode') }), _jsx("dd", { children: label('dashboard.account.modeLocal') }), _jsx("dt", { children: label('dashboard.account.signedIn') }), _jsx("dd", { children: fmtdate(new Date(local.issuedat).toISOString()) }), _jsx("dt", { children: label('dashboard.account.expires') }), _jsx("dd", { children: fmtdate(new Date(local.expiresat).toISOString()) })] }), _jsxs("div", { className: "dash-actions", children: [_jsx("button", { type: "button", className: "dash-btn", disabled: busy, onClick: () => void backup(), children: label('dashboard.account.backup') }), _jsx("button", { type: "button", className: "dash-btn", disabled: busy, onClick: () => {
|
|
53
|
+
fileref.current?.click();
|
|
54
|
+
}, children: label('dashboard.account.restore') })] }), status !== '' && (_jsx("p", { className: "dash-statusnote", role: "status", "aria-live": "polite", children: status })), _jsx("input", { ref: fileref, type: "file", accept: "application/json,.json", hidden: true, onChange: (event) => {
|
|
55
|
+
const file = event.target.files?.[0];
|
|
56
|
+
if (file !== undefined) {
|
|
57
|
+
void restore(file);
|
|
58
|
+
}
|
|
59
|
+
} })] }));
|
|
60
|
+
}
|
|
61
|
+
/* api mode: the account is null only while the session bootstrap runs —
|
|
62
|
+
the skeleton is the honest load, never a invented field */
|
|
63
|
+
const account = user;
|
|
64
|
+
return (_jsxs("dl", { className: "dash-kv", children: [_jsx("dt", { children: label('dashboard.account.username') }), _jsx("dd", { children: account === null ? _jsx("span", { className: "dash-skeleton" }) : account.username }), _jsx("dt", { children: label('dashboard.account.role') }), _jsx("dd", { children: account === null ? _jsx("span", { className: "dash-skeleton" }) : account.role }), _jsx("dt", { children: label('dashboard.account.created') }), _jsx("dd", { children: account === null ? _jsx("span", { className: "dash-skeleton" }) : fmtdate(account.createdat) }), _jsx("dt", { children: label('dashboard.account.lastLogin') }), _jsx("dd", { children: account === null ? _jsx("span", { className: "dash-skeleton" }) : fmtdate(account.lastloginat) }), account?.id !== null && account?.id !== undefined && (_jsxs(_Fragment, { children: [_jsx("dt", { children: label('dashboard.account.id') }), _jsx("dd", { children: String(account.id) })] }))] }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the admin view absorbed from dashboard.js — overview
|
|
3
|
+
// cards with global refresh, mesh nodes, users, global sandboxes and
|
|
4
|
+
// audit; on the static edge, the browser's own overview.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { apiresult, fmtuptime, pick, readlocalsandboxes } from '../../dashboardlib.ts';
|
|
7
|
+
import { label } from '../../labels.ts';
|
|
8
|
+
import { available } from '../../localauth.ts';
|
|
9
|
+
import AuditLog from './DashboardAuditlog.tsx';
|
|
10
|
+
import GlobalSandboxesTable from './DashboardGlobalsandboxestable.tsx';
|
|
11
|
+
import MeshNodesTable from './DashboardMeshnodestable.tsx';
|
|
12
|
+
import UsersTable from './DashboardUserstable.tsx';
|
|
13
|
+
/** the honest failure state: every counter names its absence. */
|
|
14
|
+
function unavailableoverview() {
|
|
15
|
+
const na = label('dashboard.admin.na');
|
|
16
|
+
return { users: na, nodes: na, sandboxes: na, sessions: na, events: na, uptime: na };
|
|
17
|
+
}
|
|
18
|
+
/** the browser-local overview of the static edge admin. */
|
|
19
|
+
function localoverview() {
|
|
20
|
+
let accounts = 0;
|
|
21
|
+
try {
|
|
22
|
+
const raw = window.localStorage.getItem('saddle_local_users');
|
|
23
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
24
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
25
|
+
accounts = Object.keys(parsed).length;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* no registry in this browser */
|
|
30
|
+
}
|
|
31
|
+
const localboxes = available() ? readlocalsandboxes().length : 0;
|
|
32
|
+
return {
|
|
33
|
+
users: String(accounts),
|
|
34
|
+
nodes: label('dashboard.admin.localNodes'),
|
|
35
|
+
sandboxes: String(localboxes),
|
|
36
|
+
sessions: label('dashboard.admin.localSessions'),
|
|
37
|
+
events: label('dashboard.admin.na'),
|
|
38
|
+
uptime: label('dashboard.admin.na'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export default function AdminPanel({ mode }) {
|
|
42
|
+
/* null while the overview loads: the cards render the honest skeleton */
|
|
43
|
+
const [overview, setoverview] = useState(null);
|
|
44
|
+
const [reloadkey, setreloadkey] = useState(0);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (mode === 'local') {
|
|
47
|
+
setoverview(localoverview());
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let stopped = false;
|
|
51
|
+
(async () => {
|
|
52
|
+
try {
|
|
53
|
+
const result = await apiresult('/api/v1/admin/overview');
|
|
54
|
+
if (stopped) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!result.ok || result.body === null || typeof result.body !== 'object') {
|
|
58
|
+
setoverview(unavailableoverview());
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const body = result.body;
|
|
62
|
+
const counts = body.counts !== null && typeof body.counts === 'object'
|
|
63
|
+
? body.counts
|
|
64
|
+
: body.overview !== null && typeof body.overview === 'object'
|
|
65
|
+
? body.overview
|
|
66
|
+
: body;
|
|
67
|
+
setoverview({
|
|
68
|
+
users: String(pick(counts, ['users', 'userCount', 'totalUsers', 'usercount'], label('dashboard.admin.na'))),
|
|
69
|
+
nodes: String(pick(counts, ['nodes', 'nodeCount', 'totalNodes', 'nodecount'], label('dashboard.admin.na'))),
|
|
70
|
+
sandboxes: String(pick(counts, ['sandboxes', 'sandboxCount', 'totalSandboxes'], label('dashboard.admin.na'))),
|
|
71
|
+
sessions: String(pick(counts, ['sessions', 'sessionCount', 'totalSessions'], label('dashboard.admin.na'))),
|
|
72
|
+
events: String(pick(counts, ['events', 'eventCount', 'totalEvents'], label('dashboard.admin.na'))),
|
|
73
|
+
uptime: fmtuptime(pick(counts, ['uptime', 'uptimeSeconds', 'uptime_seconds'], 0)),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
if (!stopped) {
|
|
78
|
+
setoverview(unavailableoverview());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
return () => {
|
|
83
|
+
stopped = true;
|
|
84
|
+
};
|
|
85
|
+
}, [mode, reloadkey]);
|
|
86
|
+
return (_jsxs("div", { children: [_jsxs("section", { className: "dash-panel", "aria-labelledby": "overtitle", children: [_jsx("h2", { id: "overtitle", children: label('dashboard.admin.overview') }), _jsxs("div", { className: "dash-cards", children: [_jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.users') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.users })] }), _jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.nodes') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.nodes })] }), _jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.sandboxes') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.sandboxes })] }), _jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.sessions') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.sessions })] }), _jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.events') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.events })] }), _jsxs("div", { className: "dash-card", children: [_jsx("div", { className: "k", children: label('dashboard.card.uptime') }), _jsx("div", { className: "v", children: overview === null ? _jsx("span", { className: "dash-skeleton" }) : overview.uptime })] })] }), _jsx("div", { className: "dash-actions", children: _jsx("button", { type: "button", className: "button", "aria-label": label('dashboard.admin.refreshAria'), "aria-busy": overview === null ? 'true' : undefined, onClick: () => {
|
|
87
|
+
setoverview(null);
|
|
88
|
+
setreloadkey((key) => key + 1);
|
|
89
|
+
}, children: label('dashboard.admin.refresh') }) })] }), _jsxs("section", { className: "dash-panel", "aria-labelledby": "nodestitle", children: [_jsx("h2", { id: "nodestitle", children: label('dashboard.admin.nodes') }), _jsx(MeshNodesTable, { mode: mode, reloadkey: reloadkey })] }), _jsxs("section", { className: "dash-panel", "aria-labelledby": "userstitle", children: [_jsx("h2", { id: "userstitle", children: label('dashboard.admin.users') }), _jsx(UsersTable, { mode: mode, reloadkey: reloadkey })] }), _jsxs("section", { className: "dash-panel", "aria-labelledby": "boxestitle", children: [_jsx("h2", { id: "boxestitle", children: label('dashboard.admin.sandboxes') }), _jsx(GlobalSandboxesTable, { mode: mode, reloadkey: reloadkey })] }), _jsxs("section", { className: "dash-panel", "aria-labelledby": "audittitle", children: [_jsx("h2", { id: "audittitle", children: label('dashboard.admin.audit') }), _jsx(AuditLog, { mode: mode, reloadkey: reloadkey })] })] }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type AuditLogProps = {
|
|
2
|
+
/** "api" lists /api/v1/admin/audit; "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 AuditLog({ mode, reloadkey }: AuditLogProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the audit log absorbed from dashboard.js — action,
|
|
3
|
+
// user, ip and instant of the entries the main node registers; on the
|
|
4
|
+
// static edge, the honest note.
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { apiresult, asarray, fmtdate, pick } from '../../dashboardlib.ts';
|
|
7
|
+
import { label, labelfill } from '../../labels.ts';
|
|
8
|
+
export default function AuditLog({ 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/audit');
|
|
19
|
+
if (stopped) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
seterror(labelfill('dashboard.audit.unavailable', { status: String(result.status) }));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
seterror('');
|
|
27
|
+
setitems(asarray(result.body, ['entries', 'audit', 'items', 'data', 'list', 'logs']));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
if (!stopped) {
|
|
31
|
+
seterror(label('dashboard.audit.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.audit.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.audit.tableAria'), children: _jsxs("table", { children: [items.length === 0 && error === '' && (_jsx("caption", { className: "dash-empty", children: label('dashboard.audit.empty') })), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: label('dashboard.audit.colAction') }), _jsx("th", { scope: "col", children: label('dashboard.audit.colUser') }), _jsx("th", { scope: "col", children: label('dashboard.audit.colIp') }), _jsx("th", { scope: "col", children: label('dashboard.audit.colWhen') })] }) }), _jsx("tbody", { children: items.map((entry) => {
|
|
45
|
+
const action = String(pick(entry, ['action', 'type', 'event'], '?'));
|
|
46
|
+
const username = String(pick(entry, ['username', 'actor', 'by', 'user', 'userid', 'user_id'], '-'));
|
|
47
|
+
const ip = String(pick(entry, ['ip', 'sourceIp', 'address'], '-'));
|
|
48
|
+
return (_jsxs("tr", { children: [_jsx("td", { children: action }), _jsx("td", { children: username }), _jsx("td", { className: "wrapcell", children: ip }), _jsx("td", { children: fmtdate(pick(entry, ['at', 'ts', 'time', 'createdAt', 'when'], null)) })] }, `${action}|${username}|${ip}`));
|
|
49
|
+
}) })] }) }), error !== '' && _jsx("p", { className: "dash-panelerror", children: error })] }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type CreateSandboxFormProps = {
|
|
2
|
+
/** "api" posts /api/v1/sandboxes; "local" drives the browser engine shelf. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** fires after a successful creation so the list reloads. */
|
|
5
|
+
oncreated: () => void;
|
|
6
|
+
/** the refresh action beside the submit button. */
|
|
7
|
+
onrefresh: () => void;
|
|
8
|
+
};
|
|
9
|
+
export default function CreateSandboxForm({ mode, oncreated, onrefresh }: CreateSandboxFormProps): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the creation form absorbed from dashboard.js —
|
|
3
|
+
// engine catalogs (sandbox.ts) with static fallback, spec posted to the
|
|
4
|
+
// api or materialized in the local engine (static mode).
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { apiresult, errormessage, fallbackcpus, fallbackgpus, readlocalsandboxes, writelocalsandboxes, } from '../../dashboardlib.ts';
|
|
7
|
+
import { label, labelfill } from '../../labels.ts';
|
|
8
|
+
import { cpudata, createSandboxState, gpudata } from '../../sandbox.ts';
|
|
9
|
+
/** the spec fields of one creation request (model from the reviewed catalog). */
|
|
10
|
+
function catalogs() {
|
|
11
|
+
const cpumodels = Array.isArray(cpudata) && cpudata.length > 0 ? cpudata.map((cpu) => cpu.model) : fallbackcpus;
|
|
12
|
+
const gpuids = Array.isArray(gpudata) && gpudata.length > 0 ? gpudata.map((gpu) => gpu.id) : fallbackgpus;
|
|
13
|
+
return { cpumodels, gpuids };
|
|
14
|
+
}
|
|
15
|
+
export default function CreateSandboxForm({ mode, oncreated, onrefresh }) {
|
|
16
|
+
const { cpumodels, gpuids } = catalogs();
|
|
17
|
+
const [model, setmodel] = useState(cpumodels[0] ?? fallbackcpus[0]);
|
|
18
|
+
const [vcpus, setvcpus] = useState(8);
|
|
19
|
+
const [ramgb, setramgb] = useState(32);
|
|
20
|
+
const [gpu, setgpu] = useState(gpuids[0] ?? fallbackgpus[0]);
|
|
21
|
+
const [mig, setmig] = useState('off');
|
|
22
|
+
const [busy, setbusy] = useState(false);
|
|
23
|
+
const [error, seterror] = useState('');
|
|
24
|
+
async function submit(event) {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
seterror('');
|
|
27
|
+
const spec = { model, vcpus: Number(vcpus) || 8, ramgb: Number(ramgb) || 32, gpu, mig };
|
|
28
|
+
if (mode === 'local') {
|
|
29
|
+
/* the browser-pure engine port drives the shelf: the same
|
|
30
|
+
* createSandboxState the console runs, no server involved.
|
|
31
|
+
* the record is a stored spec — nothing executes here —
|
|
32
|
+
* so the honest state chip reads 'created', never
|
|
33
|
+
* 'running'. */
|
|
34
|
+
const state = createSandboxState(spec);
|
|
35
|
+
const list = readlocalsandboxes();
|
|
36
|
+
list.unshift({
|
|
37
|
+
id: state.id,
|
|
38
|
+
spec: {
|
|
39
|
+
model: state.model,
|
|
40
|
+
vcpus: state.vcpus,
|
|
41
|
+
ramgb: state.ramgb,
|
|
42
|
+
gpu: state.gpu,
|
|
43
|
+
mig: state.mig,
|
|
44
|
+
},
|
|
45
|
+
createdat: new Date().toISOString(),
|
|
46
|
+
state: 'created',
|
|
47
|
+
});
|
|
48
|
+
writelocalsandboxes(list);
|
|
49
|
+
oncreated();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setbusy(true);
|
|
53
|
+
try {
|
|
54
|
+
const result = await apiresult('/api/v1/sandboxes', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
body: JSON.stringify(spec),
|
|
57
|
+
});
|
|
58
|
+
if (result.ok) {
|
|
59
|
+
oncreated();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
seterror(errormessage(result, labelfill('dashboard.create.failedStatus', { status: String(result.status) })));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
seterror(label('dashboard.create.unreachable'));
|
|
67
|
+
}
|
|
68
|
+
setbusy(false);
|
|
69
|
+
}
|
|
70
|
+
return (_jsxs("form", { noValidate: true, "aria-label": label('dashboard.create.formAria'), onSubmit: submit, children: [_jsxs("div", { className: "dash-formrow", children: [_jsxs("div", { className: "dash-field", children: [_jsx("label", { htmlFor: "newmodel", children: label('dashboard.create.cpuLabel') }), _jsx("select", { id: "newmodel", "aria-label": label('dashboard.create.cpuAria'), value: model, onChange: (event) => setmodel(event.target.value), children: cpumodels.map((entry) => (_jsx("option", { value: entry, children: entry }, entry))) })] }), _jsxs("div", { className: "dash-field", children: [_jsx("label", { htmlFor: "newvcpus", children: label('dashboard.create.vcpusLabel') }), _jsx("input", { type: "number", id: "newvcpus", min: 1, max: 192, value: vcpus, "aria-label": label('dashboard.create.vcpusAria'), onChange: (event) => setvcpus(Number(event.target.value)) })] }), _jsxs("div", { className: "dash-field", children: [_jsx("label", { htmlFor: "newram", children: label('dashboard.create.ramLabel') }), _jsx("input", { type: "number", id: "newram", min: 1, max: 1024, value: ramgb, "aria-label": label('dashboard.create.ramAria'), onChange: (event) => setramgb(Number(event.target.value)) })] }), _jsxs("div", { className: "dash-field", children: [_jsx("label", { htmlFor: "newgpu", children: label('dashboard.create.gpuLabel') }), _jsx("select", { id: "newgpu", "aria-label": label('dashboard.create.gpuAria'), value: gpu, onChange: (event) => setgpu(event.target.value), children: gpuids.map((entry) => (_jsx("option", { value: entry, children: entry }, entry))) })] }), _jsxs("div", { className: "dash-field", children: [_jsx("label", { htmlFor: "newmig", children: label('dashboard.create.migLabel') }), _jsxs("select", { id: "newmig", "aria-label": label('dashboard.create.migAria'), value: mig, onChange: (event) => setmig(event.target.value), children: [_jsx("option", { value: "off", children: "off" }), _jsx("option", { value: "1g.24gb", children: "1g.24gb" }), _jsx("option", { value: "2g.48gb", children: "2g.48gb" }), _jsx("option", { value: "4g.96gb", children: "4g.96gb" })] })] })] }), error !== '' && (_jsx("p", { className: "dash-panelerror", role: "alert", children: error })), _jsxs("div", { className: "dash-actions", children: [_jsx("button", { type: "submit", className: "primary", disabled: busy, "aria-busy": busy ? 'true' : undefined, "aria-label": label('dashboard.create.submitAria'), children: label('dashboard.create.submit') }), _jsx("button", { type: "button", className: "button", "aria-label": label('dashboard.create.refreshAria'), onClick: onrefresh, children: label('dashboard.create.refresh') })] })] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type EventsTimelineProps = {
|
|
2
|
+
/** "api" polls the bus; "local" shows the static-edge note. */
|
|
3
|
+
mode: 'api' | 'local';
|
|
4
|
+
/** false while the session bootstrap runs (avoids a premature 401). */
|
|
5
|
+
ready: boolean;
|
|
6
|
+
};
|
|
7
|
+
export default function EventsTimeline({ mode, ready }: EventsTimelineProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Signal & Ledger: the bus event timeline absorbed from
|
|
3
|
+
// dashboard.js — poll of /api/v1/events?since= every 10 s with cursor,
|
|
4
|
+
// prepend and a 50-entry cap; on the static edge it becomes the honest note.
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { apiresult, asarray, fmtdate, pick } from '../../dashboardlib.ts';
|
|
7
|
+
import { label } from '../../labels.ts';
|
|
8
|
+
/** classifies one topic into the timeline color kinds. */
|
|
9
|
+
function kindfor(topic) {
|
|
10
|
+
if (/exec/i.test(topic)) {
|
|
11
|
+
return 'exec';
|
|
12
|
+
}
|
|
13
|
+
if (/mesh|node|ping/i.test(topic)) {
|
|
14
|
+
return 'mesh';
|
|
15
|
+
}
|
|
16
|
+
return 'lifecycle';
|
|
17
|
+
}
|
|
18
|
+
/** projects one bus event into a rendered entry (defensive). */
|
|
19
|
+
function toentry(record, index) {
|
|
20
|
+
const topic = String(pick(record, ['topic', 'type', 'event', 'name'], 'event'));
|
|
21
|
+
const detail = pick(record, ['detail', 'data', 'message', 'payload'], '');
|
|
22
|
+
const at = pick(record, ['at', 'ts', 'time', 'createdAt', 'created_at', 'when'], null);
|
|
23
|
+
return {
|
|
24
|
+
key: `${String(pick(record, ['id', 'seq'], index))}-${index}`,
|
|
25
|
+
kind: kindfor(topic),
|
|
26
|
+
time: fmtdate(at),
|
|
27
|
+
topic,
|
|
28
|
+
detail: typeof detail === 'string' ? detail : JSON.stringify(detail),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export default function EventsTimeline({ mode, ready }) {
|
|
32
|
+
const [entries, setentries] = useState([]);
|
|
33
|
+
const cursorref = useRef('');
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (mode !== 'api' || !ready) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
let stopped = false;
|
|
39
|
+
const tick = async () => {
|
|
40
|
+
if (document.visibilityState !== 'visible') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const query = cursorref.current === '' ? '' : `?since=${encodeURIComponent(cursorref.current)}`;
|
|
45
|
+
const result = await apiresult(`/api/v1/events${query}`);
|
|
46
|
+
if (stopped || !result.ok) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const items = asarray(result.body, ['events', 'items', 'data', 'list']);
|
|
50
|
+
const lastid = pick(result.body, ['lastid', 'lastId'], null);
|
|
51
|
+
if (lastid !== null && lastid !== undefined) {
|
|
52
|
+
cursorref.current = String(lastid);
|
|
53
|
+
}
|
|
54
|
+
if (items.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const projected = items.map((item, index) => {
|
|
58
|
+
const next = pick(item, ['id', 'seq'], null);
|
|
59
|
+
if (next !== null && next !== undefined) {
|
|
60
|
+
cursorref.current = String(next);
|
|
61
|
+
}
|
|
62
|
+
return toentry(item, index);
|
|
63
|
+
});
|
|
64
|
+
setentries((previous) => [...projected, ...previous].slice(0, 50));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* the next poll retries silently */
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
void tick();
|
|
71
|
+
const timer = setInterval(() => {
|
|
72
|
+
void tick();
|
|
73
|
+
}, 10000);
|
|
74
|
+
return () => {
|
|
75
|
+
stopped = true;
|
|
76
|
+
clearInterval(timer);
|
|
77
|
+
};
|
|
78
|
+
}, [mode, ready]);
|
|
79
|
+
if (mode === 'local') {
|
|
80
|
+
return (_jsx("ol", { className: "dash-timeline", role: "log", "aria-live": "polite", "aria-label": label('dashboard.events.aria'), children: _jsx("li", { className: "dash-empty", children: _jsx("span", { children: label('dashboard.events.localNote') }) }) }));
|
|
81
|
+
}
|
|
82
|
+
return (_jsxs("div", { children: [_jsxs("ol", { className: "dash-timeline", role: "log", "aria-live": "polite", "aria-label": label('dashboard.events.aria'), children: [entries.length === 0 && _jsx("li", { className: "dash-empty", children: label('dashboard.events.empty') }), entries.map((entry) => (_jsxs("li", { className: entry.kind, children: [_jsxs("span", { className: "time", children: [entry.time, " "] }), _jsx("span", { className: "topic", children: entry.topic }), _jsx("span", { className: "detail", children: entry.detail })] }, entry.key)))] }), _jsx("p", { className: "dash-statusnote", children: label('dashboard.events.pollNote') })] }));
|
|
83
|
+
}
|