jsgui3-server 0.0.147 → 0.0.148

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.
@@ -0,0 +1,194 @@
1
+ # Chapter 3: Controls
2
+
3
+ ## Control Placement Strategy
4
+
5
+ | Control | Location | Rationale |
6
+ |---------|----------|-----------|
7
+ | `Property_Viewer` | jsgui3-html | General-purpose read-only property display |
8
+ | `Property_Editor` | jsgui3-html | Already exists - editing properties |
9
+ | `Object_KVP_Viewer` | jsgui3-html | Already exists - key-value pairs |
10
+ | `Object_KVP_Editor` | jsgui3-html | Already exists - editable KVP |
11
+ | `Resource_Viewer` | jsgui3-html | General-purpose for any resource with name/type/status |
12
+ | `Tree_View` | jsgui3-html | General-purpose hierarchical display |
13
+ | `Admin_Page` | jsgui3-server | Server-specific admin shell |
14
+ | `Resource_List` | jsgui3-server | Server-specific resource listing |
15
+ | `Observables_List` | jsgui3-server | Server-specific observable listing |
16
+ | `Observable_Monitor` | jsgui3-server | Server-specific real-time monitoring |
17
+ | `Resource_Detail_Page` | jsgui3-server | Server-specific resource detail view |
18
+
19
+ ---
20
+
21
+ ## General-Purpose Controls (jsgui3-html)
22
+
23
+ ### Existing Controls to Leverage
24
+
25
+ 1. **`Property_Editor`** - Edits properties with type-specific inputs
26
+ 2. **`Object_KVP_Viewer`** - Displays object as key-value pairs
27
+ 3. **`Object_KVP_Editor`** - Editable key-value pairs
28
+
29
+ ### New Controls Needed in jsgui3-html
30
+
31
+ #### Property_Viewer
32
+
33
+ Read-only display of an object's properties in a clean table format.
34
+
35
+ ```javascript
36
+ const viewer = new controls.Property_Viewer({
37
+ context,
38
+ data: { name: 'MyResource', type: 'observable', status: 'active' },
39
+ schema: {
40
+ name: { label: 'Name', type: 'string' },
41
+ type: { label: 'Type', type: 'badge' },
42
+ status: { label: 'Status', type: 'status-indicator' }
43
+ }
44
+ });
45
+ ```
46
+
47
+ #### Resource_Viewer
48
+
49
+ Displays a resource with icon, name, type badge, and expandable details.
50
+
51
+ ```javascript
52
+ const rv = new controls.Resource_Viewer({
53
+ context,
54
+ resource: {
55
+ name: '/api/tick-stream',
56
+ type: 'observable',
57
+ schema: { type: 'int' },
58
+ status: 'active',
59
+ connections: 3
60
+ }
61
+ });
62
+ ```
63
+
64
+ #### Tree_View
65
+
66
+ Hierarchical tree with expand/collapse, icons, and selection.
67
+
68
+ ```javascript
69
+ const tree = new controls.Tree_View({
70
+ context,
71
+ data: [
72
+ { label: 'Publishers', icon: '📡', children: [...] },
73
+ { label: 'Routes', icon: '🛤️', children: [...] }
74
+ ],
75
+ onSelect: (node) => console.log('Selected:', node)
76
+ });
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Server-Specific Controls (jsgui3-server/admin-ui)
82
+
83
+ ### 1. Admin_Page
84
+
85
+ The main container control that serves as the Admin UI shell.
86
+
87
+ ```javascript
88
+ class Admin_Page extends Active_HTML_Document {
89
+ // Renders sidebar + main panel + header
90
+ }
91
+ ```
92
+
93
+ **Layout:**
94
+ - Left sidebar: `Resource_List` + `Observables_List`
95
+ - Main panel: `Resource_Detail_Page` or selected content
96
+ - Header: Server name, uptime, connection count
97
+
98
+ ### 2. Resource_List
99
+
100
+ Displays all registered server resources using `Tree_View`.
101
+
102
+ **Data Source:** `GET /api/admin/resources`
103
+
104
+ **Structure:**
105
+ ```
106
+ ├── Routes
107
+ │ ├── / (webpage)
108
+ │ ├── /admin (admin-ui)
109
+ │ └── /api/* (function)
110
+ ├── Publishers
111
+ │ ├── HTTP_Website_Publisher
112
+ │ └── HTTP_Observable_Publisher
113
+ └── Resources
114
+ ├── Server Router
115
+ └── Resource Pool
116
+ ```
117
+
118
+ ### 3. Observables_List
119
+
120
+ Lists all published observables with status indicators.
121
+
122
+ **Data Source:** `GET /api/admin/observables`
123
+
124
+ **Item Display:**
125
+ - Route path
126
+ - Schema type badge
127
+ - Status indicator (active/paused/stopped)
128
+ - Connection count
129
+
130
+ ### 4. Observable_Monitor
131
+
132
+ Real-time display for a single observable using `Auto_Observable_UI`.
133
+
134
+ **Features:**
135
+ - Play/Pause controls
136
+ - History buffer (last N values)
137
+ - Schema display
138
+ - Export to JSON
139
+
140
+ ### 5. Resource_Detail_Page
141
+
142
+ Detail view for a selected resource.
143
+
144
+ **Sections:**
145
+ - **Header**: Name, type badge
146
+ - **Properties**: Using `Property_Viewer`
147
+ - **Actions**: Pause, resume, stop (for observables)
148
+ - **Live View**: For observables, embedded monitor
149
+
150
+ ### 6. Config_Panel
151
+
152
+ Form-based configuration editor using `Property_Editor`.
153
+
154
+ **Features:**
155
+ - Read-only by default (safety)
156
+ - Unlock with confirmation for editing
157
+ - Validation before save
158
+
159
+ ### 7. Metrics_Dashboard
160
+
161
+ Displays server health metrics.
162
+
163
+ **Metrics:**
164
+ - Active connections
165
+ - Requests/second
166
+ - Memory usage
167
+ - Observable subscription count
168
+
169
+ ---
170
+
171
+ ## Styling Approach
172
+
173
+ All controls use CSS-in-JS via the static `.css` property:
174
+
175
+ ```javascript
176
+ Admin_Page.css = `
177
+ .admin-page {
178
+ display: grid;
179
+ grid-template-columns: 280px 1fr;
180
+ height: 100vh;
181
+ background: #1a1a2e;
182
+ }
183
+ .admin-sidebar { ... }
184
+ .admin-main { ... }
185
+ `;
186
+ ```
187
+
188
+ ## Extension Points
189
+
190
+ Custom admin panels can be registered:
191
+
192
+ ```javascript
193
+ server.admin.registerPanel('MyPanel', MyPanelControl);
194
+ ```
@@ -0,0 +1,62 @@
1
+ # Chapter 4: Implementation Plan
2
+
3
+ ## Phase 1: Foundation
4
+
5
+ ### jsgui3-html (General-Purpose)
6
+ - [ ] Implement `Property_Viewer` control
7
+ - [ ] Implement `Resource_Viewer` control
8
+ - [ ] Implement `Tree_View` control
9
+
10
+ ### jsgui3-server (Admin-Specific)
11
+ - [x] Create `admin-ui/client.js` with `Admin_Page`
12
+ - [x] Create `admin-ui/server.js` with API routes
13
+ - [x] Register `/admin` route in main server
14
+
15
+ ## Phase 2: Resource Browser
16
+
17
+ - [ ] Implement `Resource_List` using `Tree_View`
18
+ - [x] Add API: `GET /api/admin/resources`
19
+ - [ ] Implement `Resource_Detail_Page`
20
+ - [ ] Wire up selection → detail view
21
+
22
+ ## Phase 3: Observable Visibility
23
+
24
+ - [ ] Implement `Observables_List` control
25
+ - [x] Add API: `GET /api/admin/observables`
26
+ - [ ] Implement `Observable_Monitor` with `Auto_Observable_UI`
27
+ - [ ] Add play/pause/history features
28
+
29
+ ## Phase 4: Metrics & Config
30
+
31
+ - [ ] Implement `Metrics_Dashboard`
32
+ - [ ] Add API: `GET /api/admin/metrics` (SSE)
33
+ - [ ] Implement `Config_Panel` using `Property_Editor`
34
+
35
+ ## Phase 5: Polish
36
+
37
+ - [ ] Responsive design
38
+ - [ ] Keyboard navigation
39
+ - [ ] Export/import config
40
+ - [ ] Documentation and examples
41
+
42
+ ## File Checklist
43
+
44
+ | File | Location | Status |
45
+ |------|----------|--------|
46
+ | `Property_Viewer.js` | jsgui3-html | Planned |
47
+ | `Resource_Viewer.js` | jsgui3-html | Planned |
48
+ | `Tree_View.js` | jsgui3-html | Planned |
49
+ | `admin-ui/client.js` | jsgui3-server | ✅ Done |
50
+ | `admin-ui/server.js` | jsgui3-server | ✅ Done |
51
+ | `admin-ui/controls/Resource_List.js` | jsgui3-server | Planned |
52
+ | `admin-ui/controls/Observables_List.js` | jsgui3-server | Planned |
53
+ | `admin-ui/controls/Observable_Monitor.js` | jsgui3-server | Planned |
54
+ | `admin-ui/controls/Resource_Detail_Page.js` | jsgui3-server | Planned |
55
+
56
+ ## Success Criteria
57
+
58
+ 1. Navigate to `/admin` and see the Admin UI
59
+ 2. Browse all server resources in tree view
60
+ 3. Select a resource to see its properties
61
+ 4. View live observable streams with auto-generated UIs
62
+ 5. See basic metrics (connections, uptime)
@@ -0,0 +1,26 @@
1
+ # Admin UI Book
2
+
3
+ This folder contains documentation for the `jsgui3-server` Admin UI system.
4
+
5
+ ## Chapters
6
+
7
+ 1. [Introduction](./01-introduction.md) - Vision and goals
8
+ 2. [Architecture](./02-architecture.md) - High-level design
9
+ 3. [Controls](./03-controls.md) - UI components
10
+ 4. [Implementation Plan](./04-implementation-plan.md) - Phases and tasks
11
+
12
+ ## Purpose
13
+
14
+ The Admin UI provides a web-based interface to administer and monitor `jsgui3-server` instances. Key features include:
15
+
16
+ - **Resource Viewer**: Browse server-side resources (publishers, routes, etc.)
17
+ - **Observable Monitor**: Real-time visibility into observable server processes
18
+ - **Configuration Editor**: Modify server settings
19
+ - **Performance Dashboard**: View metrics and health
20
+
21
+ ## Design Principles
22
+
23
+ 1. **Dogfooding**: Built entirely with jsgui3 controls
24
+ 2. **Real-time**: Uses `Remote_Observable` for live updates
25
+ 3. **Extensible**: Plugin architecture for custom panels
26
+ 4. **Polished UX**: Modern dark theme, smooth transitions
@@ -0,0 +1,125 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const { controls, Control } = jsgui;
3
+ const Auto_Observable_UI = require('../../../client/controls/auto-observable');
4
+ controls.Auto_Observable_UI = Auto_Observable_UI;
5
+
6
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
7
+
8
+ class Demo_Page extends Active_HTML_Document {
9
+ constructor(spec = {}) {
10
+ spec.__type_name = spec.__type_name || 'demo_page';
11
+ super(spec);
12
+ const { context } = this;
13
+
14
+ if (typeof this.body.add_class === 'function') {
15
+ this.body.add_class('demo-page');
16
+ }
17
+
18
+ const compose = () => {
19
+ const h1 = new controls.h1({ context });
20
+ h1.add('Auto Observable UI Demo');
21
+ this.body.add(h1);
22
+
23
+ const container = new controls.div({ context, class: 'params-container' });
24
+ this.body.add(container);
25
+
26
+ // 1. Tick Counter (Int)
27
+ const ui_tick = new Auto_Observable_UI({
28
+ context,
29
+ url: '/api/tick',
30
+ class: 'param-ui'
31
+ });
32
+ container.add(ui_tick);
33
+
34
+ // 2. CPU Load (Gauge)
35
+ const ui_cpu = new Auto_Observable_UI({
36
+ context,
37
+ url: '/api/cpu',
38
+ class: 'param-ui'
39
+ });
40
+ container.add(ui_cpu);
41
+
42
+ // 3. System Logs (Log)
43
+ const ui_log = new Auto_Observable_UI({
44
+ context,
45
+ url: '/api/logs',
46
+ class: 'param-ui span-2'
47
+ });
48
+ container.add(ui_log);
49
+ };
50
+
51
+ if (!spec.el) {
52
+ compose();
53
+ }
54
+ }
55
+ }
56
+
57
+ Demo_Page.css = `
58
+ body {
59
+ background: #222;
60
+ color: #eee;
61
+ font-family: sans-serif;
62
+ padding: 20px;
63
+ }
64
+ .params-container {
65
+ display: grid;
66
+ grid-template-columns: 1fr 1fr;
67
+ gap: 20px;
68
+ max-width: 800px;
69
+ }
70
+ .param-ui {
71
+ background: #333;
72
+ padding: 15px;
73
+ border-radius: 8px;
74
+ box-shadow: 0 2px 10px rgba(0,0,0,0.5);
75
+ }
76
+ .span-2 {
77
+ grid-column: span 2;
78
+ }
79
+ .value-display {
80
+ text-align: center;
81
+ }
82
+ .value-text {
83
+ font-size: 3em;
84
+ font-weight: bold;
85
+ color: #4facfe;
86
+ }
87
+ .progress-bg {
88
+ background: #444;
89
+ height: 20px;
90
+ border-radius: 10px;
91
+ overflow: hidden;
92
+ margin: 10px 0;
93
+ }
94
+ .progress-fill {
95
+ background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
96
+ height: 100%;
97
+ width: 0%;
98
+ transition: width 0.3s ease;
99
+ }
100
+ .log-area {
101
+ height: 150px;
102
+ background: #111;
103
+ overflow-y: auto;
104
+ text-align: left;
105
+ padding: 10px;
106
+ font-family: monospace;
107
+ font-size: 0.9em;
108
+ border: 1px solid #444;
109
+ }
110
+ .log-entry {
111
+ margin-bottom: 4px;
112
+ border-bottom: 1px solid #222;
113
+ }
114
+ .status-indicator {
115
+ font-size: 0.8em;
116
+ text-transform: uppercase;
117
+ margin-bottom: 5px;
118
+ color: #888;
119
+ }
120
+ .status-connected { color: #0f0; }
121
+ .status-error { color: #f00; }
122
+ `;
123
+
124
+ module.exports = jsgui;
125
+ controls.Demo_Page = Demo_Page;
@@ -0,0 +1,64 @@
1
+ const jsgui = require('./client');
2
+ const Server = require('../../../server');
3
+ const { Demo_Page } = jsgui.controls;
4
+ const { observable } = require('fnl');
5
+
6
+ if (require.main === module) {
7
+ const server = new Server({
8
+ Ctrl: Demo_Page,
9
+ src_path_client_js: require.resolve('./client.js')
10
+ });
11
+
12
+ server.on('ready', () => {
13
+ // 1. Tick Observable
14
+ const obs_tick = observable((next) => {
15
+ let i = 0;
16
+ const timer = setInterval(() => next(++i), 1000);
17
+ return () => clearInterval(timer);
18
+ });
19
+ // Attach schema manually (or could be done via helper)
20
+ obs_tick.schema = {
21
+ name: 'Uptime (Seconds)',
22
+ type: 'int'
23
+ };
24
+ server.publish_observable('/api/tick', obs_tick);
25
+
26
+ // 2. CPU Observable (Simulated)
27
+ const obs_cpu = observable((next) => {
28
+ const timer = setInterval(() => {
29
+ const load = 40 + Math.random() * 30;
30
+ next(Math.floor(load));
31
+ }, 500);
32
+ return () => clearInterval(timer);
33
+ });
34
+ obs_cpu.schema = {
35
+ name: 'CPU Load',
36
+ type: 'number',
37
+ min: 0,
38
+ max: 100
39
+ };
40
+ server.publish_observable('/api/cpu', obs_cpu);
41
+
42
+ // 3. Logs Observable
43
+ const obs_logs = observable((next) => {
44
+ const msgs = ['System check OK', 'Request received', 'Cache invalidated', 'User login', 'Background job started'];
45
+ const timer = setInterval(() => {
46
+ const msg = msgs[Math.floor(Math.random() * msgs.length)];
47
+ next({ message: msg, level: 'info' });
48
+ }, 2000);
49
+ return () => clearInterval(timer);
50
+ });
51
+ obs_logs.schema = {
52
+ name: 'Server Logs',
53
+ type: 'log'
54
+ };
55
+ server.publish_observable('/api/logs', obs_logs);
56
+
57
+ // Start
58
+ const port = 52100;
59
+ server.start(port, (err) => {
60
+ if (err) throw err;
61
+ console.log(`Demo running at http://localhost:${port}`);
62
+ });
63
+ });
64
+ }
package/package.json CHANGED
@@ -10,8 +10,8 @@
10
10
  "esbuild": "^0.27.1",
11
11
  "fnl": "^0.0.37",
12
12
  "fnlfs": "^0.0.34",
13
- "jsgui3-client": "^0.0.128",
14
- "jsgui3-html": "^0.0.179",
13
+ "jsgui3-client": "^0.0.129",
14
+ "jsgui3-html": "^0.0.180",
15
15
  "jsgui3-webpage": "^0.0.8",
16
16
  "jsgui3-website": "^0.0.8",
17
17
  "lang-tools": "^0.0.44",
@@ -43,7 +43,7 @@
43
43
  "type": "git",
44
44
  "url": "https://github.com/metabench/jsgui3-server.git"
45
45
  },
46
- "version": "0.0.147",
46
+ "version": "0.0.148",
47
47
  "scripts": {
48
48
  "cli": "node cli.js",
49
49
  "serve": "node cli.js serve",
@@ -27,6 +27,9 @@ class Observable_Publisher extends HTTP_Publisher {
27
27
  this.type = 'observable';
28
28
  this.obs = obs;
29
29
 
30
+ // Expose schema if provided (either in spec or on the observable itself)
31
+ this.schema = spec.schema || obs.schema || null;
32
+
30
33
  this.keep_alive_interval_ms = (spec && spec.keep_alive_interval_ms !== undefined)
31
34
  ? spec.keep_alive_interval_ms
32
35
  : default_keep_alive_interval_ms;
@@ -301,6 +304,11 @@ class Observable_Publisher extends HTTP_Publisher {
301
304
  this._write_sse(res, `event: paused\ndata: ${this._stringify_sse_data({ status: 'paused' })}\n\n`);
302
305
  }
303
306
 
307
+ // Send schema if available
308
+ if (this.schema) {
309
+ this._write_sse(res, `event: schema\ndata: ${this._stringify_sse_data(this.schema)}\n\n`);
310
+ }
311
+
304
312
  let removed = false;
305
313
  const remove_connection = () => {
306
314
  if (removed) return;
@@ -72,9 +72,19 @@ class HTTP_Webpage_Publisher extends HTTP_Webpageorsite_Publisher {
72
72
  bundler_config: this.bundler_config
73
73
  });
74
74
  (async () => {
75
- const res_get_ready = await this.get_ready();
76
- this.raise('ready', res_get_ready);
77
-
75
+ try {
76
+ const res_get_ready = await this.get_ready();
77
+ this.raise('ready', res_get_ready);
78
+ } catch (e) {
79
+ console.error('HTTP_Webpage_Publisher error: Failed to get ready (bundling or preparation failed).');
80
+ console.error(e);
81
+ // Can't just ignore it if it means the server won't handle requests correctly,
82
+ // but at least it won't crash the whole process.
83
+ // We might want to emit an error event.
84
+ this.raise('error', e);
85
+ // Also raise 'ready' so the pool/server can continue starting up other things
86
+ this.raise('ready', {});
87
+ }
78
88
  })();
79
89
 
80
90
  }
@@ -162,6 +162,24 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
162
162
 
163
163
  console.log('[HTTP_Webpageorsite_Publisher] get_ready called, src_path_client_js:', src_path_client_js);
164
164
 
165
+ // Defensive programming: Handle missing client JS path
166
+ if (!src_path_client_js) {
167
+ console.warn('[HTTP_Webpageorsite_Publisher] No src_path_client_js provided. Returning empty bundle.');
168
+ const bundle = new Bundle();
169
+ // Could add empty CSS/JS placeholders if needed to prevent errors downstream
170
+ bundle.push({
171
+ type: 'CSS',
172
+ extension: 'css',
173
+ text: '/* No client CSS - Server Start Mode */'
174
+ });
175
+ bundle.push({
176
+ type: 'JavaScript',
177
+ extension: 'js',
178
+ text: '/* No client JS - Server Start Mode */'
179
+ });
180
+ return bundle;
181
+ }
182
+
165
183
  // Skip bundling if no client.js path is provided
166
184
  // This allows Server.serve() to work with SSR-only controls
167
185
  if (!src_path_client_js) {