jsgui3-server 0.0.146 → 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.
- package/README.md +16 -0
- package/admin-ui/client.js +213 -0
- package/admin-ui/server.js +104 -0
- package/client/controls/auto-observable.js +207 -0
- package/docs/agent-development-guide.md +92 -9
- package/docs/books/admin-ui/01-introduction.md +32 -0
- package/docs/books/admin-ui/02-architecture.md +92 -0
- package/docs/books/admin-ui/03-controls.md +194 -0
- package/docs/books/admin-ui/04-implementation-plan.md +62 -0
- package/docs/books/admin-ui/README.md +26 -0
- package/docs/books/jsgui3-mvvm-fullstack/00-overview.md +17 -0
- package/docs/books/jsgui3-mvvm-fullstack/01-stack-map.md +25 -0
- package/docs/books/jsgui3-mvvm-fullstack/02-control-lifecycle.md +32 -0
- package/docs/books/jsgui3-mvvm-fullstack/03-mvvm-basics.md +57 -0
- package/docs/books/jsgui3-mvvm-fullstack/04-bindings-and-validation.md +51 -0
- package/docs/books/jsgui3-mvvm-fullstack/05-full-stack-example.md +50 -0
- package/docs/books/jsgui3-mvvm-fullstack/06-testing.md +23 -0
- package/docs/books/jsgui3-mvvm-fullstack/07-troubleshooting.md +20 -0
- package/docs/books/jsgui3-mvvm-fullstack/08-agent-playbooks.md +30 -0
- package/docs/books/jsgui3-mvvm-fullstack/README.md +30 -0
- package/docs/comprehensive-documentation.md +145 -34
- package/docs/diagrams/jsgui3-progress-update.svg +181 -0
- package/examples/controls/18) window, observable bindRemote/README.md +28 -0
- package/examples/controls/18) window, observable bindRemote/check.js +144 -0
- package/examples/controls/18) window, observable bindRemote/client.js +387 -0
- package/examples/controls/18) window, observable bindRemote/server.js +107 -0
- package/examples/controls/19) window, auto observable ui/client.js +125 -0
- package/examples/controls/19) window, auto observable ui/server.js +64 -0
- package/package.json +4 -4
- package/publishers/http-observable-publisher.js +8 -0
- package/publishers/http-webpage-publisher.js +52 -19
- package/publishers/http-webpageorsite-publisher.js +65 -34
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +54 -32
- package/server.js +270 -187
- package/tests/server-publish-observable.test.js +99 -0
package/README.md
CHANGED
|
@@ -72,6 +72,22 @@ Server.serve({
|
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Real-time SSE Streams
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const { observable } = require('fnl');
|
|
79
|
+
// Create an infinite stream that ticks every second
|
|
80
|
+
const obs = observable(next => {
|
|
81
|
+
setInterval(() => next({ tick: Date.now() }), 1000);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Start server and publish stream
|
|
85
|
+
const server = await Server.serve(MyControl);
|
|
86
|
+
server.publish_observable('/api/stream', obs);
|
|
87
|
+
// Server publishes SSE stream at http://localhost:3000/api/stream
|
|
88
|
+
// Client connects via EventSource or Remote_Observable
|
|
89
|
+
```
|
|
90
|
+
|
|
75
91
|
> **Note:** The new `Server.serve()` API is the recommended approach for most use cases. See [Simple Server API Design](docs/simple-server-api-design.md) for complete documentation and advanced features.
|
|
76
92
|
|
|
77
93
|
## Architecture Overview
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const jsgui = require('jsgui3-client');
|
|
2
|
+
const { controls, Control, mixins } = jsgui;
|
|
3
|
+
const Active_HTML_Document = require('../controls/Active_HTML_Document');
|
|
4
|
+
|
|
5
|
+
class Admin_Page extends Active_HTML_Document {
|
|
6
|
+
constructor(spec = {}) {
|
|
7
|
+
spec.__type_name = spec.__type_name || 'admin_page';
|
|
8
|
+
super(spec);
|
|
9
|
+
const { context } = this;
|
|
10
|
+
|
|
11
|
+
if (typeof this.body.add_class === 'function') {
|
|
12
|
+
this.body.add_class('admin-page');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const compose = () => {
|
|
16
|
+
// Sidebar
|
|
17
|
+
const sidebar = new controls.div({ context, class: 'admin-sidebar' });
|
|
18
|
+
this.body.add(sidebar);
|
|
19
|
+
this.sidebar = sidebar;
|
|
20
|
+
|
|
21
|
+
// Sidebar Header
|
|
22
|
+
const brand = new controls.div({ context, class: 'admin-brand' });
|
|
23
|
+
brand.add(new controls.span({ context, class: 'brand-icon' }).add('⚙️'));
|
|
24
|
+
brand.add(new controls.span({ context, class: 'brand-text' }).add('jsgui3 Admin'));
|
|
25
|
+
sidebar.add(brand);
|
|
26
|
+
|
|
27
|
+
// Menu Container (Placeholder for Resource_List and Observables_List)
|
|
28
|
+
const menu = new controls.div({ context, class: 'admin-menu' });
|
|
29
|
+
sidebar.add(menu);
|
|
30
|
+
this.menu = menu;
|
|
31
|
+
|
|
32
|
+
this._add_menu_item('Overview', 'overview', true);
|
|
33
|
+
this._add_menu_item('Resources', 'resources');
|
|
34
|
+
this._add_menu_item('Observables', 'observables');
|
|
35
|
+
this._add_menu_item('Settings', 'settings');
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// Main Content Area
|
|
39
|
+
const main = new controls.div({ context, class: 'admin-main' });
|
|
40
|
+
this.body.add(main);
|
|
41
|
+
this.main = main;
|
|
42
|
+
|
|
43
|
+
// Top Bar
|
|
44
|
+
const top_bar = new controls.div({ context, class: 'admin-top-bar' });
|
|
45
|
+
main.add(top_bar);
|
|
46
|
+
|
|
47
|
+
// Breadcrumbs / Title
|
|
48
|
+
this.page_title = new controls.h2({ context, class: 'page-title' });
|
|
49
|
+
this.page_title.add('Overview');
|
|
50
|
+
top_bar.add(this.page_title);
|
|
51
|
+
|
|
52
|
+
// Content Panel
|
|
53
|
+
const content = new controls.div({ context, class: 'admin-content' });
|
|
54
|
+
main.add(content);
|
|
55
|
+
this.content = content;
|
|
56
|
+
|
|
57
|
+
// Default content (Overview)
|
|
58
|
+
this._render_overview();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!spec.el) {
|
|
62
|
+
compose();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_add_menu_item(label, id, active = false) {
|
|
67
|
+
const item = new controls.div({
|
|
68
|
+
context: this.context,
|
|
69
|
+
class: `menu-item ${active ? 'active' : ''}`
|
|
70
|
+
});
|
|
71
|
+
item.dom.attributes['data-id'] = id;
|
|
72
|
+
item.add(label);
|
|
73
|
+
this.menu.add(item);
|
|
74
|
+
return item;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_render_overview() {
|
|
78
|
+
// Clear main content area (this.content is the admin-content div)
|
|
79
|
+
if (this.content && this.content.content && typeof this.content.content.clear === 'function') {
|
|
80
|
+
this.content.content.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const welcome = new controls.div({ context: this.context, class: 'welcome-card' });
|
|
84
|
+
welcome.add(new controls.h3({ context: this.context }).add('Welcome directly to jsgui3-server Admin'));
|
|
85
|
+
welcome.add(new controls.p({ context: this.context }).add('Select a resource from the sidebar to inspect.'));
|
|
86
|
+
this.content.add(welcome);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
activate() {
|
|
90
|
+
if (!this.__active) {
|
|
91
|
+
super.activate();
|
|
92
|
+
|
|
93
|
+
// Handle menu clicks
|
|
94
|
+
const menu_items = document.querySelectorAll('.menu-item');
|
|
95
|
+
menu_items.forEach(el => {
|
|
96
|
+
el.addEventListener('click', () => {
|
|
97
|
+
// Update Active State
|
|
98
|
+
menu_items.forEach(i => i.classList.remove('active'));
|
|
99
|
+
el.classList.add('active');
|
|
100
|
+
|
|
101
|
+
// Update Title
|
|
102
|
+
const id = el.getAttribute('data-id');
|
|
103
|
+
const label = el.innerText;
|
|
104
|
+
document.querySelector('.page-title').innerText = label;
|
|
105
|
+
|
|
106
|
+
// Placeholder navigation logic
|
|
107
|
+
console.log('Navigate to:', id);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Admin_Page.css = `
|
|
115
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
116
|
+
body {
|
|
117
|
+
background: #1a1a2e;
|
|
118
|
+
color: #e0e0e0;
|
|
119
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
120
|
+
height: 100vh;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
}
|
|
123
|
+
.admin-page {
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-columns: 260px 1fr;
|
|
126
|
+
height: 100%;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Sidebar */
|
|
130
|
+
.admin-sidebar {
|
|
131
|
+
background: #16213e;
|
|
132
|
+
border-right: 1px solid #2a2a4a;
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
}
|
|
136
|
+
.admin-brand {
|
|
137
|
+
padding: 20px;
|
|
138
|
+
font-size: 1.2rem;
|
|
139
|
+
font-weight: bold;
|
|
140
|
+
color: #fff;
|
|
141
|
+
border-bottom: 1px solid #2a2a4a;
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 10px;
|
|
145
|
+
}
|
|
146
|
+
.brand-icon { filter: drop-shadow(0 0 5px rgba(255,255,255,0.3)); }
|
|
147
|
+
|
|
148
|
+
.admin-menu {
|
|
149
|
+
flex: 1;
|
|
150
|
+
padding: 20px 0;
|
|
151
|
+
overflow-y: auto;
|
|
152
|
+
}
|
|
153
|
+
.menu-item {
|
|
154
|
+
padding: 12px 24px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
border-left: 3px solid transparent;
|
|
157
|
+
transition: all 0.2s;
|
|
158
|
+
color: #a0a0b0;
|
|
159
|
+
}
|
|
160
|
+
.menu-item:hover {
|
|
161
|
+
background: rgba(255,255,255,0.05);
|
|
162
|
+
color: #fff;
|
|
163
|
+
}
|
|
164
|
+
.menu-item.active {
|
|
165
|
+
background: rgba(79, 172, 254, 0.1);
|
|
166
|
+
color: #4facfe;
|
|
167
|
+
border-left-color: #4facfe;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Main Area */
|
|
171
|
+
.admin-main {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
background: #1a1a2e;
|
|
175
|
+
}
|
|
176
|
+
.admin-top-bar {
|
|
177
|
+
height: 64px;
|
|
178
|
+
border-bottom: 1px solid #2a2a4a;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
padding: 0 30px;
|
|
182
|
+
background: #16213e;
|
|
183
|
+
}
|
|
184
|
+
.page-title {
|
|
185
|
+
font-size: 1.2rem;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
color: #fff;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.admin-content {
|
|
191
|
+
flex: 1;
|
|
192
|
+
overflow-y: auto;
|
|
193
|
+
padding: 30px;
|
|
194
|
+
position: relative;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Cards */
|
|
198
|
+
.welcome-card {
|
|
199
|
+
background: #2a2a4a;
|
|
200
|
+
padding: 40px;
|
|
201
|
+
border-radius: 12px;
|
|
202
|
+
text-align: center;
|
|
203
|
+
max-width: 600px;
|
|
204
|
+
margin: 40px auto;
|
|
205
|
+
border: 1px solid #3a3a5a;
|
|
206
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
|
207
|
+
}
|
|
208
|
+
.welcome-card h3 { color: #fff; margin-bottom: 15px; }
|
|
209
|
+
.welcome-card p { color: #a0a0b0; }
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
controls.Admin_Page = Admin_Page;
|
|
213
|
+
module.exports = jsgui;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const jsgui = require('./client');
|
|
2
|
+
const { Admin_Page } = jsgui.controls;
|
|
3
|
+
const { each } = jsgui;
|
|
4
|
+
|
|
5
|
+
class Admin_Module {
|
|
6
|
+
constructor(server) {
|
|
7
|
+
this.server = server;
|
|
8
|
+
this.setup_routes();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setup_routes() {
|
|
12
|
+
const { server } = this;
|
|
13
|
+
|
|
14
|
+
// 1. Main Admin Page Route
|
|
15
|
+
// Using a custom Website_Resource for the admin app
|
|
16
|
+
// We register Admin_Page as the content control
|
|
17
|
+
|
|
18
|
+
// Manual route setup to render the page
|
|
19
|
+
// (Similar to how examples work, but integrated)
|
|
20
|
+
|
|
21
|
+
// We'll expose a 'setup' method that the main server calls
|
|
22
|
+
// Or we can attach directly if we have access to the router
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Called by the main server to attach admin functionality
|
|
26
|
+
attach_to_router(router) {
|
|
27
|
+
console.log('[Admin_Module] Attaching /admin routes...');
|
|
28
|
+
|
|
29
|
+
// API: List Resources
|
|
30
|
+
// GET /api/admin/resources
|
|
31
|
+
router.set_route('/api/admin/resources', (req, res) => {
|
|
32
|
+
const resources_data = this.get_resources_tree();
|
|
33
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
34
|
+
res.end(JSON.stringify(resources_data));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// API: List Observables
|
|
38
|
+
// GET /api/admin/observables
|
|
39
|
+
router.set_route('/api/admin/observables', (req, res) => {
|
|
40
|
+
const observables = this.get_observables_list();
|
|
41
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
42
|
+
res.end(JSON.stringify(observables));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get_resources_tree() {
|
|
47
|
+
const pool = this.server.resource_pool;
|
|
48
|
+
const tree = {
|
|
49
|
+
name: 'Root',
|
|
50
|
+
type: 'pool',
|
|
51
|
+
children: []
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (pool && pool.resources) {
|
|
55
|
+
// Need to iterate pool resources safely
|
|
56
|
+
// resource_pool.resources is likely a Collection or array
|
|
57
|
+
const resources = pool.resources._arr || []; // Assuming Data_Structures.Collection
|
|
58
|
+
|
|
59
|
+
resources.forEach(res => {
|
|
60
|
+
tree.children.push({
|
|
61
|
+
name: res.name || 'Unnamed Resource',
|
|
62
|
+
type: res.constructor.name
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return tree;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get_observables_list() {
|
|
70
|
+
// We need a way to track all published observables
|
|
71
|
+
// Ideally, HTTP_Observable_Publisher instances are stored in the resource pool or a specific list
|
|
72
|
+
|
|
73
|
+
// For now, we'll scan the router for observable publishers
|
|
74
|
+
// This relies on the server exposing its router/routes map
|
|
75
|
+
const observables = [];
|
|
76
|
+
|
|
77
|
+
// Implementation detail: server.router doesn't expose a simple list of routes easily in standard Router
|
|
78
|
+
// We might need to track them when publish_observable is called.
|
|
79
|
+
// But we can look at server.resource_pool for publishers
|
|
80
|
+
|
|
81
|
+
const pool = this.server.resource_pool;
|
|
82
|
+
if (pool && pool.resources) {
|
|
83
|
+
const resources = pool.resources._arr || [];
|
|
84
|
+
resources.forEach(res => {
|
|
85
|
+
// Check if it's an observable publisher
|
|
86
|
+
// Robust check: does it have 'obs' property and handle_http?
|
|
87
|
+
// Or check constructor name if available
|
|
88
|
+
if (res.constructor.name === 'Observable_Publisher' || (res.obs && res.handle_http)) {
|
|
89
|
+
observables.push({
|
|
90
|
+
name: res.name || 'Observable',
|
|
91
|
+
route: '?', // Route might not be stored on the resource itself, but on the router
|
|
92
|
+
schema: res.schema,
|
|
93
|
+
status: res.is_paused ? 'paused' : 'active',
|
|
94
|
+
connections: res.active_sse_connections ? res.active_sse_connections.size : 0
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return observables;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = Admin_Module;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const jsgui = require('jsgui3-client');
|
|
2
|
+
const { Control, controls } = jsgui;
|
|
3
|
+
|
|
4
|
+
class Auto_Observable_UI extends Control {
|
|
5
|
+
constructor(spec = {}) {
|
|
6
|
+
spec.__type_name = spec.__type_name || 'auto_observable_ui';
|
|
7
|
+
super(spec);
|
|
8
|
+
this.add_class('auto-observable-ui');
|
|
9
|
+
|
|
10
|
+
this.url = spec.url;
|
|
11
|
+
this.obs = spec.obs;
|
|
12
|
+
|
|
13
|
+
// Container for the dynamic content
|
|
14
|
+
this.content_container = new controls.div({
|
|
15
|
+
context: this.context,
|
|
16
|
+
class: 'content-container'
|
|
17
|
+
});
|
|
18
|
+
this.add(this.content_container);
|
|
19
|
+
|
|
20
|
+
// Status indicator
|
|
21
|
+
this.status_indicator = new controls.div({
|
|
22
|
+
context: this.context,
|
|
23
|
+
class: 'status-indicator status-connecting'
|
|
24
|
+
});
|
|
25
|
+
this.add(this.status_indicator);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
activate() {
|
|
29
|
+
if (!this.__active) {
|
|
30
|
+
super.activate();
|
|
31
|
+
this._connect();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_connect() {
|
|
36
|
+
let obs = this.obs;
|
|
37
|
+
if (!obs && this.url) {
|
|
38
|
+
obs = new jsgui.Remote_Observable({ url: this.url });
|
|
39
|
+
this.obs = obs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!obs) return;
|
|
43
|
+
|
|
44
|
+
obs.on('connect', () => {
|
|
45
|
+
this.status_indicator.remove_class('status-connecting');
|
|
46
|
+
this.status_indicator.add_class('status-connected');
|
|
47
|
+
this.status_indicator.dom.innerText = 'Connected';
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
obs.on('schema', (schema) => {
|
|
51
|
+
console.log('Schema received:', schema);
|
|
52
|
+
this._build_ui_from_schema(schema);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
obs.on('next', (data) => {
|
|
56
|
+
// If we haven't received a schema yet, maybe infer it or just display raw?
|
|
57
|
+
// For now, if we built the UI, we update it.
|
|
58
|
+
if (this._update_handler) {
|
|
59
|
+
this._update_handler(data);
|
|
60
|
+
} else if (!this._ui_built) {
|
|
61
|
+
// Infer simple schema if not provided?
|
|
62
|
+
// Or just show raw JSON
|
|
63
|
+
this._build_default_ui(data);
|
|
64
|
+
this._ui_built = true;
|
|
65
|
+
if (this._update_handler) this._update_handler(data);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
obs.on('error', (err) => {
|
|
70
|
+
this.status_indicator.add_class('status-error');
|
|
71
|
+
this.status_indicator.dom.innerText = 'Error: ' + err.message;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
obs.connect();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_build_ui_from_schema(schema) {
|
|
78
|
+
if (this._ui_built) return; // Don't rebuild for now
|
|
79
|
+
this._ui_built = true;
|
|
80
|
+
|
|
81
|
+
this.content_container.content.clear();
|
|
82
|
+
|
|
83
|
+
const type = schema.type || (schema.output_type ? schema.output_type : 'unknown');
|
|
84
|
+
|
|
85
|
+
if (type === 'int' || type === 'number') {
|
|
86
|
+
this._build_number_ui(schema);
|
|
87
|
+
} else if (type === 'text' || type === 'log') {
|
|
88
|
+
this._build_log_ui(schema);
|
|
89
|
+
} else if (type === 'percentage' || (type === 'number' && schema.min !== undefined && schema.max !== undefined)) {
|
|
90
|
+
this._build_gauge_ui(schema);
|
|
91
|
+
} else {
|
|
92
|
+
this._build_json_ui(schema);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_build_default_ui(first_data) {
|
|
97
|
+
if (typeof first_data === 'number') {
|
|
98
|
+
this._build_number_ui({ type: 'number' });
|
|
99
|
+
} else if (typeof first_data === 'string') {
|
|
100
|
+
this._build_log_ui({ type: 'text' });
|
|
101
|
+
} else {
|
|
102
|
+
this._build_json_ui({ type: 'object' });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_build_number_ui(schema) {
|
|
107
|
+
const val_div = new controls.div({
|
|
108
|
+
context: this.context,
|
|
109
|
+
class: 'value-display number-display'
|
|
110
|
+
});
|
|
111
|
+
this.content_container.add(val_div);
|
|
112
|
+
|
|
113
|
+
const label = new controls.h3({ context: this.context });
|
|
114
|
+
label.add(schema.name || 'Value');
|
|
115
|
+
val_div.add(label);
|
|
116
|
+
|
|
117
|
+
const value_text = new controls.div({ context: this.context, class: 'value-text' });
|
|
118
|
+
value_text.add('--');
|
|
119
|
+
val_div.add(value_text);
|
|
120
|
+
|
|
121
|
+
this._update_handler = (data) => {
|
|
122
|
+
let val = data;
|
|
123
|
+
if (typeof data === 'object' && data.value !== undefined) val = data.value;
|
|
124
|
+
value_text.dom.innerText = String(val);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_build_log_ui(schema) {
|
|
129
|
+
const log_container = new controls.div({
|
|
130
|
+
context: this.context,
|
|
131
|
+
class: 'value-display log-display'
|
|
132
|
+
});
|
|
133
|
+
this.content_container.add(log_container);
|
|
134
|
+
|
|
135
|
+
const label = new controls.h3({ context: this.context });
|
|
136
|
+
label.add(schema.name || 'Log');
|
|
137
|
+
log_container.add(label);
|
|
138
|
+
|
|
139
|
+
const log_area = new controls.div({ context: this.context, class: 'log-area' });
|
|
140
|
+
log_container.add(log_area);
|
|
141
|
+
|
|
142
|
+
this._update_handler = (data) => {
|
|
143
|
+
const entry = document.createElement('div');
|
|
144
|
+
entry.className = 'log-entry';
|
|
145
|
+
let msg = data;
|
|
146
|
+
if (typeof data === 'object') msg = data.message || JSON.stringify(data);
|
|
147
|
+
|
|
148
|
+
entry.innerText = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
|
149
|
+
log_area.dom.appendChild(entry);
|
|
150
|
+
log_area.dom.scrollTop = log_area.dom.scrollHeight;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_build_gauge_ui(schema) {
|
|
155
|
+
// Simple progress bar for now
|
|
156
|
+
const container = new controls.div({
|
|
157
|
+
context: this.context,
|
|
158
|
+
class: 'value-display gauge-display'
|
|
159
|
+
});
|
|
160
|
+
this.content_container.add(container);
|
|
161
|
+
|
|
162
|
+
const label = new controls.h3({ context: this.context });
|
|
163
|
+
label.add(schema.name || 'Gauge');
|
|
164
|
+
container.add(label);
|
|
165
|
+
|
|
166
|
+
const bar_bg = new controls.div({ context: this.context, class: 'progress-bg' });
|
|
167
|
+
const bar_fill = new controls.div({ context: this.context, class: 'progress-fill' });
|
|
168
|
+
bar_bg.add(bar_fill);
|
|
169
|
+
container.add(bar_bg);
|
|
170
|
+
|
|
171
|
+
const value_text = new controls.div({ context: this.context, class: 'value-text-small' });
|
|
172
|
+
container.add(value_text);
|
|
173
|
+
|
|
174
|
+
const min = schema.min || 0;
|
|
175
|
+
const max = schema.max || 100;
|
|
176
|
+
|
|
177
|
+
this._update_handler = (data) => {
|
|
178
|
+
let val = data;
|
|
179
|
+
if (typeof data === 'object' && data.value !== undefined) val = data.value;
|
|
180
|
+
|
|
181
|
+
const pct = Math.max(0, Math.min(100, ((val - min) / (max - min)) * 100));
|
|
182
|
+
bar_fill.dom.style.width = pct + '%';
|
|
183
|
+
value_text.dom.innerText = `${val} / ${max}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_build_json_ui(schema) {
|
|
188
|
+
const container = new controls.div({
|
|
189
|
+
context: this.context,
|
|
190
|
+
class: 'value-display json-display'
|
|
191
|
+
});
|
|
192
|
+
this.content_container.add(container);
|
|
193
|
+
|
|
194
|
+
const label = new controls.h3({ context: this.context });
|
|
195
|
+
label.add(schema.name || 'Data');
|
|
196
|
+
container.add(label);
|
|
197
|
+
|
|
198
|
+
const pre = new controls.pre({ context: this.context });
|
|
199
|
+
container.add(pre);
|
|
200
|
+
|
|
201
|
+
this._update_handler = (data) => {
|
|
202
|
+
pre.dom.innerText = JSON.stringify(data, null, 2);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = Auto_Observable_UI;
|
|
@@ -26,14 +26,97 @@ This document is specifically for AI agents working on the JSGUI3 Server codebas
|
|
|
26
26
|
- Include comprehensive error handling and logging
|
|
27
27
|
- Add JSDoc comments for public APIs
|
|
28
28
|
|
|
29
|
-
### Development Workflow
|
|
30
|
-
1. **Analyze the task** and understand requirements
|
|
31
|
-
2. **Check existing code** for patterns and conventions
|
|
32
|
-
3. **Implement changes** following established patterns
|
|
33
|
-
4. **Test thoroughly** and document any issues found
|
|
34
|
-
5. **Update documentation** including this guide
|
|
35
|
-
|
|
36
|
-
##
|
|
29
|
+
### Development Workflow
|
|
30
|
+
1. **Analyze the task** and understand requirements
|
|
31
|
+
2. **Check existing code** for patterns and conventions
|
|
32
|
+
3. **Implement changes** following established patterns
|
|
33
|
+
4. **Test thoroughly** and document any issues found
|
|
34
|
+
5. **Update documentation** including this guide
|
|
35
|
+
|
|
36
|
+
## Quick Start for AI Agents (Low Thinking Time)
|
|
37
|
+
|
|
38
|
+
Use this section when you need to act fast with minimal deliberation. It is a short, repeatable workflow designed to reduce cognitive overhead.
|
|
39
|
+
|
|
40
|
+
### Fast Lane Checklist (5 Minutes)
|
|
41
|
+
1. **Read the task** and extract the target file, example, or subsystem.
|
|
42
|
+
2. **Locate the target** with `rg` or `rg --files`.
|
|
43
|
+
3. **Open the closest reference doc** from the map below.
|
|
44
|
+
4. **Make a minimal, reversible change** that addresses the task.
|
|
45
|
+
5. **Run the narrowest test** that validates the change.
|
|
46
|
+
6. **Log any gaps** in "Known Issues" below if you find a blocker.
|
|
47
|
+
|
|
48
|
+
### Quick Doc Map
|
|
49
|
+
- **Repo overview**: `README.md`
|
|
50
|
+
- **Testing workflow**: `tests/README.md`
|
|
51
|
+
- **Bundling & assets**: `docs/bundling-system-deep-dive.md`
|
|
52
|
+
- **Server publishing**: `docs/publishers-guide.md`
|
|
53
|
+
- **Troubleshooting**: `docs/troubleshooting.md`
|
|
54
|
+
- **MVVM + full-stack controls**: `docs/books/jsgui3-mvvm-fullstack/README.md`
|
|
55
|
+
- **Agent workflow depth**: `docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md`
|
|
56
|
+
|
|
57
|
+
### Common Task Playbooks
|
|
58
|
+
|
|
59
|
+
#### 1) Fix an Example or Control
|
|
60
|
+
**Goal**: Example renders correctly with expected controls.
|
|
61
|
+
1. Open `examples/.../client.js` and check for `Active_HTML_Document`.
|
|
62
|
+
2. Ensure composition is inside `if (!spec.el)`.
|
|
63
|
+
3. Confirm the control is exported: `controls.Demo_UI = Demo_UI`.
|
|
64
|
+
4. Run the focused test: `node tests/test-runner.js --test=examples-controls.e2e.test.js`.
|
|
65
|
+
5. If the error is render-time, reproduce with `Server_Static_Page_Context`.
|
|
66
|
+
|
|
67
|
+
**Quick probe**:
|
|
68
|
+
```js
|
|
69
|
+
const Server_Static_Page_Context = require('./static-page-context');
|
|
70
|
+
const jsgui = require('./examples/controls/1) window/client.js');
|
|
71
|
+
const Ctrl = jsgui.controls.Demo_UI;
|
|
72
|
+
const ctrl = new Ctrl({ context: new Server_Static_Page_Context() });
|
|
73
|
+
const html = ctrl.all_html_render();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 2) Investigate a Bundling Failure
|
|
77
|
+
**Goal**: Bundler completes and emits JS/CSS routes.
|
|
78
|
+
1. Find bundler class in `resources/processors/bundlers`.
|
|
79
|
+
2. Verify `complete(...)` is called on success and error paths.
|
|
80
|
+
3. Run: `node tests/test-runner.js --test=bundlers.test.js`.
|
|
81
|
+
4. If compression fails, check `docs/troubleshooting.md`.
|
|
82
|
+
|
|
83
|
+
#### 3) Add or Extend Tests
|
|
84
|
+
**Goal**: Increase coverage without breaking existing flows.
|
|
85
|
+
1. Add new tests under `tests/` with smallest scope.
|
|
86
|
+
2. Use snake_case names and minimal fixtures.
|
|
87
|
+
3. Run only the new test: `node tests/test-runner.js --test=your-test-file.test.js`.
|
|
88
|
+
4. Update `tests/README.md` if workflow changes.
|
|
89
|
+
|
|
90
|
+
#### 4) Update or Add Documentation
|
|
91
|
+
**Goal**: Keep agents and developers aligned on current behavior.
|
|
92
|
+
1. Add short, high-signal sections (avoid large rewrites).
|
|
93
|
+
2. Include commands and file paths.
|
|
94
|
+
3. For broken behavior, add a "Known Issues" entry here.
|
|
95
|
+
|
|
96
|
+
#### 5) MVVM Binding and Full-Stack Control Usage
|
|
97
|
+
**Goal**: Wire data models to controls and serve them end to end.
|
|
98
|
+
1. Build controls under `client.js` using `Active_HTML_Document`.
|
|
99
|
+
2. Compose controls only when `!spec.el`.
|
|
100
|
+
3. Use `this.data.model` and `this.view.data.model` plus `watch` and `computed`.
|
|
101
|
+
4. Render server-side with `Server_Static_Page_Context` for quick validation.
|
|
102
|
+
5. Serve with `Server.serve` and run `examples-controls.e2e.test.js`.
|
|
103
|
+
|
|
104
|
+
### Rapid Triage Checklist (When Tests Fail)
|
|
105
|
+
1. Identify whether the failure is **render**, **bundle**, **runtime**, or **assertion**.
|
|
106
|
+
2. If render: instantiate with `Server_Static_Page_Context`.
|
|
107
|
+
3. If bundle: inspect esbuild logs and `resources/processors/bundlers/...`.
|
|
108
|
+
4. If runtime: open the example in a browser or puppeteer.
|
|
109
|
+
5. If assertion: validate expected output and update fixtures.
|
|
110
|
+
|
|
111
|
+
### Suggested Minimal Command Set
|
|
112
|
+
```bash
|
|
113
|
+
rg -n "needle" path/
|
|
114
|
+
rg --files path/
|
|
115
|
+
node tests/test-runner.js --test=examples-controls.e2e.test.js
|
|
116
|
+
node tests/test-runner.js --test=window-examples.puppeteer.test.js
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Codebase Structure Overview
|
|
37
120
|
|
|
38
121
|
### Core Architecture
|
|
39
122
|
|
|
@@ -487,4 +570,4 @@ If you discover critical issues:
|
|
|
487
570
|
|
|
488
571
|
---
|
|
489
572
|
|
|
490
|
-
**Remember**: This guide is the central place for agents to document broken functionality and implementation gaps. If you find something broken or incomplete, document it here immediately with details about location, impact, and status.
|
|
573
|
+
**Remember**: This guide is the central place for agents to document broken functionality and implementation gaps. If you find something broken or incomplete, document it here immediately with details about location, impact, and status.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Chapter 1: Introduction
|
|
2
|
+
|
|
3
|
+
## Vision
|
|
4
|
+
|
|
5
|
+
The Admin UI is the **go-to interface** for developers and operators to administer `jsgui3-server` instances. It should be:
|
|
6
|
+
|
|
7
|
+
- **Instantaneous**: Zero setup required; just navigate to `/admin`
|
|
8
|
+
- **Insightful**: Surface the internal state of the server in real-time
|
|
9
|
+
- **Actionable**: Allow common admin tasks directly from the UI
|
|
10
|
+
|
|
11
|
+
## Goals
|
|
12
|
+
|
|
13
|
+
1. **Visibility into Observables**: If a server publishes an observable (e.g., for crawl progress, metrics), the Admin UI should display it automatically with an appropriate control.
|
|
14
|
+
2. **Resource Browser**: List all registered resources (routes, publishers, static paths) with introspection.
|
|
15
|
+
3. **Configuration Panel**: View and modify server configuration (where safe).
|
|
16
|
+
4. **Performance Metrics**: Display throughput, active connections, memory usage.
|
|
17
|
+
|
|
18
|
+
## Inspiration
|
|
19
|
+
|
|
20
|
+
The design is inspired by:
|
|
21
|
+
- Database admin tools (pgAdmin, phpMyAdmin)
|
|
22
|
+
- Monitoring dashboards (Grafana, Kibana)
|
|
23
|
+
- The jsgui3 Window control aesthetic
|
|
24
|
+
|
|
25
|
+
## Target Experience
|
|
26
|
+
|
|
27
|
+
A developer starts a jsgui3-server, then navigates to `http://localhost:PORT/admin`. They immediately see:
|
|
28
|
+
- A sidebar listing all resources
|
|
29
|
+
- A main panel showing the selected resource's details
|
|
30
|
+
- Real-time updates for any observables
|
|
31
|
+
|
|
32
|
+
No extra code required—the Admin UI is built into jsgui3-server and activates automatically.
|