jsgui3-server 0.0.149 → 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 (98) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/AGENTS.md +4 -0
  3. package/README.md +130 -0
  4. package/admin-ui/client.js +73 -43
  5. package/admin-ui/v1/admin_auth_service.js +197 -0
  6. package/admin-ui/v1/admin_user_store.js +71 -0
  7. package/admin-ui/v1/client.js +17 -0
  8. package/admin-ui/v1/controls/admin_shell.js +1399 -0
  9. package/admin-ui/v1/controls/group_box.js +84 -0
  10. package/admin-ui/v1/controls/stat_card.js +125 -0
  11. package/admin-ui/v1/server.js +658 -0
  12. package/admin-ui/v1/utils/formatters.js +68 -0
  13. package/docs/admin-extension-guide.md +345 -0
  14. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  15. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  16. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  17. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  18. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  19. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  20. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  21. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  22. package/docs/books/adaptive-control-improvements/README.md +66 -0
  23. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  24. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  25. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  26. package/docs/books/admin-ui-authentication/README.md +25 -0
  27. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  28. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  29. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  30. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  31. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  32. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  33. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  34. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  35. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  36. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  37. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  38. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  39. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  40. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  41. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  42. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  43. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  44. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  45. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  46. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  47. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  48. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  49. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  50. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  51. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  52. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  53. package/docs/books/device-adaptive-composition/README.md +47 -0
  54. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  55. package/docs/designs/server-admin-interface-aero.svg +611 -0
  56. package/docs/troubleshooting.md +84 -53
  57. package/module.js +16 -11
  58. package/package.json +1 -1
  59. package/serve-factory.js +1 -0
  60. package/server.js +199 -0
  61. package/tests/README.md +5 -0
  62. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  63. package/tests/test-runner.js +1 -0
  64. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
  65. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
  66. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
  67. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
  68. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
  69. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
  70. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
  71. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
  72. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
  73. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
  74. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
  75. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
  76. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
  77. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
  78. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
  79. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
  80. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
  81. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
  82. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
  83. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
  84. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
  85. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
  86. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
  87. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
  88. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
  89. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
  90. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
  91. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
  92. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
  93. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
  94. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
  95. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
  96. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
  97. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
  98. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +0 -39
@@ -0,0 +1,808 @@
1
+ # Chapter 11: Domain Controls — Configuration Panel
2
+
3
+ ## Overview
4
+
5
+ The Configuration Panel provides a read-only view of the server's runtime configuration with selective edit capability for safe, hot-reloadable settings. It reflects the "Server Configuration" section in the design reference, showing port, bundle paths, debug mode, source map settings, and other operational parameters.
6
+
7
+ Key principle: **read-only by default**. Only settings that can be safely changed at runtime (debug mode, log level) are editable. Structural settings (port, entry point) are displayed but locked.
8
+
9
+ ---
10
+
11
+ ## Available Configuration Data
12
+
13
+ From Chapter 3 (Server Introspection), the following configuration data is available:
14
+
15
+ | Setting | Source | Editable | Notes |
16
+ |---------|--------|:--------:|-------|
17
+ | Port | `server.port` | ❌ | Requires restart; display only |
18
+ | Host | `server.host` or implicit | ❌ | Requires restart |
19
+ | Entry point JS | `server.src_path_client_js` | ❌ | Requires restart |
20
+ | Build output path | Publisher build path | ❌ | Set at initialization |
21
+ | Source maps | `server.source_maps` or publisher option | ⚠️ | Could toggle for next build |
22
+ | Debug mode | `process.env.JSGUI_DEBUG` | ✅ | Hot-reloadable |
23
+ | Log level | Adapter-managed | ✅ | Hot-reloadable |
24
+ | Minification | Publisher option | ⚠️ | Affects next build only |
25
+ | Compression | `server.compression` | ❌ | Requires middleware change |
26
+ | Process title | `process.title` | ✅ | Can be set at runtime |
27
+ | Node version | `process.version` | ❌ | System info |
28
+ | Platform | `process.platform` | ❌ | System info |
29
+
30
+ ---
31
+
32
+ ## Adapter Enhancement
33
+
34
+ ### API Endpoint
35
+
36
+ `GET /api/admin/v1/config`
37
+
38
+ ```json
39
+ {
40
+ "server": {
41
+ "port": 8080,
42
+ "host": "0.0.0.0",
43
+ "entry_point": "client.js",
44
+ "build_path": "/tmp/jsgui-build",
45
+ "source_maps": true,
46
+ "minification": false,
47
+ "compression": false
48
+ },
49
+ "runtime": {
50
+ "debug_mode": false,
51
+ "log_level": "info",
52
+ "process_title": "jsgui3-server",
53
+ "node_version": "v20.11.0",
54
+ "platform": "linux",
55
+ "arch": "x64",
56
+ "pid": 12345,
57
+ "uptime_seconds": 3600
58
+ },
59
+ "environment": {
60
+ "NODE_ENV": "development",
61
+ "JSGUI_DEBUG": "0"
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### Mutable Settings Endpoint
67
+
68
+ `POST /api/admin/v1/config`
69
+
70
+ Request body (only mutable fields accepted):
71
+ ```json
72
+ {
73
+ "debug_mode": true,
74
+ "log_level": "debug",
75
+ "process_title": "my-app-server"
76
+ }
77
+ ```
78
+
79
+ Response:
80
+ ```json
81
+ {
82
+ "status": "ok",
83
+ "applied": {
84
+ "debug_mode": true,
85
+ "log_level": "debug",
86
+ "process_title": "my-app-server"
87
+ },
88
+ "rejected": {}
89
+ }
90
+ ```
91
+
92
+ ### Adapter Implementation
93
+
94
+ ```javascript
95
+ // In Admin_Module_V1
96
+ get_config(server) {
97
+ return {
98
+ server: {
99
+ port: server.port || null,
100
+ host: server.host || '0.0.0.0',
101
+ entry_point: this._get_entry_point(server),
102
+ build_path: this._get_build_path(server),
103
+ source_maps: this._get_source_maps(server),
104
+ minification: this._get_minification(server),
105
+ compression: !!server.compression
106
+ },
107
+ runtime: {
108
+ debug_mode: !!process.env.JSGUI_DEBUG,
109
+ log_level: this._log_level || 'info',
110
+ process_title: process.title,
111
+ node_version: process.version,
112
+ platform: process.platform,
113
+ arch: process.arch,
114
+ pid: process.pid,
115
+ uptime_seconds: Math.floor(process.uptime())
116
+ },
117
+ environment: {
118
+ NODE_ENV: process.env.NODE_ENV || 'development',
119
+ JSGUI_DEBUG: process.env.JSGUI_DEBUG || '0'
120
+ }
121
+ };
122
+ }
123
+
124
+ set_config(changes) {
125
+ const applied = {};
126
+ const rejected = {};
127
+
128
+ const mutable_fields = {
129
+ debug_mode: (val) => {
130
+ process.env.JSGUI_DEBUG = val ? '1' : '0';
131
+ applied.debug_mode = val;
132
+ },
133
+ log_level: (val) => {
134
+ const valid_levels = ['error', 'warn', 'info', 'debug'];
135
+ if (valid_levels.includes(val)) {
136
+ this._log_level = val;
137
+ applied.log_level = val;
138
+ } else {
139
+ rejected.log_level = `Invalid level: ${val}`;
140
+ }
141
+ },
142
+ process_title: (val) => {
143
+ if (typeof val === 'string' && val.length > 0 && val.length < 256) {
144
+ process.title = val;
145
+ applied.process_title = val;
146
+ } else {
147
+ rejected.process_title = 'Invalid title';
148
+ }
149
+ }
150
+ };
151
+
152
+ for (const [key, value] of Object.entries(changes)) {
153
+ if (mutable_fields[key]) {
154
+ mutable_fields[key](value);
155
+ } else {
156
+ rejected[key] = 'Field is not mutable at runtime';
157
+ }
158
+ }
159
+
160
+ return { status: 'ok', applied, rejected };
161
+ }
162
+
163
+ _get_entry_point(server) {
164
+ if (server.src_path_client_js) {
165
+ return server.src_path_client_js.split(/[/\\]/).pop();
166
+ }
167
+ return null;
168
+ }
169
+
170
+ _get_build_path(server) {
171
+ // Check publishers for build path
172
+ if (server.publishers) {
173
+ for (const pub of server.publishers) {
174
+ if (pub.build_path) return pub.build_path;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+
180
+ _get_source_maps(server) {
181
+ if (server.source_maps !== undefined) return server.source_maps;
182
+ // Check publisher options
183
+ if (server.publishers) {
184
+ for (const pub of server.publishers) {
185
+ if (pub.source_maps !== undefined) return pub.source_maps;
186
+ }
187
+ }
188
+ return false;
189
+ }
190
+
191
+ _get_minification(server) {
192
+ if (server.publishers) {
193
+ for (const pub of server.publishers) {
194
+ if (pub.minify !== undefined) return pub.minify;
195
+ }
196
+ }
197
+ return false;
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Config_Panel Control
204
+
205
+ ### Spec
206
+
207
+ ```javascript
208
+ {
209
+ __type_name: 'config_panel',
210
+ title: 'Server Configuration'
211
+ }
212
+ ```
213
+
214
+ ### Visual Anatomy
215
+
216
+ ```
217
+ ┌─ Server Configuration ────────────────────────────────┐
218
+ │ │
219
+ │ SERVER │
220
+ │ ────────────────────────────────────────────── │
221
+ │ Port 8080 🔒 │
222
+ │ Host 0.0.0.0 🔒 │
223
+ │ Entry Point client.js 🔒 │
224
+ │ Build Path /tmp/jsgui-build 🔒 │
225
+ │ Source Maps ✓ Enabled 🔒 │
226
+ │ Minification ✗ Disabled 🔒 │
227
+ │ │
228
+ │ RUNTIME │
229
+ │ ────────────────────────────────────────────── │
230
+ │ Debug Mode [ OFF | ON ] ✏️ │
231
+ │ Log Level [ info ▾ ] ✏️ │
232
+ │ Process Title [ jsgui3-server ] ✏️ │
233
+ │ │
234
+ │ ENVIRONMENT │
235
+ │ ────────────────────────────────────────────── │
236
+ │ Node.js v20.11.0 │
237
+ │ Platform linux x64 │
238
+ │ PID 12345 │
239
+ │ Uptime 1h 23m │
240
+ │ │
241
+ │ [ Apply Changes ] │
242
+ │ │
243
+ └────────────────────────────────────────────────────────┘
244
+ ```
245
+
246
+ ### Constructor
247
+
248
+ ```javascript
249
+ class Config_Panel extends jsgui.Control {
250
+ constructor(spec = {}) {
251
+ spec.__type_name = spec.__type_name || 'config_panel';
252
+ super(spec);
253
+ const { context } = this;
254
+
255
+ this._pending_changes = {};
256
+
257
+ const compose = () => {
258
+ const group = new Group_Box({ context, title: spec.title || 'Server Configuration' });
259
+ this.add(group);
260
+ this._group = group;
261
+
262
+ // Server section
263
+ const server_section = this._make_section('Server');
264
+ group.add(server_section);
265
+ this._server_section = server_section;
266
+
267
+ this._port_row = this._make_readonly_row('Port', '—');
268
+ server_section.add(this._port_row);
269
+
270
+ this._host_row = this._make_readonly_row('Host', '—');
271
+ server_section.add(this._host_row);
272
+
273
+ this._entry_row = this._make_readonly_row('Entry Point', '—');
274
+ server_section.add(this._entry_row);
275
+
276
+ this._build_path_row = this._make_readonly_row('Build Path', '—');
277
+ server_section.add(this._build_path_row);
278
+
279
+ this._source_maps_row = this._make_readonly_row('Source Maps', '—');
280
+ server_section.add(this._source_maps_row);
281
+
282
+ this._minify_row = this._make_readonly_row('Minification', '—');
283
+ server_section.add(this._minify_row);
284
+
285
+ // Runtime section
286
+ const runtime_section = this._make_section('Runtime');
287
+ group.add(runtime_section);
288
+ this._runtime_section = runtime_section;
289
+
290
+ this._debug_row = this._make_toggle_row('Debug Mode', false);
291
+ runtime_section.add(this._debug_row);
292
+
293
+ this._log_level_row = this._make_select_row('Log Level', 'info', ['error', 'warn', 'info', 'debug']);
294
+ runtime_section.add(this._log_level_row);
295
+
296
+ this._title_row = this._make_text_row('Process Title', '');
297
+ runtime_section.add(this._title_row);
298
+
299
+ // Environment section
300
+ const env_section = this._make_section('Environment');
301
+ group.add(env_section);
302
+ this._env_section = env_section;
303
+
304
+ this._node_row = this._make_readonly_row('Node.js', '—');
305
+ env_section.add(this._node_row);
306
+
307
+ this._platform_row = this._make_readonly_row('Platform', '—');
308
+ env_section.add(this._platform_row);
309
+
310
+ this._pid_row = this._make_readonly_row('PID', '—');
311
+ env_section.add(this._pid_row);
312
+
313
+ this._uptime_row = this._make_readonly_row('Uptime', '—');
314
+ env_section.add(this._uptime_row);
315
+
316
+ // Apply button
317
+ const button_row = new controls.div({ context, class: 'config-button-row' });
318
+ group.add(button_row);
319
+
320
+ const apply_btn = new controls.Button({ context, text: 'Apply Changes' });
321
+ apply_btn.dom.attributes.class = 'config-apply-btn';
322
+ button_row.add(apply_btn);
323
+ this._apply_btn = apply_btn;
324
+ };
325
+
326
+ if (!spec.el) { compose(); }
327
+ }
328
+
329
+ _make_section(title) {
330
+ const { context } = this;
331
+ const section = new controls.div({ context, class: 'config-section' });
332
+ const header = new controls.div({ context, class: 'config-section-header' });
333
+ header.add(title);
334
+ section.add(header);
335
+ return section;
336
+ }
337
+
338
+ _make_readonly_row(label, value) {
339
+ const { context } = this;
340
+ const row = new controls.div({ context, class: 'config-row config-row-readonly' });
341
+
342
+ const lbl = new controls.span({ context, class: 'config-label' });
343
+ lbl.add(label);
344
+ row.add(lbl);
345
+
346
+ const val = new controls.span({ context, class: 'config-value' });
347
+ val.add(value);
348
+ row.add(val);
349
+
350
+ const lock = new controls.span({ context, class: 'config-lock' });
351
+ lock.add('🔒');
352
+ row.add(lock);
353
+
354
+ row._value_el = val;
355
+ return row;
356
+ }
357
+
358
+ _make_toggle_row(label, initial_value) {
359
+ const { context } = this;
360
+ const row = new controls.div({ context, class: 'config-row config-row-editable' });
361
+
362
+ const lbl = new controls.span({ context, class: 'config-label' });
363
+ lbl.add(label);
364
+ row.add(lbl);
365
+
366
+ const toggle_wrap = new controls.div({ context, class: 'config-toggle' });
367
+
368
+ const off_btn = new controls.span({ context, class: `toggle-option ${!initial_value ? 'toggle-active' : ''}` });
369
+ off_btn.dom.attributes['data-value'] = 'false';
370
+ off_btn.add('OFF');
371
+ toggle_wrap.add(off_btn);
372
+
373
+ const on_btn = new controls.span({ context, class: `toggle-option ${initial_value ? 'toggle-active' : ''}` });
374
+ on_btn.dom.attributes['data-value'] = 'true';
375
+ on_btn.add('ON');
376
+ toggle_wrap.add(on_btn);
377
+
378
+ row.add(toggle_wrap);
379
+ row._toggle = toggle_wrap;
380
+ row._field = label.toLowerCase().replace(/\s/g, '_');
381
+ return row;
382
+ }
383
+
384
+ _make_select_row(label, initial_value, options) {
385
+ const { context } = this;
386
+ const row = new controls.div({ context, class: 'config-row config-row-editable' });
387
+
388
+ const lbl = new controls.span({ context, class: 'config-label' });
389
+ lbl.add(label);
390
+ row.add(lbl);
391
+
392
+ // Rendered as a simple dropdown on client
393
+ const select_wrap = new controls.div({ context, class: 'config-select-wrap' });
394
+ const display = new controls.span({ context, class: 'config-select-display' });
395
+ display.add(initial_value);
396
+ select_wrap.add(display);
397
+
398
+ const arrow = new controls.span({ context, class: 'config-select-arrow' });
399
+ arrow.add('▾');
400
+ select_wrap.add(arrow);
401
+
402
+ row.add(select_wrap);
403
+ row._display = display;
404
+ row._options = options;
405
+ row._field = label.toLowerCase().replace(/\s/g, '_');
406
+ return row;
407
+ }
408
+
409
+ _make_text_row(label, initial_value) {
410
+ const { context } = this;
411
+ const row = new controls.div({ context, class: 'config-row config-row-editable' });
412
+
413
+ const lbl = new controls.span({ context, class: 'config-label' });
414
+ lbl.add(label);
415
+ row.add(lbl);
416
+
417
+ const input_wrap = new controls.div({ context, class: 'config-text-wrap' });
418
+ const display = new controls.span({ context, class: 'config-text-display' });
419
+ display.add(initial_value || '—');
420
+ input_wrap.add(display);
421
+
422
+ row.add(input_wrap);
423
+ row._display = display;
424
+ row._field = label.toLowerCase().replace(/\s/g, '_');
425
+ return row;
426
+ }
427
+
428
+ update(config_data) {
429
+ if (!config_data) return;
430
+
431
+ // Server section (read-only)
432
+ const s = config_data.server || {};
433
+ this._set_readonly(this._port_row, s.port);
434
+ this._set_readonly(this._host_row, s.host);
435
+ this._set_readonly(this._entry_row, s.entry_point);
436
+ this._set_readonly(this._build_path_row, s.build_path);
437
+ this._set_readonly(this._source_maps_row, s.source_maps ? '✓ Enabled' : '✗ Disabled');
438
+ this._set_readonly(this._minify_row, s.minification ? '✓ Enabled' : '✗ Disabled');
439
+
440
+ // Runtime section
441
+ const r = config_data.runtime || {};
442
+ this._set_toggle(this._debug_row, r.debug_mode);
443
+ this._set_select(this._log_level_row, r.log_level);
444
+ this._set_text(this._title_row, r.process_title);
445
+
446
+ // Environment section
447
+ this._set_readonly(this._node_row, r.node_version);
448
+ this._set_readonly(this._platform_row, `${r.platform || '?'} ${r.arch || ''}`);
449
+ this._set_readonly(this._pid_row, r.pid);
450
+ this._set_readonly(this._uptime_row, format_uptime(r.uptime_seconds));
451
+ }
452
+
453
+ _set_readonly(row, value) {
454
+ if (row && row._value_el && row._value_el.el) {
455
+ row._value_el.el.innerText = value != null ? String(value) : '—';
456
+ }
457
+ }
458
+
459
+ _set_toggle(row, value) {
460
+ if (!row || !row._toggle || !row._toggle.el) return;
461
+ const options = row._toggle.el.querySelectorAll('.toggle-option');
462
+ options.forEach(opt => {
463
+ const is_active = (opt.getAttribute('data-value') === 'true') === !!value;
464
+ opt.classList.toggle('toggle-active', is_active);
465
+ });
466
+ }
467
+
468
+ _set_select(row, value) {
469
+ if (row && row._display && row._display.el) {
470
+ row._display.el.innerText = value || '—';
471
+ }
472
+ }
473
+
474
+ _set_text(row, value) {
475
+ if (row && row._display && row._display.el) {
476
+ row._display.el.innerText = value || '—';
477
+ }
478
+ }
479
+
480
+ activate() {
481
+ if (!this.__active) {
482
+ super.activate();
483
+
484
+ // Fetch initial config
485
+ this._fetch_config();
486
+
487
+ // Toggle click handlers
488
+ this._activate_toggles();
489
+
490
+ // Select dropdown handlers
491
+ this._activate_selects();
492
+
493
+ // Text edit handlers
494
+ this._activate_text_inputs();
495
+
496
+ // Apply button
497
+ if (this._apply_btn && this._apply_btn.el) {
498
+ this._apply_btn.el.addEventListener('click', () => this._apply_changes());
499
+ }
500
+ }
501
+ }
502
+
503
+ _activate_toggles() {
504
+ const toggle_rows = [this._debug_row];
505
+ toggle_rows.forEach(row => {
506
+ if (!row || !row._toggle || !row._toggle.el) return;
507
+ const options = row._toggle.el.querySelectorAll('.toggle-option');
508
+ options.forEach(opt => {
509
+ opt.addEventListener('click', () => {
510
+ options.forEach(o => o.classList.remove('toggle-active'));
511
+ opt.classList.add('toggle-active');
512
+ this._pending_changes[row._field] = opt.getAttribute('data-value') === 'true';
513
+ this._mark_dirty();
514
+ });
515
+ });
516
+ });
517
+ }
518
+
519
+ _activate_selects() {
520
+ const select_rows = [this._log_level_row];
521
+ select_rows.forEach(row => {
522
+ if (!row || !row._display || !row._display.el) return;
523
+ const display_el = row._display.el.parentElement;
524
+
525
+ display_el.addEventListener('click', () => {
526
+ // Simple cycling through options
527
+ const current = row._display.el.innerText;
528
+ const idx = row._options.indexOf(current);
529
+ const next = row._options[(idx + 1) % row._options.length];
530
+ row._display.el.innerText = next;
531
+ this._pending_changes[row._field] = next;
532
+ this._mark_dirty();
533
+ });
534
+ });
535
+ }
536
+
537
+ _activate_text_inputs() {
538
+ const text_rows = [this._title_row];
539
+ text_rows.forEach(row => {
540
+ if (!row || !row._display || !row._display.el) return;
541
+
542
+ row._display.el.contentEditable = true;
543
+ row._display.el.addEventListener('input', () => {
544
+ this._pending_changes[row._field] = row._display.el.innerText.trim();
545
+ this._mark_dirty();
546
+ });
547
+ });
548
+ }
549
+
550
+ _mark_dirty() {
551
+ if (this._apply_btn && this._apply_btn.el) {
552
+ this._apply_btn.el.classList.add('config-btn-dirty');
553
+ }
554
+ }
555
+
556
+ async _fetch_config() {
557
+ try {
558
+ const res = await fetch('/api/admin/v1/config');
559
+ if (res.ok) {
560
+ const data = await res.json();
561
+ this.update(data);
562
+ }
563
+ } catch (err) {
564
+ console.warn('Config fetch failed:', err);
565
+ }
566
+ }
567
+
568
+ async _apply_changes() {
569
+ if (Object.keys(this._pending_changes).length === 0) return;
570
+
571
+ try {
572
+ const res = await fetch('/api/admin/v1/config', {
573
+ method: 'POST',
574
+ headers: { 'Content-Type': 'application/json' },
575
+ body: JSON.stringify(this._pending_changes)
576
+ });
577
+
578
+ if (res.ok) {
579
+ const result = await res.json();
580
+ if (Object.keys(result.rejected).length > 0) {
581
+ console.warn('Some config changes rejected:', result.rejected);
582
+ }
583
+ this._pending_changes = {};
584
+ if (this._apply_btn && this._apply_btn.el) {
585
+ this._apply_btn.el.classList.remove('config-btn-dirty');
586
+ }
587
+ // Refresh config display
588
+ this._fetch_config();
589
+ }
590
+ } catch (err) {
591
+ console.error('Config apply failed:', err);
592
+ }
593
+ }
594
+ }
595
+ ```
596
+
597
+ ### CSS
598
+
599
+ ```css
600
+ .config_panel {
601
+ min-width: 280px;
602
+ }
603
+
604
+ .config-section {
605
+ margin-bottom: 12px;
606
+ }
607
+
608
+ .config-section-header {
609
+ font-size: 8px;
610
+ font-weight: 700;
611
+ text-transform: uppercase;
612
+ letter-spacing: 1px;
613
+ color: #808080;
614
+ padding: 8px 12px 4px 12px;
615
+ border-bottom: 1px solid #E0DCD4;
616
+ margin-bottom: 4px;
617
+ }
618
+
619
+ .config-row {
620
+ display: flex;
621
+ align-items: center;
622
+ padding: 4px 12px;
623
+ font-size: 8.5px;
624
+ gap: 8px;
625
+ }
626
+
627
+ .config-row:hover {
628
+ background: rgba(0, 0, 0, 0.015);
629
+ }
630
+
631
+ .config-label {
632
+ min-width: 100px;
633
+ color: #666;
634
+ font-weight: 500;
635
+ }
636
+
637
+ .config-value {
638
+ flex: 1;
639
+ color: #2A4060;
640
+ font-family: 'Consolas', 'Courier New', monospace;
641
+ font-size: 8px;
642
+ }
643
+
644
+ .config-lock {
645
+ font-size: 7px;
646
+ opacity: 0.4;
647
+ }
648
+
649
+ /* Toggle */
650
+ .config-toggle {
651
+ display: flex;
652
+ border: 1px solid #D0CCC4;
653
+ border-radius: 3px;
654
+ overflow: hidden;
655
+ }
656
+
657
+ .toggle-option {
658
+ padding: 2px 10px;
659
+ font-size: 8px;
660
+ font-weight: 600;
661
+ cursor: pointer;
662
+ color: #808080;
663
+ transition: all 0.15s;
664
+ }
665
+
666
+ .toggle-option:first-child {
667
+ border-right: 1px solid #D0CCC4;
668
+ }
669
+
670
+ .toggle-option.toggle-active {
671
+ background: #4488CC;
672
+ color: #FFFFFF;
673
+ }
674
+
675
+ .toggle-option:hover:not(.toggle-active) {
676
+ background: rgba(68, 136, 204, 0.1);
677
+ }
678
+
679
+ /* Select */
680
+ .config-select-wrap {
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 4px;
684
+ border: 1px solid #D0CCC4;
685
+ border-radius: 3px;
686
+ padding: 2px 8px;
687
+ cursor: pointer;
688
+ font-size: 8px;
689
+ color: #2A4060;
690
+ transition: border-color 0.15s;
691
+ }
692
+
693
+ .config-select-wrap:hover {
694
+ border-color: #4488CC;
695
+ }
696
+
697
+ .config-select-arrow {
698
+ font-size: 7px;
699
+ color: #808080;
700
+ }
701
+
702
+ /* Text input */
703
+ .config-text-wrap {
704
+ flex: 1;
705
+ }
706
+
707
+ .config-text-display {
708
+ font-family: 'Consolas', 'Courier New', monospace;
709
+ font-size: 8px;
710
+ color: #2A4060;
711
+ padding: 2px 6px;
712
+ border: 1px solid transparent;
713
+ border-radius: 3px;
714
+ outline: none;
715
+ transition: border-color 0.15s;
716
+ display: inline-block;
717
+ min-width: 100px;
718
+ }
719
+
720
+ .config-text-display:focus {
721
+ border-color: #4488CC;
722
+ background: #FFFFFF;
723
+ }
724
+
725
+ /* Apply button */
726
+ .config-button-row {
727
+ display: flex;
728
+ justify-content: flex-end;
729
+ padding: 8px 12px;
730
+ border-top: 1px solid #E0DCD4;
731
+ }
732
+
733
+ .config-apply-btn {
734
+ font-size: 8px;
735
+ padding: 4px 16px;
736
+ background: #F4F2EC;
737
+ border: 1px solid #D0CCC4;
738
+ border-radius: 3px;
739
+ cursor: pointer;
740
+ color: #808080;
741
+ transition: all 0.15s;
742
+ }
743
+
744
+ .config-apply-btn:hover {
745
+ border-color: #4488CC;
746
+ color: #4488CC;
747
+ }
748
+
749
+ .config-btn-dirty {
750
+ background: #4488CC;
751
+ color: #FFFFFF;
752
+ border-color: #3377BB;
753
+ }
754
+
755
+ .config-btn-dirty:hover {
756
+ background: #3377BB;
757
+ color: #FFFFFF;
758
+ }
759
+ ```
760
+
761
+ ---
762
+
763
+ ## Safety Considerations
764
+
765
+ ### Why Most Settings Are Read-Only
766
+
767
+ Changing the server port, host, or entry point at runtime would require:
768
+ 1. Closing the HTTP listener
769
+ 2. Rebinding to a new port
770
+ 3. Potentially losing active connections
771
+ 4. Rebuilding bundles with new configuration
772
+
773
+ This is inherently dangerous for a production server. The config panel avoids this by showing these values for informational purposes only, marked with a lock icon.
774
+
775
+ ### Mutable Settings — Safe Changes
776
+
777
+ The three editable fields were chosen because they have no destructive side effects:
778
+
779
+ | Field | Effect | Risk |
780
+ |-------|--------|------|
781
+ | Debug Mode | Sets `JSGUI_DEBUG` env var; increases logging verbosity | Low — extra log output |
782
+ | Log Level | Controls adapter-managed log filtering | Low — changes what's logged |
783
+ | Process Title | Sets `process.title`; visible in `ps`/task manager | None |
784
+
785
+ ### Input Validation
786
+
787
+ All mutable settings are validated server-side in the adapter:
788
+ - `debug_mode`: Must be boolean
789
+ - `log_level`: Must be one of `['error', 'warn', 'info', 'debug']`
790
+ - `process_title`: Must be a non-empty string under 256 characters
791
+
792
+ Invalid values are returned in the `rejected` field of the response, and the UI displays the original value unchanged.
793
+
794
+ ---
795
+
796
+ ## Integration with Dashboard
797
+
798
+ The Config_Panel appears as a dedicated sidebar section ("Configuration") rather than on the main dashboard. This keeps the dashboard focused on live metrics while configuration is a separate concern.
799
+
800
+ ```javascript
801
+ // In Admin_Shell navigation handler
802
+ case 'configuration':
803
+ const config_panel = new Config_Panel({ context, title: 'Server Configuration' });
804
+ this._content_area.add(config_panel);
805
+ break;
806
+ ```
807
+
808
+ Changes applied through the config panel are reflected immediately in the adapter layer. For example, toggling debug mode causes subsequent requests to include debug timing headers, and the log viewer begins showing debug-level entries.