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.
- package/.github/agents/Mobile Developer.agent.md +89 -0
- package/AGENTS.md +4 -0
- package/README.md +130 -0
- package/admin-ui/client.js +73 -43
- 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/docs/admin-extension-guide.md +345 -0
- 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/comparison-report-express-plex-cpanel.md +549 -0
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/troubleshooting.md +84 -53
- package/module.js +16 -11
- package/package.json +1 -1
- package/serve-factory.js +1 -0
- package/server.js +199 -0
- package/tests/README.md +5 -0
- package/tests/admin-ui-jsgui-controls.test.js +581 -0
- package/tests/test-runner.js +1 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
- 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.
|