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,556 @@
1
+ # Chapter 13: Telemetry Integration — Instrumenting the Server
2
+
3
+ ## Overview
4
+
5
+ This chapter covers the code changes needed to make the server emit the telemetry data that the Admin UI consumes. Unlike the domain controls (Chapters 5–11), which specify client-side controls, this chapter specifies **server-side modifications** — the adapter functions that intercept, measure, and broadcast server activity.
6
+
7
+ The guiding principle: **instrument, don't restructure**. We wrap existing methods and listen to existing events rather than rewriting core systems.
8
+
9
+ ---
10
+
11
+ ## What Needs Instrumentation
12
+
13
+ From the data availability audit in Chapter 3, the following data sources require adapter-level instrumentation:
14
+
15
+ | Data | Current State | Instrumentation Needed |
16
+ |------|---------------|----------------------|
17
+ | Request count & timing | Not tracked | Wrap `router.process` |
18
+ | Request rate (req/min) | Not tracked | Rolling window counter |
19
+ | Response status codes | Not tracked | Wrap `res.end` |
20
+ | Route list | No listing API | Wrap `set_route` |
21
+ | Build timestamps | Not persisted | Listen to publisher `ready` |
22
+ | Bundle sizes (gzip) | Not computed | Compute on `ready` |
23
+ | SSE connection count | Available per-publisher | Aggregate across publishers |
24
+ | Process resource events | Events exist | Forward to SSE channel |
25
+ | Config snapshot | Scattered properties | Aggregate adapter function |
26
+
27
+ ---
28
+
29
+ ## Request Telemetry
30
+
31
+ ### Interception Point
32
+
33
+ The server's request handling flows through `router.process(req, res)`. We wrap this method to capture timing and status:
34
+
35
+ ```javascript
36
+ // In Admin_Module_V1.init(server)
37
+ instrument_request_handler(server) {
38
+ const router = server.router;
39
+ if (!router || !router.process) return;
40
+
41
+ // Preserve original
42
+ const original_process = router.process.bind(router);
43
+
44
+ // Counters
45
+ this._request_count = 0;
46
+ this._request_window = []; // timestamps for rate calculation
47
+ this._status_counts = {}; // { 200: 142, 404: 3, ... }
48
+
49
+ router.process = (req, res) => {
50
+ const start = Date.now();
51
+ this._request_count++;
52
+
53
+ // Track in rolling window (last 60 seconds)
54
+ const now = Date.now();
55
+ this._request_window.push(now);
56
+ this._trim_request_window(now);
57
+
58
+ // Wrap res.end to capture status and timing
59
+ const original_end = res.end.bind(res);
60
+ res.end = (...args) => {
61
+ const duration_ms = Date.now() - start;
62
+ const status = res.statusCode || 200;
63
+
64
+ // Update status counts
65
+ this._status_counts[status] = (this._status_counts[status] || 0) + 1;
66
+
67
+ // Broadcast to SSE
68
+ this._broadcast_request({
69
+ method: req.method,
70
+ url: req.url,
71
+ status: status,
72
+ duration_ms: duration_ms,
73
+ timestamp: start,
74
+ content_length: res.getHeader('content-length') || null
75
+ });
76
+
77
+ return original_end(...args);
78
+ };
79
+
80
+ return original_process(req, res);
81
+ };
82
+ }
83
+
84
+ _trim_request_window(now) {
85
+ const cutoff = now - 60000; // 60 second window
86
+ while (this._request_window.length > 0 && this._request_window[0] < cutoff) {
87
+ this._request_window.shift();
88
+ }
89
+ }
90
+
91
+ get_requests_per_minute() {
92
+ this._trim_request_window(Date.now());
93
+ return this._request_window.length;
94
+ }
95
+ ```
96
+
97
+ ### Filter: Admin Routes
98
+
99
+ Admin API requests (`/api/admin/*`) should **not** be counted in the request telemetry to avoid feedback loops. The SSE connection itself also generates requests that shouldn't inflate the counter:
100
+
101
+ ```javascript
102
+ // In the wrapped router.process
103
+ router.process = (req, res) => {
104
+ // Skip admin routes from telemetry
105
+ if (req.url && req.url.startsWith('/api/admin/')) {
106
+ return original_process(req, res);
107
+ }
108
+ if (req.url && req.url.startsWith('/admin')) {
109
+ return original_process(req, res);
110
+ }
111
+
112
+ // ... instrumentation continues
113
+ };
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Route Registration Tracking
119
+
120
+ ### Interception Point
121
+
122
+ The router's `set_route` method is wrapped to maintain a list of all registered routes:
123
+
124
+ ```javascript
125
+ track_route_registration(server) {
126
+ const router = server.router;
127
+ if (!router || !router.set_route) return;
128
+
129
+ this._routes = [];
130
+ const original_set_route = router.set_route.bind(router);
131
+
132
+ router.set_route = (path, responder_or_handler, handler) => {
133
+ // Determine the handler type
134
+ const route_info = {
135
+ path: path,
136
+ type: this._categorize_route(path, responder_or_handler),
137
+ handler_name: this._get_handler_name(responder_or_handler, handler),
138
+ registered_at: Date.now()
139
+ };
140
+
141
+ this._routes.push(route_info);
142
+
143
+ return original_set_route(path, responder_or_handler, handler);
144
+ };
145
+ }
146
+
147
+ _categorize_route(path, handler) {
148
+ if (path.startsWith('/api/admin')) return 'admin';
149
+ if (path.startsWith('/api/')) return 'api';
150
+ if (path === '/admin') return 'admin';
151
+
152
+ // Check handler type
153
+ const handler_name = handler?.constructor?.name || '';
154
+ if (handler_name.includes('Webpage')) return 'webpage';
155
+ if (handler_name.includes('Function')) return 'api';
156
+ if (handler_name.includes('Observable')) return 'observable';
157
+ if (handler_name.includes('SSE')) return 'sse';
158
+ if (handler_name.includes('CSS')) return 'static';
159
+ if (handler_name.includes('JS')) return 'static';
160
+
161
+ return 'route';
162
+ }
163
+
164
+ _get_handler_name(responder, handler) {
165
+ if (handler && typeof handler === 'function') {
166
+ return handler.name || 'anonymous';
167
+ }
168
+ if (responder && responder.constructor) {
169
+ return responder.constructor.name;
170
+ }
171
+ return 'unknown';
172
+ }
173
+
174
+ get_routes_list() {
175
+ return this._routes.map(r => ({
176
+ path: r.path,
177
+ type: r.type,
178
+ handler: r.handler_name,
179
+ method: this._infer_method(r.type)
180
+ }));
181
+ }
182
+
183
+ _infer_method(type) {
184
+ switch (type) {
185
+ case 'api': return 'POST';
186
+ case 'observable': return 'GET';
187
+ case 'sse': return 'GET';
188
+ case 'static': return 'GET';
189
+ case 'webpage': return 'GET';
190
+ default: return 'ANY';
191
+ }
192
+ }
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Build & Bundle Telemetry
198
+
199
+ ### Interception Point
200
+
201
+ Build data is captured when `HTTP_Webpage_Publisher` instances emit their `ready` event:
202
+
203
+ ```javascript
204
+ capture_bundle_info(server) {
205
+ this._build_info = null;
206
+ const publishers = server._publishers || [];
207
+
208
+ publishers.forEach(pub => {
209
+ // Check if it's a webpage publisher with bundling
210
+ if (pub.js_output_path !== undefined || pub.css_output_path !== undefined) {
211
+ const capture = () => {
212
+ const info = {
213
+ publisher_name: pub.name || pub.constructor.name || 'default',
214
+ items: [],
215
+ built_at: Date.now(),
216
+ entry_point: pub.src_path_client_js || null,
217
+ build_path: pub.build_path || null,
218
+ source_maps: !!pub.source_maps
219
+ };
220
+
221
+ // JS bundle
222
+ if (pub.js_output_path && fs.existsSync(pub.js_output_path)) {
223
+ const stat = fs.statSync(pub.js_output_path);
224
+ let gzip_size = 0;
225
+ try {
226
+ const buf = fs.readFileSync(pub.js_output_path);
227
+ gzip_size = zlib.gzipSync(buf).length;
228
+ } catch (e) { /* non-critical */ }
229
+
230
+ info.items.push({
231
+ type: 'js',
232
+ path: pub.js_output_path,
233
+ filename: path.basename(pub.js_output_path),
234
+ size_identity: stat.size,
235
+ size_gzip: gzip_size,
236
+ compression_ratio: gzip_size > 0
237
+ ? (gzip_size / stat.size * 100).toFixed(1) : '0'
238
+ });
239
+ }
240
+
241
+ // CSS bundle
242
+ if (pub.css_output_path && fs.existsSync(pub.css_output_path)) {
243
+ const stat = fs.statSync(pub.css_output_path);
244
+ let gzip_size = 0;
245
+ try {
246
+ const buf = fs.readFileSync(pub.css_output_path);
247
+ gzip_size = zlib.gzipSync(buf).length;
248
+ } catch (e) { /* non-critical */ }
249
+
250
+ info.items.push({
251
+ type: 'css',
252
+ path: pub.css_output_path,
253
+ filename: path.basename(pub.css_output_path),
254
+ size_identity: stat.size,
255
+ size_gzip: gzip_size,
256
+ compression_ratio: gzip_size > 0
257
+ ? (gzip_size / stat.size * 100).toFixed(1) : '0'
258
+ });
259
+ }
260
+
261
+ this._build_info = info;
262
+
263
+ // Broadcast build event
264
+ this._broadcast('build_complete', info);
265
+ };
266
+
267
+ // Listen for ready
268
+ if (typeof pub.on === 'function') {
269
+ pub.on('ready', capture);
270
+ }
271
+
272
+ // If already ready, capture now
273
+ if (pub._ready) {
274
+ capture();
275
+ }
276
+ }
277
+ });
278
+ }
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Resource Pool Event Forwarding
284
+
285
+ The `Server_Resource_Pool` already emits events (`resource_state_change`, `crashed`, `unhealthy`, `unreachable`, `recovered`). The adapter subscribes and forwards to the SSE channel:
286
+
287
+ ```javascript
288
+ subscribe_resource_events(server) {
289
+ const pool = server.resource_pool;
290
+ if (!pool) return;
291
+
292
+ const events_to_forward = [
293
+ 'resource_state_change',
294
+ 'crashed',
295
+ 'unhealthy',
296
+ 'unreachable',
297
+ 'recovered',
298
+ 'removed'
299
+ ];
300
+
301
+ events_to_forward.forEach(event_name => {
302
+ pool.on(event_name, (data) => {
303
+ this._broadcast(event_name, {
304
+ event: event_name,
305
+ resourceName: data.name || data.resourceName || 'unknown',
306
+ from: data.from || null,
307
+ to: data.to || data.state || null,
308
+ timestamp: Date.now(),
309
+ details: data
310
+ });
311
+ });
312
+ });
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Process Resource Event Forwarding
319
+
320
+ Individual `Process_Resource` instances emit stdout, stderr, and lifecycle events. These are forwarded for the Process Panel and Log Viewer:
321
+
322
+ ```javascript
323
+ subscribe_process_events(server) {
324
+ const pool = server.resource_pool;
325
+ if (!pool || !pool.resources) return;
326
+
327
+ pool.resources.forEach((resource, name) => {
328
+ if (resource.type === 'process' || resource.constructor.name === 'Process_Resource') {
329
+ // State changes
330
+ resource.on('state_change', (data) => {
331
+ this._broadcast('process_state_change', {
332
+ name: name,
333
+ pid: resource.pid,
334
+ from: data.from,
335
+ to: data.to,
336
+ timestamp: Date.now()
337
+ });
338
+ });
339
+
340
+ // Health checks
341
+ resource.on('health_check', (data) => {
342
+ this._broadcast('process_health', {
343
+ name: name,
344
+ pid: resource.pid,
345
+ healthy: data.healthy,
346
+ memory_usage: data.memory_usage || null,
347
+ timestamp: Date.now()
348
+ });
349
+ });
350
+ }
351
+ });
352
+
353
+ // Also watch for newly added resources
354
+ pool.on('resource_added', (data) => {
355
+ // Re-subscribe to new process resources
356
+ const resource = pool.resources.get(data.name);
357
+ if (resource && (resource.type === 'process' || resource.constructor.name === 'Process_Resource')) {
358
+ resource.on('state_change', (state_data) => {
359
+ this._broadcast('process_state_change', {
360
+ name: data.name,
361
+ pid: resource.pid,
362
+ from: state_data.from,
363
+ to: state_data.to,
364
+ timestamp: Date.now()
365
+ });
366
+ });
367
+ }
368
+ });
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Heartbeat Emission
375
+
376
+ A periodic heartbeat event provides status bar data without requiring the client to poll:
377
+
378
+ ```javascript
379
+ start_heartbeat(server) {
380
+ this._heartbeat_interval = setInterval(() => {
381
+ const pool = server.resource_pool;
382
+ const pool_summary = pool ? pool.summary : { total: 0, running: 0 };
383
+
384
+ this._broadcast('heartbeat', {
385
+ uptime: Math.floor(process.uptime()),
386
+ pid: process.pid,
387
+ memory: process.memoryUsage(),
388
+ request_count: this._request_count || 0,
389
+ requests_per_minute: this.get_requests_per_minute(),
390
+ pool_summary: pool_summary,
391
+ route_count: this._routes ? this._routes.length : 0,
392
+ timestamp: Date.now()
393
+ });
394
+ }, 5000); // Every 5 seconds
395
+ }
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Aggregate Status Endpoint
401
+
402
+ `GET /api/admin/v1/status` returns a comprehensive snapshot that the dashboard uses for initial population:
403
+
404
+ ```javascript
405
+ get_status(server) {
406
+ const mem = process.memoryUsage();
407
+ const pool = server.resource_pool;
408
+
409
+ return {
410
+ process: {
411
+ pid: process.pid,
412
+ title: process.title,
413
+ uptime: Math.floor(process.uptime()),
414
+ memory: {
415
+ rss: mem.rss,
416
+ heap_used: mem.heapUsed,
417
+ heap_total: mem.heapTotal,
418
+ external: mem.external
419
+ },
420
+ node_version: process.version,
421
+ platform: process.platform,
422
+ arch: process.arch,
423
+ cwd: process.cwd()
424
+ },
425
+ server: {
426
+ port: server.port || null,
427
+ host: server.host || '0.0.0.0'
428
+ },
429
+ telemetry: {
430
+ request_count: this._request_count || 0,
431
+ requests_per_minute: this.get_requests_per_minute(),
432
+ status_counts: this._status_counts || {}
433
+ },
434
+ pool: pool ? pool.summary : { total: 0, running: 0, stopped: 0 },
435
+ routes: {
436
+ total: this._routes ? this._routes.length : 0
437
+ },
438
+ build: this._build_info || null
439
+ };
440
+ }
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Initialization Sequence
446
+
447
+ The instrumentation must be applied in the correct order, after the server's core systems are initialized but before it starts accepting requests:
448
+
449
+ ```javascript
450
+ class Admin_Module_V1 {
451
+ init(server) {
452
+ // 1. Track route registration FIRST (before other modules register routes)
453
+ this.track_route_registration(server);
454
+
455
+ // 2. Register admin API endpoints (these are tracked by step 1)
456
+ this._register_endpoints(server);
457
+
458
+ // 3. Instrument request handler (after routes exist)
459
+ this.instrument_request_handler(server);
460
+
461
+ // 4. Subscribe to resource pool events
462
+ this.subscribe_resource_events(server);
463
+
464
+ // 5. Subscribe to process resource events
465
+ this.subscribe_process_events(server);
466
+
467
+ // 6. Capture build info (listens for publisher ready)
468
+ this.capture_bundle_info(server);
469
+
470
+ // 7. Start SSE channel
471
+ this._init_sse_channel(server);
472
+
473
+ // 8. Start heartbeat
474
+ this.start_heartbeat(server);
475
+ }
476
+
477
+ _register_endpoints(server) {
478
+ const fn_publisher = server.fn_publisher || new HTTP_Function_Publisher({ context: server.context });
479
+
480
+ // GET endpoints
481
+ fn_publisher.publish('/api/admin/v1/status', () => this.get_status(server));
482
+ fn_publisher.publish('/api/admin/v1/resources', () => this.get_resources_tree(server));
483
+ fn_publisher.publish('/api/admin/v1/routes', () => this.get_routes_list());
484
+ fn_publisher.publish('/api/admin/v1/build', () => this._build_info || {});
485
+ fn_publisher.publish('/api/admin/v1/config', () => this.get_config(server));
486
+
487
+ // POST endpoint for config changes
488
+ fn_publisher.publish('/api/admin/v1/config/set', (body) => this.set_config(body));
489
+ }
490
+
491
+ _init_sse_channel(server) {
492
+ this._sse_publisher = new HTTP_SSE_Publisher({
493
+ context: server.context,
494
+ history_size: 100
495
+ });
496
+
497
+ server.router.set_route('/api/admin/v1/events', this._sse_publisher);
498
+ }
499
+
500
+ _broadcast(event_name, data) {
501
+ if (this._sse_publisher) {
502
+ this._sse_publisher.broadcast(event_name, data);
503
+ }
504
+ }
505
+
506
+ _broadcast_request(data) {
507
+ this._broadcast('request', data);
508
+ }
509
+
510
+ destroy() {
511
+ if (this._heartbeat_interval) {
512
+ clearInterval(this._heartbeat_interval);
513
+ }
514
+ if (this._sse_publisher) {
515
+ // Close all SSE connections
516
+ this._sse_publisher.close_all && this._sse_publisher.close_all();
517
+ }
518
+ }
519
+ }
520
+ ```
521
+
522
+ ---
523
+
524
+ ## Impact Assessment
525
+
526
+ ### Performance Impact
527
+
528
+ | Instrumentation | Overhead per Request | Notes |
529
+ |-----------------|---------------------|-------|
530
+ | Request wrapping | ~0.1ms | One Date.now() call + array push |
531
+ | Route tracking | None at runtime | Only runs during registration |
532
+ | Build capture | None at runtime | Only runs on publisher ready |
533
+ | Resource events | None per request | Event-driven, no polling |
534
+ | Heartbeat | ~1ms every 5s | process.memoryUsage() is the main cost |
535
+ | SSE broadcast | ~0.5ms per event | JSON serialization + write to clients |
536
+
537
+ Total overhead: **< 1ms per request** for the common case.
538
+
539
+ ### Memory Impact
540
+
541
+ | Data Structure | Max Size | Notes |
542
+ |---------------|----------|-------|
543
+ | Request window | ~60K entries max | 1-minute window, trimmed continuously |
544
+ | Route list | ~100 entries | Static after initialization |
545
+ | Build info | 1 object | Overwritten on each build |
546
+ | SSE history | 100 events | Configured via `history_size` |
547
+ | Status counts | ~10 entries | One per status code |
548
+
549
+ Total: **< 1 MB** additional memory for typical workloads.
550
+
551
+ ### Safety
552
+
553
+ - **No core modifications**: All instrumentation wraps existing methods; originals are preserved
554
+ - **Admin routes excluded**: The admin panel's own requests don't inflate telemetry
555
+ - **Graceful degradation**: If any instrumentation fails to attach (missing router, no pool), the admin panel shows "–" instead of crashing
556
+ - **Cleanup**: `destroy()` clears intervals and closes SSE connections