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,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format a byte count into a human-readable string.
|
|
5
|
+
* @param {number} bytes
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
function format_bytes(bytes) {
|
|
9
|
+
if (bytes === 0 || bytes === null || bytes === undefined) return '0 B';
|
|
10
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
11
|
+
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024));
|
|
12
|
+
const index = Math.min(i, units.length - 1);
|
|
13
|
+
const value = bytes / Math.pow(1024, index);
|
|
14
|
+
return (index > 0 ? value.toFixed(1) : Math.round(value)) + ' ' + units[index];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Format seconds into a human-readable uptime string.
|
|
19
|
+
* @param {number} seconds
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function format_uptime(seconds) {
|
|
23
|
+
if (seconds === null || seconds === undefined || seconds < 0) return '—';
|
|
24
|
+
seconds = Math.floor(seconds);
|
|
25
|
+
if (seconds < 60) return seconds + 's';
|
|
26
|
+
|
|
27
|
+
const days = Math.floor(seconds / 86400);
|
|
28
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
29
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
30
|
+
const secs = seconds % 60;
|
|
31
|
+
|
|
32
|
+
if (days > 0) return days + 'd ' + hours + 'h';
|
|
33
|
+
if (hours > 0) return hours + 'h ' + minutes + 'm';
|
|
34
|
+
return minutes + 'm ' + secs + 's';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format a timestamp into HH:MM:SS.
|
|
39
|
+
* @param {number} timestamp - Unix milliseconds
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function format_time(timestamp) {
|
|
43
|
+
if (!timestamp) return '--:--:--';
|
|
44
|
+
const d = new Date(timestamp);
|
|
45
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
46
|
+
return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format a timestamp into a relative human-readable string.
|
|
51
|
+
* @param {number} timestamp - Unix milliseconds
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function format_relative_time(timestamp) {
|
|
55
|
+
if (!timestamp) return '—';
|
|
56
|
+
const diff = Date.now() - timestamp;
|
|
57
|
+
if (diff < 60000) return 'just now';
|
|
58
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + ' minutes ago';
|
|
59
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + ' hours ago';
|
|
60
|
+
return new Date(timestamp).toLocaleString();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
format_bytes,
|
|
65
|
+
format_uptime,
|
|
66
|
+
format_time,
|
|
67
|
+
format_relative_time
|
|
68
|
+
};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Admin UI Extension Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The jsgui3-server admin dashboard (`/admin/v1`) is fully extensible. You can:
|
|
6
|
+
|
|
7
|
+
1. **Disable** the admin UI entirely
|
|
8
|
+
2. **Add custom sidebar sections** with automatic data rendering
|
|
9
|
+
3. **Add custom protected API endpoints** with role-based auth
|
|
10
|
+
4. **Use plugins** to bundle related admin extensions
|
|
11
|
+
5. **Access admin internals** (auth service, user store, SSE channel) for advanced use
|
|
12
|
+
|
|
13
|
+
All extension APIs follow snake_case naming and return `this` for chaining.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
const Server = require('jsgui3-server');
|
|
21
|
+
|
|
22
|
+
const server = await Server.serve(MyControl, { port: 8080 });
|
|
23
|
+
|
|
24
|
+
// Add a custom section — shows up in the sidebar
|
|
25
|
+
server.admin_v1.add_section({
|
|
26
|
+
id: 'my_data',
|
|
27
|
+
label: 'My Data',
|
|
28
|
+
api_path: '/api/admin/v1/my-data',
|
|
29
|
+
handler: (req, res) => {
|
|
30
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
31
|
+
res.end(JSON.stringify([
|
|
32
|
+
{ name: 'Item A', value: 42 },
|
|
33
|
+
{ name: 'Item B', value: 99 }
|
|
34
|
+
]));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That's it. The sidebar section appears automatically, and clicking it shows a table.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## API Reference
|
|
44
|
+
|
|
45
|
+
### `admin_v1.add_section(options)`
|
|
46
|
+
|
|
47
|
+
Register a custom sidebar section.
|
|
48
|
+
|
|
49
|
+
| Parameter | Type | Required | Default | Description |
|
|
50
|
+
|----------------|-----------|----------|----------------|-------------|
|
|
51
|
+
| `id` | `string` | Yes | — | Unique section identifier (snake_case) |
|
|
52
|
+
| `label` | `string` | Yes | — | Human-readable sidebar label |
|
|
53
|
+
| `icon` | `string` | No | `null` | Emoji or text icon prefix |
|
|
54
|
+
| `api_path` | `string` | Yes | — | Data endpoint path |
|
|
55
|
+
| `role` | `string` | No | `'admin_read'` | Required role to view |
|
|
56
|
+
| `handler` | `Function`| No | — | `(req, res)` handler for the endpoint |
|
|
57
|
+
|
|
58
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
59
|
+
|
|
60
|
+
If `handler` is provided, it is automatically registered as a protected endpoint at `api_path`. If omitted, you must register the endpoint separately with `add_endpoint()` or via `server.publish()`.
|
|
61
|
+
|
|
62
|
+
#### Auto-Rendering Behaviour
|
|
63
|
+
|
|
64
|
+
The admin shell fetches `api_path` and renders the response based on its shape:
|
|
65
|
+
|
|
66
|
+
| Response Shape | Rendered As |
|
|
67
|
+
|------------------------|-----------------------|
|
|
68
|
+
| Array of objects | Table (columns = keys)|
|
|
69
|
+
| Object | Key-value panel |
|
|
70
|
+
| Scalar (string/number) | Plain text block |
|
|
71
|
+
| Empty array | "No data" message |
|
|
72
|
+
|
|
73
|
+
The v1 shell builds these panels with jsgui controls (table rows, key-value rows, buttons) rather than string-based `innerHTML` rendering.
|
|
74
|
+
|
|
75
|
+
#### Example: Array → Table
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// Handler returns:
|
|
79
|
+
[
|
|
80
|
+
{ name: 'Worker 1', status: 'running', cpu: '12%' },
|
|
81
|
+
{ name: 'Worker 2', status: 'idle', cpu: '0%' }
|
|
82
|
+
]
|
|
83
|
+
// Renders as a 3-column table: Name | Status | CPU
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Example: Object → Key-Value
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Handler returns:
|
|
90
|
+
{ version: '2.1.0', uptime: '4h 12m', mode: 'production' }
|
|
91
|
+
// Renders as Key-Value pairs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `admin_v1.add_endpoint(options)`
|
|
97
|
+
|
|
98
|
+
Register a custom protected API endpoint.
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Required | Default | Description |
|
|
101
|
+
|-----------|-----------|----------|----------------|-------------|
|
|
102
|
+
| `path` | `string` | Yes | — | Route path |
|
|
103
|
+
| `role` | `string` | No | `'admin_read'` | Required role |
|
|
104
|
+
| `handler` | `Function`| Yes | — | `(req, res)` handler |
|
|
105
|
+
|
|
106
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
107
|
+
|
|
108
|
+
The endpoint is automatically wrapped with role-based authentication. Unauthenticated requests get 401, insufficient-role requests get 403.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
server.admin_v1.add_endpoint({
|
|
112
|
+
path: '/api/admin/v1/workers/restart',
|
|
113
|
+
role: 'admin_write',
|
|
114
|
+
handler: (req, res) => {
|
|
115
|
+
// restart logic ...
|
|
116
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
117
|
+
res.end(JSON.stringify({ ok: true }));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `admin_v1.use(plugin_fn)`
|
|
125
|
+
|
|
126
|
+
Plugin-style extension point. The function receives the admin module instance.
|
|
127
|
+
|
|
128
|
+
**Returns:** `Admin_Module_V1` (chainable)
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// my_admin_plugin.js
|
|
132
|
+
function my_admin_plugin(admin) {
|
|
133
|
+
admin.add_section({
|
|
134
|
+
id: 'metrics',
|
|
135
|
+
label: 'Metrics',
|
|
136
|
+
icon: '📊',
|
|
137
|
+
api_path: '/api/admin/v1/metrics',
|
|
138
|
+
handler: (req, res) => {
|
|
139
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
140
|
+
res.end(JSON.stringify({ requests: 12345, errors: 2 }));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
admin.add_endpoint({
|
|
145
|
+
path: '/api/admin/v1/metrics/reset',
|
|
146
|
+
role: 'admin_write',
|
|
147
|
+
handler: (req, res) => {
|
|
148
|
+
// reset metrics...
|
|
149
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
150
|
+
res.end(JSON.stringify({ ok: true }));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = my_admin_plugin;
|
|
156
|
+
|
|
157
|
+
// In your server.js:
|
|
158
|
+
server.admin_v1.use(require('./my_admin_plugin'));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### `admin_v1.get_custom_sections()`
|
|
164
|
+
|
|
165
|
+
Returns the current list of registered custom section metadata.
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
const sections = server.admin_v1.get_custom_sections();
|
|
169
|
+
// [{ id: 'metrics', label: 'Metrics', icon: '📊', api_path: '/api/admin/v1/metrics' }]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Declarative Configuration
|
|
175
|
+
|
|
176
|
+
Custom sections and endpoints can be declared in the `Server.serve()` options:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
Server.serve({
|
|
180
|
+
Ctrl: MyControl,
|
|
181
|
+
port: 8080,
|
|
182
|
+
admin: {
|
|
183
|
+
sections: [
|
|
184
|
+
{
|
|
185
|
+
id: 'jobs',
|
|
186
|
+
label: 'Background Jobs',
|
|
187
|
+
icon: '⚙️',
|
|
188
|
+
api_path: '/api/admin/v1/jobs',
|
|
189
|
+
handler: (req, res) => {
|
|
190
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
191
|
+
res.end(JSON.stringify(get_job_status()));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
endpoints: [
|
|
196
|
+
{
|
|
197
|
+
path: '/api/admin/v1/jobs/trigger',
|
|
198
|
+
role: 'admin_write',
|
|
199
|
+
handler: (req, res) => {
|
|
200
|
+
trigger_job();
|
|
201
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
202
|
+
res.end(JSON.stringify({ ok: true }));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Disabling the Admin UI
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Boolean shorthand
|
|
216
|
+
Server.serve({ Ctrl: MyControl, admin: false });
|
|
217
|
+
|
|
218
|
+
// Object form
|
|
219
|
+
Server.serve({ Ctrl: MyControl, admin: { enabled: false } });
|
|
220
|
+
|
|
221
|
+
// Constructor form
|
|
222
|
+
const server = new Server({ Ctrl: MyControl, admin: false });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
When disabled, no admin routes, SSE channel, or request instrumentation are set up. `server.admin_v1` is `null`.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Exported Classes
|
|
230
|
+
|
|
231
|
+
For advanced use cases (custom auth, subclassing, testing), the admin classes are exported:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const Server = require('jsgui3-server');
|
|
235
|
+
|
|
236
|
+
// On the Server constructor:
|
|
237
|
+
const { Admin_Module_V1, Admin_Auth_Service, Admin_User_Store } = Server;
|
|
238
|
+
|
|
239
|
+
// From the npm module entry:
|
|
240
|
+
const jsgui = require('jsgui3-server');
|
|
241
|
+
const { Admin_Module_V1, Admin_Auth_Service, Admin_User_Store } = jsgui;
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Admin_Module_V1
|
|
245
|
+
|
|
246
|
+
The core admin adapter. Instruments the server, manages telemetry, provides APIs and SSE.
|
|
247
|
+
|
|
248
|
+
**Key properties:**
|
|
249
|
+
- `auth` — `Admin_Auth_Service` instance
|
|
250
|
+
- `user_store` — `Admin_User_Store` instance
|
|
251
|
+
|
|
252
|
+
**Key methods:**
|
|
253
|
+
- `init(server)` — Attach to a server instance
|
|
254
|
+
- `add_section(opts)` — Register custom sidebar section
|
|
255
|
+
- `add_endpoint(opts)` — Register custom API endpoint
|
|
256
|
+
- `use(plugin_fn)` — Apply a plugin function
|
|
257
|
+
- `get_custom_sections()` — Get section metadata array
|
|
258
|
+
- `get_status()` — Get server status snapshot
|
|
259
|
+
- `get_resources_tree()` — Get resource pool tree
|
|
260
|
+
- `get_routes_list()` — Get route registrations
|
|
261
|
+
- `destroy()` — Cleanup heartbeat and SSE
|
|
262
|
+
|
|
263
|
+
### Admin_Auth_Service
|
|
264
|
+
|
|
265
|
+
Session-based authentication service.
|
|
266
|
+
|
|
267
|
+
**Key methods:**
|
|
268
|
+
- `is_authenticated(req)` — Check if request has a valid session
|
|
269
|
+
- `has_role(req, role)` — Check if session has a specific role
|
|
270
|
+
- `has_any_role(req, roles)` — Check if session has any of the given roles
|
|
271
|
+
- `create_session(username, roles)` — Create a new session
|
|
272
|
+
- `destroy_session(session_id)` — Remove a session
|
|
273
|
+
- `handle_login(req, res)` — HTTP login handler
|
|
274
|
+
- `handle_logout(req, res)` — HTTP logout handler
|
|
275
|
+
- `handle_session(req, res)` — HTTP session check handler
|
|
276
|
+
|
|
277
|
+
### Admin_User_Store
|
|
278
|
+
|
|
279
|
+
In-memory user credential store using scrypt hashing.
|
|
280
|
+
|
|
281
|
+
**Key methods:**
|
|
282
|
+
- `add_user({ username, password, roles })` — Add a user (password is hashed)
|
|
283
|
+
- `verify_credentials(username, password)` — Verify credentials (returns `{ valid, user }`)
|
|
284
|
+
- `has_user(username)` — Check if user exists
|
|
285
|
+
- `get_user(username)` — Get user record (without password hash)
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Architecture Notes
|
|
290
|
+
|
|
291
|
+
### How Custom Sections Work
|
|
292
|
+
|
|
293
|
+
1. **Server-side:** `add_section()` stores metadata and optionally registers an API endpoint
|
|
294
|
+
2. **Client-side:** On activation, the admin shell fetches `GET /api/admin/v1/custom-sections`
|
|
295
|
+
3. **Dynamic nav:** Custom sections are added to the sidebar below a separator line
|
|
296
|
+
4. **Data fetch:** When clicked, the shell fetches the section's `api_path`
|
|
297
|
+
5. **Auto-render:** The response is rendered as a table, key-value panel, or text
|
|
298
|
+
|
|
299
|
+
On repeated metadata fetches, previously mounted custom section nav controls are removed before new ones are added. This keeps the sidebar free of duplicate custom entries.
|
|
300
|
+
|
|
301
|
+
### Security Model
|
|
302
|
+
|
|
303
|
+
- All custom endpoints are wrapped with role-based guards automatically
|
|
304
|
+
- Unauthenticated → 401 JSON response
|
|
305
|
+
- Authenticated but missing role → 403 JSON response
|
|
306
|
+
- Session cookies are `httpOnly` with `SameSite=Lax`
|
|
307
|
+
|
|
308
|
+
### Roles
|
|
309
|
+
|
|
310
|
+
| Role | Purpose |
|
|
311
|
+
|---------------|-------------------------------|
|
|
312
|
+
| `admin_read` | View dashboard data and sections |
|
|
313
|
+
| `admin_write` | Mutating operations (start/stop/restart) |
|
|
314
|
+
|
|
315
|
+
Default users get both roles. Custom user creation:
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
server.admin_v1.user_store.add_user({
|
|
319
|
+
username: 'viewer',
|
|
320
|
+
password: 'readonly123',
|
|
321
|
+
roles: ['admin_read']
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Chaining Example
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
server.admin_v1
|
|
331
|
+
.add_section({ id: 'workers', label: 'Workers', api_path: '/api/admin/v1/workers', handler: workers_handler })
|
|
332
|
+
.add_section({ id: 'queues', label: 'Queues', api_path: '/api/admin/v1/queues', handler: queues_handler })
|
|
333
|
+
.add_endpoint({ path: '/api/admin/v1/workers/scale', role: 'admin_write', handler: scale_handler })
|
|
334
|
+
.use(require('./my-monitoring-plugin'));
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Verification
|
|
340
|
+
|
|
341
|
+
Use the Admin UI interaction regression suite after extension changes that affect shell behavior:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
node tests/test-runner.js --test=admin-ui-jsgui-controls.test.js
|
|
345
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Chapter 1 — Control Candidate Matrix and Findings
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Prioritize the control catalog for adaptive upgrades based on real impact:
|
|
6
|
+
|
|
7
|
+
- Structural risk on phone/tablet/orientation change
|
|
8
|
+
- Data density and interaction complexity
|
|
9
|
+
- Existing gap between desktop and touch behavior
|
|
10
|
+
- Reuse value as a pattern for other controls
|
|
11
|
+
|
|
12
|
+
## Evidence Snapshot
|
|
13
|
+
|
|
14
|
+
Recent source-level review identified these concrete issues in key controls:
|
|
15
|
+
|
|
16
|
+
- `master_detail.js`: fixed two-column grid (`minmax(180px, 240px) 1fr`) with no layout-mode branch.
|
|
17
|
+
- `tabbed-panel.js`: horizontal tab strip is vulnerable to overflow in narrow widths.
|
|
18
|
+
- `split_pane.js`: pointer-oriented resize interaction with no touch-first fallback.
|
|
19
|
+
- `data_table.js`: large data surface with no responsive column-priority/card transformation strategy.
|
|
20
|
+
- `sidebar_nav.js`: manual collapse exists, but no environment-driven auto-collapse/morph.
|
|
21
|
+
- `Toolbar.js`: no overflow strategy for narrow widths.
|
|
22
|
+
- `modal.js`: static size variants only; no automatic phone full-screen mode.
|
|
23
|
+
- `form_container.js`: no adaptive multi-column to single-column strategy.
|
|
24
|
+
- `status_dashboard.js`: strongest baseline due to grid auto-fit, but no explicit density/layout-mode policy.
|
|
25
|
+
|
|
26
|
+
## Tiering Criteria
|
|
27
|
+
|
|
28
|
+
### Impact score (1-5)
|
|
29
|
+
|
|
30
|
+
- 5: Breaks or heavily degrades on common phone/tablet layouts
|
|
31
|
+
- 4: Usable but substantially reduced usability on touch/narrow layouts
|
|
32
|
+
- 3: Mostly usable; quality or accessibility debt
|
|
33
|
+
- 2: Minor adaptive polish only
|
|
34
|
+
- 1: Already adaptive enough through existing composition/tokens
|
|
35
|
+
|
|
36
|
+
### Effort score (1-5)
|
|
37
|
+
|
|
38
|
+
- 5: Significant API and behavior changes across composition + interaction + tests
|
|
39
|
+
- 4: Medium-high code and test work
|
|
40
|
+
- 3: Moderate scoped update
|
|
41
|
+
- 2: Small targeted update
|
|
42
|
+
- 1: Minimal changes
|
|
43
|
+
|
|
44
|
+
## Candidate Matrix
|
|
45
|
+
|
|
46
|
+
| Control | Impact | Effort | Priority | Why now |
|
|
47
|
+
|---|---:|---:|---|---|
|
|
48
|
+
| Master_Detail | 5 | 3 | Tier 1 | Canonical two-pane pattern; immediate phone pain; good first pattern anchor |
|
|
49
|
+
| Tabbed_Panel | 5 | 4 | Tier 1 | High usage + overflow + keyboard/touch adaptation needs |
|
|
50
|
+
| Split_Pane | 5 | 4 | Tier 1 | Pointer-first behavior conflicts with touch/mobile |
|
|
51
|
+
| Data_Table | 5 | 5 | Tier 1 | Highest value for admin UIs; biggest adaptation gap |
|
|
52
|
+
| Sidebar_Nav | 4 | 3 | Tier 2 | Shell-level navigation needs auto morphing |
|
|
53
|
+
| Form_Container | 4 | 3 | Tier 2 | Forms must adapt cleanly across orientation and width |
|
|
54
|
+
| Modal | 4 | 2 | Tier 2 | Quick high-value upgrade for phone usability |
|
|
55
|
+
| Toolbar | 4 | 3 | Tier 2 | Navigation/action density requires overflow strategy |
|
|
56
|
+
| Window / Window_Manager | 3 | 4 | Tier 3 | Desktop metaphor must degrade predictably on touch |
|
|
57
|
+
| Wizard | 3 | 2 | Tier 3 | Stepper strip and nav controls need mobile pattern |
|
|
58
|
+
| Status_Dashboard | 2 | 2 | Tier 4 | Mostly good; needs explicit density/mode tuning |
|
|
59
|
+
| Drawer | 2 | 2 | Tier 4 | Good baseline; mostly environment integration and polish |
|
|
60
|
+
|
|
61
|
+
## Top Discoveries
|
|
62
|
+
|
|
63
|
+
### Discovery A — the highest-value pattern is the two-pane morph
|
|
64
|
+
|
|
65
|
+
`Master_Detail`, `Split_Pane`, and many app-level shells all need the same adaptive morph:
|
|
66
|
+
|
|
67
|
+
- Desktop: dual pane
|
|
68
|
+
- Tablet portrait: primary + secondary as revealable overlay
|
|
69
|
+
- Phone: stacked flow or drawer/sheet secondary
|
|
70
|
+
|
|
71
|
+
This should be implemented once as a reusable adaptive region pattern and reused.
|
|
72
|
+
|
|
73
|
+
### Discovery B — navigation controls need shared overflow/morph infrastructure
|
|
74
|
+
|
|
75
|
+
`Tabbed_Panel`, `Sidebar_Nav`, and `Toolbar` all face narrow-width overflow and touch navigation constraints.
|
|
76
|
+
|
|
77
|
+
Without shared helpers, each control will reinvent:
|
|
78
|
+
|
|
79
|
+
- overflow detection
|
|
80
|
+
- “more” bucket behavior
|
|
81
|
+
- focus order and ARIA preservation across morphs
|
|
82
|
+
|
|
83
|
+
### Discovery C — Data_Table is both a control and a mode family
|
|
84
|
+
|
|
85
|
+
Treat `Data_Table` as a multi-mode control family:
|
|
86
|
+
|
|
87
|
+
- Desktop mode: full grid
|
|
88
|
+
- Tablet mode: reduced columns + optional detail pane
|
|
89
|
+
- Phone mode: list/card mode with row expansion
|
|
90
|
+
|
|
91
|
+
Trying to keep a single static grid shape across all modes will keep mobile quality low.
|
|
92
|
+
|
|
93
|
+
## Layer Analysis (A/B/C/D)
|
|
94
|
+
|
|
95
|
+
Applying the four-layer model:
|
|
96
|
+
|
|
97
|
+
- Layer A (Domain): mostly unaffected; business data stays stable.
|
|
98
|
+
- Layer B (View Composition): major work in Tier 1 and Tier 2 controls.
|
|
99
|
+
- Layer C (Adaptive Resolution): requires consistent environment service usage.
|
|
100
|
+
- Layer D (Concrete Render): token/mode-attribute CSS work across most controls.
|
|
101
|
+
|
|
102
|
+
## Model Placement Rules for This Program
|
|
103
|
+
|
|
104
|
+
For all upgrades in this book:
|
|
105
|
+
|
|
106
|
+
- `data.model`: business entities, user-intent preferences that are device-agnostic.
|
|
107
|
+
- `view.data.model`: resolved adaptive state (layout_mode, visible_columns, region presentation state).
|
|
108
|
+
- `view.model`: transient interaction state (open/closed, active tab index, temporary scroll/focus state).
|
|
109
|
+
|
|
110
|
+
Never persist runtime viewport-derived values as domain data.
|
|
111
|
+
|
|
112
|
+
## Success Criteria for Candidate Selection
|
|
113
|
+
|
|
114
|
+
A control is ready for implementation when:
|
|
115
|
+
|
|
116
|
+
1. Target mode behavior is specified for phone, tablet, desktop.
|
|
117
|
+
2. State placement is mapped to model layers.
|
|
118
|
+
3. CSS mode-attribute strategy is identified.
|
|
119
|
+
4. P0/P1/P2 viewport assertions are defined.
|
|
120
|
+
5. Regression impact on existing desktop behavior is bounded.
|
|
121
|
+
|
|
122
|
+
Next: Tier 1 playbooks with concrete upgrade recipes.
|