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,592 @@
|
|
|
1
|
+
# Chapter 4: The Admin Module Adapter Layer
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The Admin Module (`admin-ui/v1/server.js`) is the bridge between raw server internals and the clean JSON API that the admin UI controls consume. It does **not** add new platform features — it reads existing data, formats it, and exposes it through HTTP endpoints and SSE channels.
|
|
6
|
+
|
|
7
|
+
This chapter specifies every adapter function, every API endpoint, and every SSE event the Admin Module must provide.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Design Principles
|
|
12
|
+
|
|
13
|
+
1. **Read-mostly** — The adapter reads server properties; it does not modify control flow.
|
|
14
|
+
2. **Snapshot + Stream** — Static/slow data is served via GET endpoints. Dynamic data is pushed via SSE.
|
|
15
|
+
3. **Defensive** — Every property access is wrapped in try/catch. A missing property never crashes the admin module.
|
|
16
|
+
4. **Self-contained** — The adapter has no external dependencies beyond what jsgui3-server already imports.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Class Structure
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
class Admin_Module_V1 {
|
|
24
|
+
constructor(server) {
|
|
25
|
+
this.server = server;
|
|
26
|
+
this._start_time = Date.now();
|
|
27
|
+
this._request_log = []; // Circular buffer, max 1000
|
|
28
|
+
this._request_count = 0;
|
|
29
|
+
this._bundle_info = null; // Captured from ready event
|
|
30
|
+
this._sse_publisher = null; // For /api/admin/events
|
|
31
|
+
this._route_registry = []; // Tracked route registrations
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
attach_to_router(router) { ... }
|
|
35
|
+
instrument_request_handler() { ... }
|
|
36
|
+
capture_bundle_info() { ... }
|
|
37
|
+
track_route_registration() { ... }
|
|
38
|
+
|
|
39
|
+
// Snapshot builders
|
|
40
|
+
get_snapshot() { ... }
|
|
41
|
+
get_process_info() { ... }
|
|
42
|
+
get_resources_tree() { ... }
|
|
43
|
+
get_routes_list() { ... }
|
|
44
|
+
get_publishers_list() { ... }
|
|
45
|
+
get_build_info() { ... }
|
|
46
|
+
get_config() { ... }
|
|
47
|
+
get_request_stats() { ... }
|
|
48
|
+
|
|
49
|
+
// SSE event broadcasters
|
|
50
|
+
broadcast_request(req_data) { ... }
|
|
51
|
+
broadcast_resource_change(event_data) { ... }
|
|
52
|
+
broadcast_build_complete(build_data) { ... }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## API Endpoints
|
|
59
|
+
|
|
60
|
+
### GET `/api/admin/snapshot`
|
|
61
|
+
|
|
62
|
+
Returns the complete server state in a single response. This is what the admin UI fetches on initial load.
|
|
63
|
+
|
|
64
|
+
**Response shape:**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
{
|
|
68
|
+
"server": {
|
|
69
|
+
"name": "jsgui3 server",
|
|
70
|
+
"pid": 7824,
|
|
71
|
+
"uptime_seconds": 15780,
|
|
72
|
+
"node_version": "v20.11.0",
|
|
73
|
+
"platform": "win32",
|
|
74
|
+
"debug": true,
|
|
75
|
+
"port": 3000,
|
|
76
|
+
"started": true,
|
|
77
|
+
"https": false
|
|
78
|
+
},
|
|
79
|
+
"memory": {
|
|
80
|
+
"rss": 134217728,
|
|
81
|
+
"heap_total": 67108864,
|
|
82
|
+
"heap_used": 45088768,
|
|
83
|
+
"external": 2097152,
|
|
84
|
+
"array_buffers": 1048576
|
|
85
|
+
},
|
|
86
|
+
"resources": {
|
|
87
|
+
"total": 5,
|
|
88
|
+
"running": 5,
|
|
89
|
+
"stopped": 0,
|
|
90
|
+
"crashed": 0,
|
|
91
|
+
"items": [
|
|
92
|
+
{
|
|
93
|
+
"name": "Local Server Info",
|
|
94
|
+
"type": "Local_Server_Info",
|
|
95
|
+
"state": "on"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Server Router",
|
|
99
|
+
"type": "Router",
|
|
100
|
+
"state": "unknown"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"routes": [
|
|
105
|
+
{
|
|
106
|
+
"path": "/",
|
|
107
|
+
"method": "GET",
|
|
108
|
+
"handler_type": "Static_Route_HTTP_Responder",
|
|
109
|
+
"category": "auto"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"path": "/api/validateUser",
|
|
113
|
+
"method": "POST",
|
|
114
|
+
"handler_type": "HTTP_Function_Publisher",
|
|
115
|
+
"category": "api"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"build": {
|
|
119
|
+
"js": {
|
|
120
|
+
"route": "/js/js.js",
|
|
121
|
+
"size_bytes": 250880,
|
|
122
|
+
"size_gzip": 62720,
|
|
123
|
+
"size_brotli": 48128,
|
|
124
|
+
"module_count": null
|
|
125
|
+
},
|
|
126
|
+
"css": {
|
|
127
|
+
"route": "/css/css.css",
|
|
128
|
+
"size_bytes": 12288,
|
|
129
|
+
"size_gzip": 3072,
|
|
130
|
+
"size_brotli": 2560
|
|
131
|
+
},
|
|
132
|
+
"built_at": 1739567000000,
|
|
133
|
+
"errors": 0,
|
|
134
|
+
"warnings": 0
|
|
135
|
+
},
|
|
136
|
+
"requests": {
|
|
137
|
+
"total": 42,
|
|
138
|
+
"per_minute": 12,
|
|
139
|
+
"recent": [
|
|
140
|
+
{
|
|
141
|
+
"timestamp": 1739567000000,
|
|
142
|
+
"method": "GET",
|
|
143
|
+
"url": "/",
|
|
144
|
+
"status": 200,
|
|
145
|
+
"duration_ms": 14
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
"processes": {
|
|
150
|
+
"main": {
|
|
151
|
+
"pid": 7824,
|
|
152
|
+
"state": "running",
|
|
153
|
+
"memory_rss": 134217728,
|
|
154
|
+
"uptime_seconds": 15780
|
|
155
|
+
},
|
|
156
|
+
"children": []
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### GET `/api/admin/resources`
|
|
162
|
+
|
|
163
|
+
Returns just the resource pool data.
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
{
|
|
167
|
+
"total": 5,
|
|
168
|
+
"running": 5,
|
|
169
|
+
"stopped": 0,
|
|
170
|
+
"crashed": 0,
|
|
171
|
+
"unreachable": 0,
|
|
172
|
+
"by_type": {
|
|
173
|
+
"Local_Server_Info": [{ "name": "Local Server Info", "state": "on" }],
|
|
174
|
+
"Router": [{ "name": "Server Router", "state": "unknown" }]
|
|
175
|
+
},
|
|
176
|
+
"items": [
|
|
177
|
+
{
|
|
178
|
+
"name": "Local Server Info",
|
|
179
|
+
"type": "Local_Server_Info",
|
|
180
|
+
"state": "on",
|
|
181
|
+
"has_status": true,
|
|
182
|
+
"status": { ... }
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### GET `/api/admin/routes`
|
|
189
|
+
|
|
190
|
+
Returns the route table.
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
{
|
|
194
|
+
"count": 7,
|
|
195
|
+
"routes": [
|
|
196
|
+
{
|
|
197
|
+
"path": "/",
|
|
198
|
+
"method": "GET",
|
|
199
|
+
"handler_type": "Static_Route_HTTP_Responder",
|
|
200
|
+
"handler_name": null,
|
|
201
|
+
"category": "auto",
|
|
202
|
+
"description": "HTML page"
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### GET `/api/admin/processes`
|
|
209
|
+
|
|
210
|
+
Returns process information.
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
{
|
|
214
|
+
"main": {
|
|
215
|
+
"pid": 7824,
|
|
216
|
+
"state": "running",
|
|
217
|
+
"uptime_seconds": 15780,
|
|
218
|
+
"memory": {
|
|
219
|
+
"rss": 134217728,
|
|
220
|
+
"heap_total": 67108864,
|
|
221
|
+
"heap_used": 45088768
|
|
222
|
+
},
|
|
223
|
+
"cpu": {
|
|
224
|
+
"user": 45000000,
|
|
225
|
+
"system": 12000000
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"children": [
|
|
229
|
+
{
|
|
230
|
+
"name": "Bundle Builder",
|
|
231
|
+
"pid": 7830,
|
|
232
|
+
"state": "running",
|
|
233
|
+
"memory": { "rss_bytes": 90177536 },
|
|
234
|
+
"restart_count": 0,
|
|
235
|
+
"type": "Process_Resource"
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### GET `/api/admin/build`
|
|
242
|
+
|
|
243
|
+
Returns build/bundle information.
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
{
|
|
247
|
+
"bundles": [
|
|
248
|
+
{
|
|
249
|
+
"type": "js",
|
|
250
|
+
"route": "/js/js.js",
|
|
251
|
+
"size_identity": 250880,
|
|
252
|
+
"size_gzip": 62720,
|
|
253
|
+
"size_brotli": 48128
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"type": "css",
|
|
257
|
+
"route": "/css/css.css",
|
|
258
|
+
"size_identity": 12288,
|
|
259
|
+
"size_gzip": 3072,
|
|
260
|
+
"size_brotli": 2560
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
"built_at": 1739567000000,
|
|
264
|
+
"source_maps": true,
|
|
265
|
+
"errors": 0,
|
|
266
|
+
"warnings": 0
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### GET `/api/admin/config`
|
|
271
|
+
|
|
272
|
+
Returns server configuration.
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
{
|
|
276
|
+
"server_name": "jsgui3 server",
|
|
277
|
+
"port": 3000,
|
|
278
|
+
"debug": true,
|
|
279
|
+
"client_js_path": "./examples/client.js",
|
|
280
|
+
"https_enabled": false,
|
|
281
|
+
"allowed_addresses": null,
|
|
282
|
+
"node_version": "v20.11.0",
|
|
283
|
+
"jsgui_version": "0.0.143",
|
|
284
|
+
"platform": "win32",
|
|
285
|
+
"hostname": "DESKTOP-ABC123"
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### GET `/api/admin/events` (SSE)
|
|
290
|
+
|
|
291
|
+
Opens an SSE stream for real-time updates.
|
|
292
|
+
|
|
293
|
+
**SSE events emitted:**
|
|
294
|
+
|
|
295
|
+
| Event Name | Trigger | Payload |
|
|
296
|
+
|-----------|---------|---------|
|
|
297
|
+
| `connected` | Client connects | `{ timestamp, client_id }` |
|
|
298
|
+
| `heartbeat` | Every 15s | `{ timestamp }` |
|
|
299
|
+
| `request` | HTTP request handled | `{ method, url, status, duration_ms, timestamp }` |
|
|
300
|
+
| `resource_state_change` | Pool event | `{ resource_name, from, to, timestamp }` |
|
|
301
|
+
| `resource_crashed` | Pool event | `{ resource_name, code, signal, timestamp }` |
|
|
302
|
+
| `resource_recovered` | Pool event | `{ resource_name, timestamp }` |
|
|
303
|
+
| `build_complete` | Bundle rebuilt | `{ bundles, built_at, errors, warnings }` |
|
|
304
|
+
| `server_stopping` | Server shutting down | `{ timestamp }` |
|
|
305
|
+
|
|
306
|
+
### POST `/api/admin/action`
|
|
307
|
+
|
|
308
|
+
Execute an admin action.
|
|
309
|
+
|
|
310
|
+
**Request body:**
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
{
|
|
314
|
+
"action": "restart_resource",
|
|
315
|
+
"target": "Bundle Builder"
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Supported actions:**
|
|
320
|
+
|
|
321
|
+
| Action | Target | Description |
|
|
322
|
+
|--------|--------|-------------|
|
|
323
|
+
| `restart_resource` | Resource name | Restart a Process_Resource |
|
|
324
|
+
| `stop_resource` | Resource name | Stop a resource |
|
|
325
|
+
| `start_resource` | Resource name | Start a stopped resource |
|
|
326
|
+
| `toggle_debug` | — | Toggle debug mode |
|
|
327
|
+
|
|
328
|
+
**Response:**
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
{
|
|
332
|
+
"ok": true,
|
|
333
|
+
"action": "restart_resource",
|
|
334
|
+
"target": "Bundle Builder",
|
|
335
|
+
"result": "restarting",
|
|
336
|
+
"timestamp": 1739567000000
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Adapter Functions — Implementation Details
|
|
343
|
+
|
|
344
|
+
### `instrument_request_handler()`
|
|
345
|
+
|
|
346
|
+
This is the most important adapter. It wraps the request processing to capture telemetry without modifying the core request flow.
|
|
347
|
+
|
|
348
|
+
**Strategy**: The Admin Module will override or wrap the `process_request` closure inside `server.start()`. Since `process_request` is a local function, we instrument it by wrapping the router's `process` method:
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
instrument_request_handler() {
|
|
352
|
+
const original_process = this.server.server_router.process.bind(this.server.server_router);
|
|
353
|
+
const admin = this;
|
|
354
|
+
|
|
355
|
+
this.server.server_router.process = function(req, res) {
|
|
356
|
+
const start_time = Date.now();
|
|
357
|
+
const req_data = {
|
|
358
|
+
method: req.method,
|
|
359
|
+
url: req.url,
|
|
360
|
+
timestamp: start_time
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Intercept res.end to capture status and timing
|
|
364
|
+
const original_end = res.end.bind(res);
|
|
365
|
+
res.end = function(...args) {
|
|
366
|
+
req_data.status = res.statusCode;
|
|
367
|
+
req_data.duration_ms = Date.now() - start_time;
|
|
368
|
+
admin._log_request(req_data);
|
|
369
|
+
return original_end(...args);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
return original_process(req, res);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Circular buffer for request log:**
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
_log_request(req_data) {
|
|
381
|
+
this._request_log.push(req_data);
|
|
382
|
+
if (this._request_log.length > 1000) {
|
|
383
|
+
this._request_log.shift();
|
|
384
|
+
}
|
|
385
|
+
this._request_count++;
|
|
386
|
+
|
|
387
|
+
// Broadcast via SSE if connected
|
|
388
|
+
if (this._sse_publisher) {
|
|
389
|
+
this._sse_publisher.broadcast('request', req_data);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### `capture_bundle_info()`
|
|
395
|
+
|
|
396
|
+
Captures bundle data from the publisher's `ready` event.
|
|
397
|
+
|
|
398
|
+
```javascript
|
|
399
|
+
capture_bundle_info() {
|
|
400
|
+
// Listen for main webpage publisher ready
|
|
401
|
+
// The publisher is created in server.js constructor
|
|
402
|
+
// We need access to it — either through the resource pool
|
|
403
|
+
// or by having the server pass it to us
|
|
404
|
+
|
|
405
|
+
const pool = this.server.resource_pool;
|
|
406
|
+
const publishers = pool.get_resources_by_type('HTTP_Webpage_Publisher');
|
|
407
|
+
|
|
408
|
+
publishers.forEach(pub => {
|
|
409
|
+
// If already ready, capture from static responders
|
|
410
|
+
// If not, listen for ready event
|
|
411
|
+
pub.on('ready', (bundle) => {
|
|
412
|
+
this._bundle_info = {
|
|
413
|
+
built_at: Date.now(),
|
|
414
|
+
items: []
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
if (bundle && bundle._arr) {
|
|
418
|
+
for (const item of bundle._arr) {
|
|
419
|
+
this._bundle_info.items.push({
|
|
420
|
+
type: item.type || item.extension?.replace('.', ''),
|
|
421
|
+
route: item.route,
|
|
422
|
+
size_identity: item.response_buffers?.identity?.length || 0,
|
|
423
|
+
size_gzip: item.response_buffers?.gzip?.length || null,
|
|
424
|
+
size_brotli: item.response_buffers?.br?.length || null
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this._sse_publisher) {
|
|
430
|
+
this._sse_publisher.broadcast('build_complete', this._bundle_info);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### `track_route_registration()`
|
|
438
|
+
|
|
439
|
+
Instruments `server_router.set_route` to maintain a route registry.
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
track_route_registration() {
|
|
443
|
+
const original_set_route = this.server.server_router.set_route.bind(
|
|
444
|
+
this.server.server_router
|
|
445
|
+
);
|
|
446
|
+
const admin = this;
|
|
447
|
+
|
|
448
|
+
this.server.server_router.set_route = function(path, responder, handler) {
|
|
449
|
+
// Record the route
|
|
450
|
+
const route_entry = {
|
|
451
|
+
path: path,
|
|
452
|
+
handler_type: responder?.constructor?.name || 'function',
|
|
453
|
+
handler_name: responder?.name || null,
|
|
454
|
+
category: admin._categorize_route(path, responder),
|
|
455
|
+
registered_at: Date.now()
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
admin._route_registry.push(route_entry);
|
|
459
|
+
|
|
460
|
+
// Call original
|
|
461
|
+
return original_set_route(path, responder, handler);
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### `_categorize_route(path, responder)`
|
|
467
|
+
|
|
468
|
+
Determines the category of a route for display purposes.
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
_categorize_route(path, responder) {
|
|
472
|
+
const type_name = responder?.constructor?.name || '';
|
|
473
|
+
|
|
474
|
+
if (path.startsWith('/api/admin/')) return 'admin';
|
|
475
|
+
if (path === '/admin') return 'admin';
|
|
476
|
+
if (type_name === 'HTTP_Function_Publisher') return 'api';
|
|
477
|
+
if (type_name === 'Observable_Publisher') return 'observable';
|
|
478
|
+
if (type_name === 'HTTP_SSE_Publisher') return 'sse';
|
|
479
|
+
if (type_name === 'Static_Route_HTTP_Responder') return 'auto';
|
|
480
|
+
if (type_name === 'HTTP_Website_Publisher') return 'website';
|
|
481
|
+
if (path.startsWith('/static')) return 'static';
|
|
482
|
+
return 'other';
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## SSE Channel Setup
|
|
489
|
+
|
|
490
|
+
The Admin Module creates its own `HTTP_SSE_Publisher` for streaming real-time events to the admin UI:
|
|
491
|
+
|
|
492
|
+
```javascript
|
|
493
|
+
_setup_sse() {
|
|
494
|
+
const HTTP_SSE_Publisher = require('../../publishers/http-sse-publisher');
|
|
495
|
+
|
|
496
|
+
this._sse_publisher = new HTTP_SSE_Publisher({
|
|
497
|
+
name: 'admin_events',
|
|
498
|
+
keepaliveIntervalMs: 15000,
|
|
499
|
+
maxClients: 10,
|
|
500
|
+
eventHistorySize: 500
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Wire up resource pool events
|
|
504
|
+
const pool = this.server.resource_pool;
|
|
505
|
+
const event_names = [
|
|
506
|
+
'resource_state_change',
|
|
507
|
+
'crashed',
|
|
508
|
+
'unhealthy',
|
|
509
|
+
'unreachable',
|
|
510
|
+
'recovered',
|
|
511
|
+
'removed'
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
event_names.forEach(event_name => {
|
|
515
|
+
pool.on(event_name, (event_data) => {
|
|
516
|
+
this._sse_publisher.broadcast(event_name, event_data);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Initialization Sequence
|
|
525
|
+
|
|
526
|
+
The Admin Module follows this startup order:
|
|
527
|
+
|
|
528
|
+
1. **Constructor** — Store server reference, initialize buffers
|
|
529
|
+
2. **`track_route_registration()`** — Instrument `set_route` before any routes are registered (must happen first!)
|
|
530
|
+
3. **`_setup_sse()`** — Create SSE publisher
|
|
531
|
+
4. **`attach_to_router(router)`** — Register all `/api/admin/*` endpoints
|
|
532
|
+
5. **`instrument_request_handler()`** — Wrap request processing (after routes are set up, to avoid instrumenting admin route setup itself)
|
|
533
|
+
6. **`capture_bundle_info()`** — Listen for bundle ready events
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
// In server.js constructor:
|
|
537
|
+
const Admin_Module = require('./admin-ui/v1/server');
|
|
538
|
+
this.admin = new Admin_Module(this);
|
|
539
|
+
this.admin.track_route_registration();
|
|
540
|
+
this.admin._setup_sse();
|
|
541
|
+
this.admin.attach_to_router(server_router);
|
|
542
|
+
|
|
543
|
+
// After all user routes are set up:
|
|
544
|
+
this.admin.instrument_request_handler();
|
|
545
|
+
this.admin.capture_bundle_info();
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Error Handling
|
|
551
|
+
|
|
552
|
+
Every adapter function is defensive:
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
get_process_info() {
|
|
556
|
+
try {
|
|
557
|
+
return {
|
|
558
|
+
main: {
|
|
559
|
+
pid: process.pid,
|
|
560
|
+
state: 'running',
|
|
561
|
+
uptime_seconds: Math.floor(process.uptime()),
|
|
562
|
+
memory: process.memoryUsage(),
|
|
563
|
+
cpu: process.cpuUsage()
|
|
564
|
+
},
|
|
565
|
+
children: this._get_child_processes()
|
|
566
|
+
};
|
|
567
|
+
} catch (err) {
|
|
568
|
+
return {
|
|
569
|
+
main: { pid: process.pid, state: 'unknown', error: err.message },
|
|
570
|
+
children: []
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
If a property doesn't exist or throws, the adapter returns a safe default. The admin UI must also handle missing/null values gracefully.
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Data Refresh Strategy
|
|
581
|
+
|
|
582
|
+
| Data Category | Strategy | Interval |
|
|
583
|
+
|--------------|----------|----------|
|
|
584
|
+
| Process memory | Client polls `/api/admin/snapshot` | Every 5s |
|
|
585
|
+
| Resource states | SSE push on state change | Instant |
|
|
586
|
+
| Route table | Client fetches on navigation | On demand |
|
|
587
|
+
| Request log | SSE push per request | Instant |
|
|
588
|
+
| Build info | SSE push on build | On build |
|
|
589
|
+
| Config | Client fetches on navigation | On demand |
|
|
590
|
+
| Uptime | Client-side timer from initial value | Every 1s (client-calculated) |
|
|
591
|
+
|
|
592
|
+
This minimizes server load: only memory/CPU require periodic polling. Everything else is either static or event-driven.
|