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.
Files changed (154) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/.github/workflows/control-scan-manifest-check.yml +31 -0
  3. package/AGENTS.md +4 -0
  4. package/README.md +215 -3
  5. package/admin-ui/client.js +81 -51
  6. package/admin-ui/v1/admin_auth_service.js +197 -0
  7. package/admin-ui/v1/admin_user_store.js +71 -0
  8. package/admin-ui/v1/client.js +17 -0
  9. package/admin-ui/v1/controls/admin_shell.js +1399 -0
  10. package/admin-ui/v1/controls/group_box.js +84 -0
  11. package/admin-ui/v1/controls/stat_card.js +125 -0
  12. package/admin-ui/v1/server.js +658 -0
  13. package/admin-ui/v1/utils/formatters.js +68 -0
  14. package/dev-status.svg +139 -0
  15. package/docs/admin-extension-guide.md +345 -0
  16. package/docs/api-reference.md +301 -43
  17. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  18. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  19. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  20. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  21. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  22. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  23. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  24. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  25. package/docs/books/adaptive-control-improvements/README.md +66 -0
  26. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  27. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  28. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  29. package/docs/books/admin-ui-authentication/README.md +25 -0
  30. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  31. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  32. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  33. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  34. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  35. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  36. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  37. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  38. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  39. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  40. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  41. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  42. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  43. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  44. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  45. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  46. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  47. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  48. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  49. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  50. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  51. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  52. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  53. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  54. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  55. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  56. package/docs/books/device-adaptive-composition/README.md +47 -0
  57. package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
  58. package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
  59. package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
  60. package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
  61. package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
  62. package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
  63. package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
  64. package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
  65. package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
  66. package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
  67. package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
  68. package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
  69. package/docs/bundling-system-deep-dive.md +9 -4
  70. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  71. package/docs/comprehensive-documentation.md +49 -18
  72. package/docs/configuration-reference.md +152 -27
  73. package/docs/core/README.md +19 -0
  74. package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
  75. package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
  76. package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
  77. package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
  78. package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
  79. package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
  80. package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
  81. package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
  82. package/docs/designs/server-admin-interface-aero.svg +611 -0
  83. package/docs/publishers-guide.md +59 -4
  84. package/docs/resources-guide.md +184 -35
  85. package/docs/simple-server-api-design.md +72 -17
  86. package/docs/system-architecture.md +18 -14
  87. package/docs/troubleshooting.md +84 -53
  88. package/examples/controls/15) window, observable SSE/server.js +6 -1
  89. package/examples/controls/19) window, auto observable ui/server.js +9 -0
  90. package/examples/controls/20) window, task manager app/README.md +133 -0
  91. package/examples/controls/20) window, task manager app/client.js +797 -0
  92. package/examples/controls/20) window, task manager app/server.js +178 -0
  93. package/examples/controls/6) window, color_palette/client.js +165 -68
  94. package/examples/controls/9) window, date picker/client.js +362 -76
  95. package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
  96. package/examples/jsgui3-html/06) theming/client.js +22 -1
  97. package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
  98. package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
  99. package/lab/experiments/capture-color-controls.js +196 -0
  100. package/lab/results/screenshots/color-controls/full_page.png +0 -0
  101. package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
  102. package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
  103. package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
  104. package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
  105. package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
  106. package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
  107. package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
  108. package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
  109. package/lab/screenshot-utils.js +248 -0
  110. package/module.js +12 -0
  111. package/package.json +12 -2
  112. package/publishers/Publishers.js +4 -3
  113. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
  114. package/publishers/http-sse-publisher.js +341 -0
  115. package/resources/process-resource.js +950 -0
  116. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +129 -33
  117. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
  118. package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
  119. package/resources/remote-process-resource.js +355 -0
  120. package/resources/server-resource-pool.js +354 -41
  121. package/serve-factory.js +442 -259
  122. package/server.js +288 -13
  123. package/tests/README.md +71 -4
  124. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  125. package/tests/admin-ui-render.test.js +24 -0
  126. package/tests/assigners.test.js +56 -40
  127. package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
  128. package/tests/configuration-validation.test.js +21 -18
  129. package/tests/content-analysis.test.js +7 -6
  130. package/tests/control-optimizer-cache-behavior.test.js +52 -0
  131. package/tests/control-scan-manifest-regression.test.js +144 -0
  132. package/tests/end-to-end.test.js +15 -14
  133. package/tests/error-handling.test.js +222 -179
  134. package/tests/fixtures/bundling-default-button-client.js +37 -0
  135. package/tests/fixtures/bundling-default-window-client.js +34 -0
  136. package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
  137. package/tests/fixtures/resource-monitor-client.js +319 -0
  138. package/tests/helpers/puppeteer-e2e-harness.js +317 -0
  139. package/tests/http-sse-publisher.test.js +136 -0
  140. package/tests/performance.test.js +69 -65
  141. package/tests/process-resource.test.js +138 -0
  142. package/tests/publishers.test.js +7 -7
  143. package/tests/remote-process-resource.test.js +160 -0
  144. package/tests/sass-controls.e2e.test.js +7 -1
  145. package/tests/serve-resources.test.js +270 -0
  146. package/tests/serve.test.js +120 -50
  147. package/tests/server-resource-pool.test.js +106 -0
  148. package/tests/small-controls-bundle-size.test.js +252 -0
  149. package/tests/test-runner.js +14 -1
  150. package/tests/window-examples.puppeteer.test.js +204 -1
  151. package/tests/window-resource-integration.puppeteer.test.js +585 -0
  152. package/tests/temp_invalid.js +0 -7
  153. package/tests/temp_invalid_utf8.js +0 -1
  154. 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
+ ```