jsgui3-server 0.0.148 → 0.0.150
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/.github/agents/Mobile Developer.agent.md +89 -0
- package/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/AGENTS.md +4 -0
- package/README.md +215 -3
- package/admin-ui/client.js +81 -51
- package/admin-ui/v1/admin_auth_service.js +197 -0
- package/admin-ui/v1/admin_user_store.js +71 -0
- package/admin-ui/v1/client.js +17 -0
- package/admin-ui/v1/controls/admin_shell.js +1399 -0
- package/admin-ui/v1/controls/group_box.js +84 -0
- package/admin-ui/v1/controls/stat_card.js +125 -0
- package/admin-ui/v1/server.js +658 -0
- package/admin-ui/v1/utils/formatters.js +68 -0
- package/dev-status.svg +139 -0
- package/docs/admin-extension-guide.md +345 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
- package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
- package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
- package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
- package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
- package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
- package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
- package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
- package/docs/books/adaptive-control-improvements/README.md +66 -0
- package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
- package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
- package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
- package/docs/books/admin-ui-authentication/README.md +25 -0
- package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
- package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
- package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
- package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
- package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
- package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
- package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
- package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
- package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
- package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
- package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
- package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
- package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
- package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
- package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
- package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
- package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
- package/docs/books/creating-a-new-admin-ui/README.md +68 -0
- package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
- package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
- package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
- package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
- package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
- package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
- package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
- package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
- package/docs/books/device-adaptive-composition/README.md +47 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comparison-report-express-plex-cpanel.md +549 -0
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/docs/troubleshooting.md +84 -53
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/server.js +9 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +12 -0
- package/package.json +12 -2
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-sse-publisher.js +341 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +129 -33
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +442 -259
- package/server.js +288 -13
- package/tests/README.md +71 -4
- package/tests/admin-ui-jsgui-controls.test.js +581 -0
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +14 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format a byte count into a human-readable string.
|
|
5
|
+
* @param {number} bytes
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
function format_bytes(bytes) {
|
|
9
|
+
if (bytes === 0 || bytes === null || bytes === undefined) return '0 B';
|
|
10
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
11
|
+
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024));
|
|
12
|
+
const index = Math.min(i, units.length - 1);
|
|
13
|
+
const value = bytes / Math.pow(1024, index);
|
|
14
|
+
return (index > 0 ? value.toFixed(1) : Math.round(value)) + ' ' + units[index];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Format seconds into a human-readable uptime string.
|
|
19
|
+
* @param {number} seconds
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function format_uptime(seconds) {
|
|
23
|
+
if (seconds === null || seconds === undefined || seconds < 0) return '—';
|
|
24
|
+
seconds = Math.floor(seconds);
|
|
25
|
+
if (seconds < 60) return seconds + 's';
|
|
26
|
+
|
|
27
|
+
const days = Math.floor(seconds / 86400);
|
|
28
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
29
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
30
|
+
const secs = seconds % 60;
|
|
31
|
+
|
|
32
|
+
if (days > 0) return days + 'd ' + hours + 'h';
|
|
33
|
+
if (hours > 0) return hours + 'h ' + minutes + 'm';
|
|
34
|
+
return minutes + 'm ' + secs + 's';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format a timestamp into HH:MM:SS.
|
|
39
|
+
* @param {number} timestamp - Unix milliseconds
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function format_time(timestamp) {
|
|
43
|
+
if (!timestamp) return '--:--:--';
|
|
44
|
+
const d = new Date(timestamp);
|
|
45
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
46
|
+
return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format a timestamp into a relative human-readable string.
|
|
51
|
+
* @param {number} timestamp - Unix milliseconds
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function format_relative_time(timestamp) {
|
|
55
|
+
if (!timestamp) return '—';
|
|
56
|
+
const diff = Date.now() - timestamp;
|
|
57
|
+
if (diff < 60000) return 'just now';
|
|
58
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + ' minutes ago';
|
|
59
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + ' hours ago';
|
|
60
|
+
return new Date(timestamp).toLocaleString();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
format_bytes,
|
|
65
|
+
format_uptime,
|
|
66
|
+
format_time,
|
|
67
|
+
format_relative_time
|
|
68
|
+
};
|
package/dev-status.svg
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 850" style="background:#1e1e2e; font-family: 'Segoe UI', sans-serif; color: #cdd6f4;">
|
|
2
|
+
<!-- Definitions for gradients and shadows -->
|
|
3
|
+
<defs>
|
|
4
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
5
|
+
<feDropShadow dx="3" dy="3" stdDeviation="4" flood-color="#000000" flood-opacity="0.5"/>
|
|
6
|
+
</filter>
|
|
7
|
+
<linearGradient id="grad-server" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
8
|
+
<stop offset="0%" style="stop-color:#313244;stop-opacity:1" />
|
|
9
|
+
<stop offset="100%" style="stop-color:#45475a;stop-opacity:1" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="grad-client" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
12
|
+
<stop offset="0%" style="stop-color:#181825;stop-opacity:1" />
|
|
13
|
+
<stop offset="100%" style="stop-color:#1e1e2e;stop-opacity:1" />
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<linearGradient id="grad-success" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
16
|
+
<stop offset="0%" style="stop-color:#a6e3a1;stop-opacity:0.2" />
|
|
17
|
+
<stop offset="100%" style="stop-color:#a6e3a1;stop-opacity:0.1" />
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
|
20
|
+
<path d="M0,0 L0,6 L9,3 z" fill="#89b4fa" />
|
|
21
|
+
</marker>
|
|
22
|
+
</defs>
|
|
23
|
+
|
|
24
|
+
<!-- Title -->
|
|
25
|
+
<text x="50" y="50" fill="#cdd6f4" font-size="32" font-weight="bold">jsgui3-server: Dev Status & Architecture</text>
|
|
26
|
+
<text x="50" y="80" fill="#a6adc8" font-size="16">Admin UI • Auto-Observables • Defensive Programming • Server Lifecycle</text>
|
|
27
|
+
|
|
28
|
+
<!-- Areas -->
|
|
29
|
+
<rect x="50" y="110" width="500" height="700" rx="15" fill="url(#grad-server)" stroke="#585b70" stroke-width="2" />
|
|
30
|
+
<text x="70" y="140" fill="#fab387" font-size="24" font-weight="bold">Server Side (Node.js)</text>
|
|
31
|
+
|
|
32
|
+
<rect x="650" y="110" width="500" height="700" rx="15" fill="url(#grad-client)" stroke="#585b70" stroke-width="2" />
|
|
33
|
+
<text x="670" y="140" fill="#89b4fa" font-size="24" font-weight="bold">Client Side (Browser)</text>
|
|
34
|
+
|
|
35
|
+
<!-- Server Components -->
|
|
36
|
+
|
|
37
|
+
<!-- Server Lifecycle Box -->
|
|
38
|
+
<g transform="translate(80, 180)">
|
|
39
|
+
<rect width="440" height="140" rx="8" fill="#313244" stroke="#f38ba8" stroke-width="2" stroke-dasharray="5,5" filter="url(#shadow)" />
|
|
40
|
+
<text x="15" y="30" fill="#f38ba8" font-size="18" font-weight="bold">Lifecycle Fixes</text>
|
|
41
|
+
|
|
42
|
+
<g transform="translate(20, 50)">
|
|
43
|
+
<circle cx="10" cy="10" r="5" fill="#f38ba8" />
|
|
44
|
+
<text x="25" y="15" fill="#cdd6f4" font-size="14">1. _started Guard (Prevents double start)</text>
|
|
45
|
+
</g>
|
|
46
|
+
<g transform="translate(20, 80)">
|
|
47
|
+
<circle cx="10" cy="10" r="5" fill="#f38ba8" />
|
|
48
|
+
<text x="25" y="15" fill="#cdd6f4" font-size="14">2. Split Events: 'ready' vs 'listening'</text>
|
|
49
|
+
</g>
|
|
50
|
+
<g transform="translate(20, 110)">
|
|
51
|
+
<text x="25" y="15" fill="#a6adc8" font-size="12" font-style="italic">Fixed EADDRINUSE crash root cause</text>
|
|
52
|
+
</g>
|
|
53
|
+
</g>
|
|
54
|
+
|
|
55
|
+
<!-- Admin Module -->
|
|
56
|
+
<g transform="translate(80, 350)">
|
|
57
|
+
<rect width="200" height="120" rx="8" fill="#45475a" stroke="#89b4fa" stroke-width="2" filter="url(#shadow)" />
|
|
58
|
+
<text x="50" y="30" fill="#89b4fa" font-size="18" font-weight="bold">Admin_Module</text>
|
|
59
|
+
<line x1="10" y1="40" x2="190" y2="40" stroke="#585b70" />
|
|
60
|
+
<text x="15" y="65" fill="#cdd6f4" font-size="14">GET /api/admin/resources</text>
|
|
61
|
+
<text x="15" y="90" fill="#cdd6f4" font-size="14">GET /api/admin/observables</text>
|
|
62
|
+
</g>
|
|
63
|
+
|
|
64
|
+
<!-- Publishers -->
|
|
65
|
+
<g transform="translate(320, 350)">
|
|
66
|
+
<rect width="200" height="120" rx="8" fill="#45475a" stroke="#a6e3a1" stroke-width="2" filter="url(#shadow)" />
|
|
67
|
+
<text x="30" y="30" fill="#a6e3a1" font-size="18" font-weight="bold">Publishers</text>
|
|
68
|
+
<line x1="10" y1="40" x2="190" y2="40" stroke="#585b70" />
|
|
69
|
+
<text x="15" y="65" fill="#cdd6f4" font-size="14">HTTP_Webpage_Publisher</text>
|
|
70
|
+
<text x="15" y="90" fill="#cdd6f4" font-size="14">HTTP_Observable_Publisher</text>
|
|
71
|
+
</g>
|
|
72
|
+
|
|
73
|
+
<!-- Defensive Layers -->
|
|
74
|
+
<g transform="translate(80, 500)">
|
|
75
|
+
<rect width="440" height="100" rx="8" fill="url(#grad-success)" stroke="#a6e3a1" stroke-width="1" />
|
|
76
|
+
<text x="15" y="30" fill="#a6e3a1" font-size="18" font-weight="bold">Defensive Coding Layers</text>
|
|
77
|
+
<text x="20" y="60" fill="#cdd6f4" font-size="14">• Resource_Pool.add(undefined) → Warn & Return</text>
|
|
78
|
+
<text x="20" y="80" fill="#cdd6f4" font-size="14">• Bundler Failures → Fallback Empty Bundle</text>
|
|
79
|
+
</g>
|
|
80
|
+
|
|
81
|
+
<!-- Bundler -->
|
|
82
|
+
<g transform="translate(80, 630)">
|
|
83
|
+
<rect width="440" height="60" rx="8" fill="#313244" stroke="#fab387" stroke-width="2" />
|
|
84
|
+
<text x="15" y="35" fill="#fab387" font-size="18" font-weight="bold">Bundler (esbuild)</text>
|
|
85
|
+
<text x="180" y="35" fill="#cdd6f4" font-size="14">Fixed: npm link jsgui3-html</text>
|
|
86
|
+
</g>
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
<!-- Client Components -->
|
|
90
|
+
|
|
91
|
+
<!-- Admin Page -->
|
|
92
|
+
<g transform="translate(680, 250)">
|
|
93
|
+
<rect width="440" height="180" rx="8" fill="#313244" stroke="#89b4fa" stroke-width="2" filter="url(#shadow)" />
|
|
94
|
+
<text x="15" y="30" fill="#89b4fa" font-size="18" font-weight="bold">Admin_Page (Client)</text>
|
|
95
|
+
<line x1="10" y1="40" x2="430" y2="40" stroke="#585b70" />
|
|
96
|
+
|
|
97
|
+
<!-- Child Controls -->
|
|
98
|
+
<g transform="translate(20, 60)">
|
|
99
|
+
<rect width="180" height="40" rx="4" fill="#45475a" stroke="#cba6f7" stroke-width="1" />
|
|
100
|
+
<text x="35" y="25" fill="#cba6f7" font-size="14">Resource_List</text>
|
|
101
|
+
</g>
|
|
102
|
+
|
|
103
|
+
<g transform="translate(220, 60)">
|
|
104
|
+
<rect width="180" height="40" rx="4" fill="#45475a" stroke="#cba6f7" stroke-width="1" />
|
|
105
|
+
<text x="25" y="25" fill="#cba6f7" font-size="14">Observables_List</text>
|
|
106
|
+
</g>
|
|
107
|
+
|
|
108
|
+
<g transform="translate(120, 120)">
|
|
109
|
+
<rect width="200" height="40" rx="4" fill="#45475a" stroke="#f9e2af" stroke-width="1" />
|
|
110
|
+
<text x="30" y="25" fill="#f9e2af" font-size="14">Auto_Observable_UI</text>
|
|
111
|
+
</g>
|
|
112
|
+
</g>
|
|
113
|
+
|
|
114
|
+
<!-- Data Flow Arrows -->
|
|
115
|
+
|
|
116
|
+
<!-- Route Info -->
|
|
117
|
+
<path d="M 280 410 L 680 300" stroke="#89b4fa" stroke-width="2" stroke-dasharray="5,5" fill="none" marker-end="url(#arrow)" />
|
|
118
|
+
<rect x="420" y="330" width="120" height="25" rx="4" fill="#1e1e2e" stroke="#89b4fa" />
|
|
119
|
+
<text x="430" y="347" fill="#89b4fa" font-size="12">JSON Data</text>
|
|
120
|
+
|
|
121
|
+
<!-- Observable Stream -->
|
|
122
|
+
<path d="M 430 470 L 800 390" stroke="#a6e3a1" stroke-width="2" fill="none" marker-end="url(#arrow)" />
|
|
123
|
+
<rect x="560" y="440" width="100" height="25" rx="4" fill="#1e1e2e" stroke="#a6e3a1" />
|
|
124
|
+
<text x="575" y="457" fill="#a6e3a1" font-size="12">SSE Steam</text>
|
|
125
|
+
|
|
126
|
+
<!-- Legend -->
|
|
127
|
+
<g transform="translate(900, 750)">
|
|
128
|
+
<text x="0" y="0" fill="#a6adc8" font-size="14" font-weight="bold">Legend</text>
|
|
129
|
+
<rect x="0" y="10" width="15" height="15" fill="#313244" stroke="#f38ba8" />
|
|
130
|
+
<text x="25" y="23" fill="#cdd6f4" font-size="12">Lifecycle Code</text>
|
|
131
|
+
|
|
132
|
+
<rect x="0" y="35" width="15" height="15" fill="#313244" stroke="#89b4fa" />
|
|
133
|
+
<text x="25" y="48" fill="#cdd6f4" font-size="12">UI / Route Component</text>
|
|
134
|
+
|
|
135
|
+
<rect x="0" y="60" width="15" height="15" fill="#313244" stroke="#a6e3a1" />
|
|
136
|
+
<text x="25" y="73" fill="#cdd6f4" font-size="12">Observable / Defensive</text>
|
|
137
|
+
</g>
|
|
138
|
+
|
|
139
|
+
</svg>
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Admin UI Extension Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The jsgui3-server admin dashboard (`/admin/v1`) is fully extensible. You can:
|
|
6
|
+
|
|
7
|
+
1. **Disable** the admin UI entirely
|
|
8
|
+
2. **Add custom sidebar sections** with automatic data rendering
|
|
9
|
+
3. **Add custom protected API endpoints** with role-based auth
|
|
10
|
+
4. **Use plugins** to bundle related admin extensions
|
|
11
|
+
5. **Access admin internals** (auth service, user store, SSE channel) for advanced use
|
|
12
|
+
|
|
13
|
+
All extension APIs follow snake_case naming and return `this` for chaining.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
const Server = require('jsgui3-server');
|
|
21
|
+
|
|
22
|
+
const server = await Server.serve(MyControl, { port: 8080 });
|
|
23
|
+
|
|
24
|
+
// Add a custom section — shows up in the sidebar
|
|
25
|
+
server.admin_v1.add_section({
|
|
26
|
+
id: 'my_data',
|
|
27
|
+
label: 'My Data',
|
|
28
|
+
api_path: '/api/admin/v1/my-data',
|
|
29
|
+
handler: (req, res) => {
|
|
30
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
31
|
+
res.end(JSON.stringify([
|
|
32
|
+
{ name: 'Item A', value: 42 },
|
|
33
|
+
{ name: 'Item B', value: 99 }
|
|
34
|
+
]));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That's it. The sidebar section appears automatically, and clicking it shows a table.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## API Reference
|
|
44
|
+
|
|
45
|
+
### `admin_v1.add_section(options)`
|
|
46
|
+
|
|
47
|
+
Register a custom sidebar section.
|
|
48
|
+
|
|
49
|
+
| Parameter | Type | Required | Default | Description |
|
|
50
|
+
|----------------|-----------|----------|----------------|-------------|
|
|
51
|
+
| `id` | `string` | Yes | — | Unique section identifier (snake_case) |
|
|
52
|
+
| `label` | `string` | Yes | — | Human-readable sidebar label |
|
|
53
|
+
| `icon` | `string` | No | `null` | Emoji or text icon prefix |
|
|
54
|
+
| `api_path` | `string` | Yes | — | Data endpoint path |
|
|
55
|
+
| `role` | `string` | No | `'admin_read'` | Required role to view |
|
|
56
|
+
| `handler` | `Function`| No | — | `(req, res)` handler for the endpoint |
|
|
57
|
+
|
|
58
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
59
|
+
|
|
60
|
+
If `handler` is provided, it is automatically registered as a protected endpoint at `api_path`. If omitted, you must register the endpoint separately with `add_endpoint()` or via `server.publish()`.
|
|
61
|
+
|
|
62
|
+
#### Auto-Rendering Behaviour
|
|
63
|
+
|
|
64
|
+
The admin shell fetches `api_path` and renders the response based on its shape:
|
|
65
|
+
|
|
66
|
+
| Response Shape | Rendered As |
|
|
67
|
+
|------------------------|-----------------------|
|
|
68
|
+
| Array of objects | Table (columns = keys)|
|
|
69
|
+
| Object | Key-value panel |
|
|
70
|
+
| Scalar (string/number) | Plain text block |
|
|
71
|
+
| Empty array | "No data" message |
|
|
72
|
+
|
|
73
|
+
The v1 shell builds these panels with jsgui controls (table rows, key-value rows, buttons) rather than string-based `innerHTML` rendering.
|
|
74
|
+
|
|
75
|
+
#### Example: Array → Table
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// Handler returns:
|
|
79
|
+
[
|
|
80
|
+
{ name: 'Worker 1', status: 'running', cpu: '12%' },
|
|
81
|
+
{ name: 'Worker 2', status: 'idle', cpu: '0%' }
|
|
82
|
+
]
|
|
83
|
+
// Renders as a 3-column table: Name | Status | CPU
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Example: Object → Key-Value
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Handler returns:
|
|
90
|
+
{ version: '2.1.0', uptime: '4h 12m', mode: 'production' }
|
|
91
|
+
// Renders as Key-Value pairs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `admin_v1.add_endpoint(options)`
|
|
97
|
+
|
|
98
|
+
Register a custom protected API endpoint.
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Required | Default | Description |
|
|
101
|
+
|-----------|-----------|----------|----------------|-------------|
|
|
102
|
+
| `path` | `string` | Yes | — | Route path |
|
|
103
|
+
| `role` | `string` | No | `'admin_read'` | Required role |
|
|
104
|
+
| `handler` | `Function`| Yes | — | `(req, res)` handler |
|
|
105
|
+
|
|
106
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
107
|
+
|
|
108
|
+
The endpoint is automatically wrapped with role-based authentication. Unauthenticated requests get 401, insufficient-role requests get 403.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
server.admin_v1.add_endpoint({
|
|
112
|
+
path: '/api/admin/v1/workers/restart',
|
|
113
|
+
role: 'admin_write',
|
|
114
|
+
handler: (req, res) => {
|
|
115
|
+
// restart logic ...
|
|
116
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
117
|
+
res.end(JSON.stringify({ ok: true }));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `admin_v1.use(plugin_fn)`
|
|
125
|
+
|
|
126
|
+
Plugin-style extension point. The function receives the admin module instance.
|
|
127
|
+
|
|
128
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// my_admin_plugin.js
|
|
132
|
+
function my_admin_plugin(admin) {
|
|
133
|
+
admin.add_section({
|
|
134
|
+
id: 'metrics',
|
|
135
|
+
label: 'Metrics',
|
|
136
|
+
icon: '📊',
|
|
137
|
+
api_path: '/api/admin/v1/metrics',
|
|
138
|
+
handler: (req, res) => {
|
|
139
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
140
|
+
res.end(JSON.stringify({ requests: 12345, errors: 2 }));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
admin.add_endpoint({
|
|
145
|
+
path: '/api/admin/v1/metrics/reset',
|
|
146
|
+
role: 'admin_write',
|
|
147
|
+
handler: (req, res) => {
|
|
148
|
+
// reset metrics...
|
|
149
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
150
|
+
res.end(JSON.stringify({ ok: true }));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = my_admin_plugin;
|
|
156
|
+
|
|
157
|
+
// In your server.js:
|
|
158
|
+
server.admin_v1.use(require('./my_admin_plugin'));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### `admin_v1.get_custom_sections()`
|
|
164
|
+
|
|
165
|
+
Returns the current list of registered custom section metadata.
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
const sections = server.admin_v1.get_custom_sections();
|
|
169
|
+
// [{ id: 'metrics', label: 'Metrics', icon: '📊', api_path: '/api/admin/v1/metrics' }]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Declarative Configuration
|
|
175
|
+
|
|
176
|
+
Custom sections and endpoints can be declared in the `Server.serve()` options:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
Server.serve({
|
|
180
|
+
Ctrl: MyControl,
|
|
181
|
+
port: 8080,
|
|
182
|
+
admin: {
|
|
183
|
+
sections: [
|
|
184
|
+
{
|
|
185
|
+
id: 'jobs',
|
|
186
|
+
label: 'Background Jobs',
|
|
187
|
+
icon: '⚙️',
|
|
188
|
+
api_path: '/api/admin/v1/jobs',
|
|
189
|
+
handler: (req, res) => {
|
|
190
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
191
|
+
res.end(JSON.stringify(get_job_status()));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
endpoints: [
|
|
196
|
+
{
|
|
197
|
+
path: '/api/admin/v1/jobs/trigger',
|
|
198
|
+
role: 'admin_write',
|
|
199
|
+
handler: (req, res) => {
|
|
200
|
+
trigger_job();
|
|
201
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
202
|
+
res.end(JSON.stringify({ ok: true }));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Disabling the Admin UI
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Boolean shorthand
|
|
216
|
+
Server.serve({ Ctrl: MyControl, admin: false });
|
|
217
|
+
|
|
218
|
+
// Object form
|
|
219
|
+
Server.serve({ Ctrl: MyControl, admin: { enabled: false } });
|
|
220
|
+
|
|
221
|
+
// Constructor form
|
|
222
|
+
const server = new Server({ Ctrl: MyControl, admin: false });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
When disabled, no admin routes, SSE channel, or request instrumentation are set up. `server.admin_v1` is `null`.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Exported Classes
|
|
230
|
+
|
|
231
|
+
For advanced use cases (custom auth, subclassing, testing), the admin classes are exported:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const Server = require('jsgui3-server');
|
|
235
|
+
|
|
236
|
+
// On the Server constructor:
|
|
237
|
+
const { Admin_Module_V1, Admin_Auth_Service, Admin_User_Store } = Server;
|
|
238
|
+
|
|
239
|
+
// From the npm module entry:
|
|
240
|
+
const jsgui = require('jsgui3-server');
|
|
241
|
+
const { Admin_Module_V1, Admin_Auth_Service, Admin_User_Store } = jsgui;
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Admin_Module_V1
|
|
245
|
+
|
|
246
|
+
The core admin adapter. Instruments the server, manages telemetry, provides APIs and SSE.
|
|
247
|
+
|
|
248
|
+
**Key properties:**
|
|
249
|
+
- `auth` — `Admin_Auth_Service` instance
|
|
250
|
+
- `user_store` — `Admin_User_Store` instance
|
|
251
|
+
|
|
252
|
+
**Key methods:**
|
|
253
|
+
- `init(server)` — Attach to a server instance
|
|
254
|
+
- `add_section(opts)` — Register custom sidebar section
|
|
255
|
+
- `add_endpoint(opts)` — Register custom API endpoint
|
|
256
|
+
- `use(plugin_fn)` — Apply a plugin function
|
|
257
|
+
- `get_custom_sections()` — Get section metadata array
|
|
258
|
+
- `get_status()` — Get server status snapshot
|
|
259
|
+
- `get_resources_tree()` — Get resource pool tree
|
|
260
|
+
- `get_routes_list()` — Get route registrations
|
|
261
|
+
- `destroy()` — Cleanup heartbeat and SSE
|
|
262
|
+
|
|
263
|
+
### Admin_Auth_Service
|
|
264
|
+
|
|
265
|
+
Session-based authentication service.
|
|
266
|
+
|
|
267
|
+
**Key methods:**
|
|
268
|
+
- `is_authenticated(req)` — Check if request has a valid session
|
|
269
|
+
- `has_role(req, role)` — Check if session has a specific role
|
|
270
|
+
- `has_any_role(req, roles)` — Check if session has any of the given roles
|
|
271
|
+
- `create_session(username, roles)` — Create a new session
|
|
272
|
+
- `destroy_session(session_id)` — Remove a session
|
|
273
|
+
- `handle_login(req, res)` — HTTP login handler
|
|
274
|
+
- `handle_logout(req, res)` — HTTP logout handler
|
|
275
|
+
- `handle_session(req, res)` — HTTP session check handler
|
|
276
|
+
|
|
277
|
+
### Admin_User_Store
|
|
278
|
+
|
|
279
|
+
In-memory user credential store using scrypt hashing.
|
|
280
|
+
|
|
281
|
+
**Key methods:**
|
|
282
|
+
- `add_user({ username, password, roles })` — Add a user (password is hashed)
|
|
283
|
+
- `verify_credentials(username, password)` — Verify credentials (returns `{ valid, user }`)
|
|
284
|
+
- `has_user(username)` — Check if user exists
|
|
285
|
+
- `get_user(username)` — Get user record (without password hash)
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Architecture Notes
|
|
290
|
+
|
|
291
|
+
### How Custom Sections Work
|
|
292
|
+
|
|
293
|
+
1. **Server-side:** `add_section()` stores metadata and optionally registers an API endpoint
|
|
294
|
+
2. **Client-side:** On activation, the admin shell fetches `GET /api/admin/v1/custom-sections`
|
|
295
|
+
3. **Dynamic nav:** Custom sections are added to the sidebar below a separator line
|
|
296
|
+
4. **Data fetch:** When clicked, the shell fetches the section's `api_path`
|
|
297
|
+
5. **Auto-render:** The response is rendered as a table, key-value panel, or text
|
|
298
|
+
|
|
299
|
+
On repeated metadata fetches, previously mounted custom section nav controls are removed before new ones are added. This keeps the sidebar free of duplicate custom entries.
|
|
300
|
+
|
|
301
|
+
### Security Model
|
|
302
|
+
|
|
303
|
+
- All custom endpoints are wrapped with role-based guards automatically
|
|
304
|
+
- Unauthenticated → 401 JSON response
|
|
305
|
+
- Authenticated but missing role → 403 JSON response
|
|
306
|
+
- Session cookies are `httpOnly` with `SameSite=Lax`
|
|
307
|
+
|
|
308
|
+
### Roles
|
|
309
|
+
|
|
310
|
+
| Role | Purpose |
|
|
311
|
+
|---------------|-------------------------------|
|
|
312
|
+
| `admin_read` | View dashboard data and sections |
|
|
313
|
+
| `admin_write` | Mutating operations (start/stop/restart) |
|
|
314
|
+
|
|
315
|
+
Default users get both roles. Custom user creation:
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
server.admin_v1.user_store.add_user({
|
|
319
|
+
username: 'viewer',
|
|
320
|
+
password: 'readonly123',
|
|
321
|
+
roles: ['admin_read']
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Chaining Example
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
server.admin_v1
|
|
331
|
+
.add_section({ id: 'workers', label: 'Workers', api_path: '/api/admin/v1/workers', handler: workers_handler })
|
|
332
|
+
.add_section({ id: 'queues', label: 'Queues', api_path: '/api/admin/v1/queues', handler: queues_handler })
|
|
333
|
+
.add_endpoint({ path: '/api/admin/v1/workers/scale', role: 'admin_write', handler: scale_handler })
|
|
334
|
+
.use(require('./my-monitoring-plugin'));
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Verification
|
|
340
|
+
|
|
341
|
+
Use the Admin UI interaction regression suite after extension changes that affect shell behavior:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
node tests/test-runner.js --test=admin-ui-jsgui-controls.test.js
|
|
345
|
+
```
|