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,586 @@
|
|
|
1
|
+
# Chapter 8: Domain Controls — Route Table & API Explorer
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Route Table displays every HTTP route registered in the server's router. Each row shows the HTTP method (as a colored badge), the path, the handler description, and the route type category. The design reference shows this within a tabbed panel ("Routes & API" tab), alongside a build status sidebar.
|
|
6
|
+
|
|
7
|
+
An API Explorer extension allows developers to test function-published endpoints directly from the admin UI.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Route_Table Control
|
|
12
|
+
|
|
13
|
+
### Spec
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
{
|
|
17
|
+
__type_name: 'route_table',
|
|
18
|
+
routes: [ // Data from /api/admin/routes
|
|
19
|
+
{
|
|
20
|
+
path: '/',
|
|
21
|
+
method: 'GET',
|
|
22
|
+
handler_type: 'Static_Route_HTTP_Responder',
|
|
23
|
+
handler_name: null,
|
|
24
|
+
category: 'auto',
|
|
25
|
+
description: 'renderControl → HTML'
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Visual Anatomy
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
METHOD PATH HANDLER TYPE
|
|
35
|
+
──────────────────────────────────────────────────────────
|
|
36
|
+
[GET ] / renderControl → HTML [auto]
|
|
37
|
+
[GET ] /js/js.js Client JS bundle [auto]
|
|
38
|
+
[GET ] /css/css.css Extracted CSS bundle [auto]
|
|
39
|
+
[POST] /api/validateUser server.publish() [API ]
|
|
40
|
+
[POST] /api/register server.publish() [API ]
|
|
41
|
+
[GET ] /static/* Static file serving [static]
|
|
42
|
+
──────────────────────────────────────────────────────────
|
|
43
|
+
+ 1 additional static route
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Constructor
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
class Route_Table extends jsgui.Control {
|
|
50
|
+
constructor(spec = {}) {
|
|
51
|
+
spec.__type_name = spec.__type_name || 'route_table';
|
|
52
|
+
super(spec);
|
|
53
|
+
const { context } = this;
|
|
54
|
+
this._routes = spec.routes || [];
|
|
55
|
+
|
|
56
|
+
const compose = () => {
|
|
57
|
+
// Table header
|
|
58
|
+
const header = new controls.div({ context, class: 'route-table-header' });
|
|
59
|
+
this.add(header);
|
|
60
|
+
|
|
61
|
+
['METHOD', 'PATH', 'HANDLER', 'TYPE'].forEach(col => {
|
|
62
|
+
const cell = new controls.span({ context, class: 'route-header-cell' });
|
|
63
|
+
cell.add(col);
|
|
64
|
+
header.add(cell);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Table body
|
|
68
|
+
const body = new controls.div({ context, class: 'route-table-body' });
|
|
69
|
+
this.add(body);
|
|
70
|
+
this._body = body;
|
|
71
|
+
|
|
72
|
+
this._routes.forEach(route => {
|
|
73
|
+
const row = this._create_route_row(route);
|
|
74
|
+
body.add(row);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Footer
|
|
78
|
+
const admin_count = this._routes.filter(r => r.category === 'admin').length;
|
|
79
|
+
if (admin_count > 0) {
|
|
80
|
+
const footer = new controls.div({ context, class: 'route-table-footer' });
|
|
81
|
+
footer.add(
|
|
82
|
+
`+ ${admin_count} admin route(s) · Router supports :param and error/not-found events`
|
|
83
|
+
);
|
|
84
|
+
this.add(footer);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!spec.el) { compose(); }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_create_route_row(route) {
|
|
92
|
+
const { context } = this;
|
|
93
|
+
const row = new controls.div({ context, class: 'route-row' });
|
|
94
|
+
|
|
95
|
+
// Method badge
|
|
96
|
+
const method_badge = new Method_Badge({
|
|
97
|
+
context,
|
|
98
|
+
method: this._infer_method(route)
|
|
99
|
+
});
|
|
100
|
+
row.add(method_badge);
|
|
101
|
+
|
|
102
|
+
// Path
|
|
103
|
+
const path_cell = new controls.span({ context, class: 'route-path' });
|
|
104
|
+
path_cell.add(route.path);
|
|
105
|
+
row.add(path_cell);
|
|
106
|
+
|
|
107
|
+
// Handler
|
|
108
|
+
const handler_cell = new controls.span({ context, class: 'route-handler' });
|
|
109
|
+
handler_cell.add(this._describe_handler(route));
|
|
110
|
+
row.add(handler_cell);
|
|
111
|
+
|
|
112
|
+
// Type badge
|
|
113
|
+
const type_badge = new controls.span({
|
|
114
|
+
context,
|
|
115
|
+
class: `route-type-badge type-${route.category}`
|
|
116
|
+
});
|
|
117
|
+
type_badge.add(route.category);
|
|
118
|
+
row.add(type_badge);
|
|
119
|
+
|
|
120
|
+
return row;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_infer_method(route) {
|
|
124
|
+
// API routes (function publishers) are typically POST
|
|
125
|
+
if (route.category === 'api') return 'POST';
|
|
126
|
+
// Observable and SSE routes are GET (with SSE accept)
|
|
127
|
+
if (route.category === 'observable' || route.category === 'sse') return 'GET';
|
|
128
|
+
// Everything else defaults to GET
|
|
129
|
+
return route.method || 'GET';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_describe_handler(route) {
|
|
133
|
+
switch (route.handler_type) {
|
|
134
|
+
case 'Static_Route_HTTP_Responder':
|
|
135
|
+
if (route.path.endsWith('.js')) return 'Client JS bundle';
|
|
136
|
+
if (route.path.endsWith('.css')) return 'Extracted CSS bundle';
|
|
137
|
+
if (route.path === '/') return 'renderControl → HTML';
|
|
138
|
+
return 'Static file serving';
|
|
139
|
+
case 'HTTP_Function_Publisher':
|
|
140
|
+
return 'server.publish()';
|
|
141
|
+
case 'Observable_Publisher':
|
|
142
|
+
return 'Observable → SSE stream';
|
|
143
|
+
case 'HTTP_SSE_Publisher':
|
|
144
|
+
return 'Event stream';
|
|
145
|
+
case 'HTTP_Website_Publisher':
|
|
146
|
+
return 'Website publisher';
|
|
147
|
+
default:
|
|
148
|
+
return route.handler_name || route.handler_type || 'Handler';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
update(routes_data) {
|
|
153
|
+
this._routes = routes_data;
|
|
154
|
+
if (this._body && this._body.el) {
|
|
155
|
+
this._body.el.innerHTML = '';
|
|
156
|
+
routes_data.forEach(route => {
|
|
157
|
+
const row = this._create_route_row(route);
|
|
158
|
+
this._body.add(row);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Method_Badge Control
|
|
168
|
+
|
|
169
|
+
A small colored badge displaying the HTTP method name.
|
|
170
|
+
|
|
171
|
+
### Spec
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
{
|
|
175
|
+
__type_name: 'method_badge',
|
|
176
|
+
method: 'GET' // 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Constructor
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
class Method_Badge extends jsgui.Control {
|
|
184
|
+
constructor(spec = {}) {
|
|
185
|
+
spec.__type_name = spec.__type_name || 'method_badge';
|
|
186
|
+
super(spec);
|
|
187
|
+
const { context } = this;
|
|
188
|
+
const method = (spec.method || 'GET').toUpperCase();
|
|
189
|
+
|
|
190
|
+
const compose = () => {
|
|
191
|
+
this.add(method);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (!spec.el) { compose(); }
|
|
195
|
+
|
|
196
|
+
// Set class for color
|
|
197
|
+
if (typeof this.add_class === 'function') {
|
|
198
|
+
this.add_class(`method-${method.toLowerCase()}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Method_Badge.css = `
|
|
204
|
+
.method_badge {
|
|
205
|
+
display: inline-block;
|
|
206
|
+
font-size: 7px;
|
|
207
|
+
font-weight: 600;
|
|
208
|
+
padding: 2px 6px;
|
|
209
|
+
border-radius: 2px;
|
|
210
|
+
text-align: center;
|
|
211
|
+
min-width: 30px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.method-get {
|
|
215
|
+
background: rgba(72, 184, 72, 0.15);
|
|
216
|
+
border: 0.5px solid #48B848;
|
|
217
|
+
color: #2A6A2A;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.method-post {
|
|
221
|
+
background: rgba(144, 112, 192, 0.15);
|
|
222
|
+
border: 0.5px solid #9070C0;
|
|
223
|
+
color: #5A3890;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.method-put {
|
|
227
|
+
background: rgba(68, 136, 204, 0.15);
|
|
228
|
+
border: 0.5px solid #4488CC;
|
|
229
|
+
color: #2A5A8A;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.method-delete {
|
|
233
|
+
background: rgba(204, 68, 68, 0.15);
|
|
234
|
+
border: 0.5px solid #CC4444;
|
|
235
|
+
color: #992222;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.method-patch {
|
|
239
|
+
background: rgba(216, 160, 32, 0.15);
|
|
240
|
+
border: 0.5px solid #D8A020;
|
|
241
|
+
color: #8A6A10;
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Route Type Badges
|
|
249
|
+
|
|
250
|
+
```css
|
|
251
|
+
.route-type-badge {
|
|
252
|
+
display: inline-block;
|
|
253
|
+
font-size: 7px;
|
|
254
|
+
padding: 2px 6px;
|
|
255
|
+
border-radius: 2px;
|
|
256
|
+
text-align: center;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.type-auto {
|
|
260
|
+
background: rgba(68, 136, 204, 0.12);
|
|
261
|
+
border: 0.5px solid #4488CC;
|
|
262
|
+
color: #3870A0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.type-api {
|
|
266
|
+
background: rgba(144, 112, 192, 0.12);
|
|
267
|
+
border: 0.5px solid #9070C0;
|
|
268
|
+
color: #5A3890;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.type-static {
|
|
272
|
+
background: rgba(160, 140, 100, 0.12);
|
|
273
|
+
border: 0.5px solid #A08060;
|
|
274
|
+
color: #806040;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.type-observable {
|
|
278
|
+
background: rgba(72, 184, 72, 0.12);
|
|
279
|
+
border: 0.5px solid #48B848;
|
|
280
|
+
color: #2A6A2A;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.type-sse {
|
|
284
|
+
background: rgba(64, 152, 184, 0.12);
|
|
285
|
+
border: 0.5px solid #4098B8;
|
|
286
|
+
color: #205868;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.type-admin {
|
|
290
|
+
background: rgba(128, 128, 128, 0.12);
|
|
291
|
+
border: 0.5px solid #808080;
|
|
292
|
+
color: #606060;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.type-website {
|
|
296
|
+
background: rgba(68, 136, 204, 0.12);
|
|
297
|
+
border: 0.5px solid #4488CC;
|
|
298
|
+
color: #3870A0;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Route Table CSS
|
|
305
|
+
|
|
306
|
+
```css
|
|
307
|
+
.route-table-header {
|
|
308
|
+
display: grid;
|
|
309
|
+
grid-template-columns: 60px 2fr 2fr 60px;
|
|
310
|
+
padding: 6px 10px;
|
|
311
|
+
background: #E8E4DC;
|
|
312
|
+
border-top: 0.5px solid #C0B8A8;
|
|
313
|
+
border-bottom: 0.5px solid #C0B8A8;
|
|
314
|
+
gap: 8px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.route-header-cell {
|
|
318
|
+
font-size: 8px;
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
color: #606060;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.route-table-body {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-direction: column;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.route-row {
|
|
329
|
+
display: grid;
|
|
330
|
+
grid-template-columns: 60px 2fr 2fr 60px;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 8px;
|
|
333
|
+
padding: 4px 10px;
|
|
334
|
+
border-bottom: 0.5px solid #E8E4DC;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.route-row:hover {
|
|
338
|
+
background: rgba(68, 136, 204, 0.05);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.route-path {
|
|
342
|
+
font-size: 9px;
|
|
343
|
+
color: #2A4060;
|
|
344
|
+
font-family: 'Consolas', monospace;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.route-handler {
|
|
348
|
+
font-size: 9px;
|
|
349
|
+
color: #808080;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.route-table-footer {
|
|
353
|
+
font-size: 8px;
|
|
354
|
+
color: #908888;
|
|
355
|
+
padding: 8px 4px 0;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## API Explorer (Phase 2)
|
|
362
|
+
|
|
363
|
+
The API Explorer extends the Route_Table with interactive testing for function-published endpoints.
|
|
364
|
+
|
|
365
|
+
### Concept
|
|
366
|
+
|
|
367
|
+
When a user clicks on an API route row, an expansion panel appears below with:
|
|
368
|
+
1. A request body editor (textarea or JSON input)
|
|
369
|
+
2. A "Send" button
|
|
370
|
+
3. A response viewer showing status and response body
|
|
371
|
+
|
|
372
|
+
### API_Test_Panel Control
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
class API_Test_Panel extends jsgui.Control {
|
|
376
|
+
constructor(spec = {}) {
|
|
377
|
+
spec.__type_name = spec.__type_name || 'api_test_panel';
|
|
378
|
+
super(spec);
|
|
379
|
+
const { context } = this;
|
|
380
|
+
|
|
381
|
+
const compose = () => {
|
|
382
|
+
// Request section
|
|
383
|
+
const req_section = new controls.div({ context, class: 'api-test-request' });
|
|
384
|
+
this.add(req_section);
|
|
385
|
+
|
|
386
|
+
const req_label = new controls.div({ context, class: 'api-test-label' });
|
|
387
|
+
req_label.add('Request Body (JSON):');
|
|
388
|
+
req_section.add(req_label);
|
|
389
|
+
|
|
390
|
+
// Using a textarea for input
|
|
391
|
+
const req_input = new controls.textarea({
|
|
392
|
+
context,
|
|
393
|
+
class: 'api-test-input'
|
|
394
|
+
});
|
|
395
|
+
req_input.dom.attributes.rows = '4';
|
|
396
|
+
req_input.dom.attributes.placeholder = '{ "key": "value" }';
|
|
397
|
+
req_section.add(req_input);
|
|
398
|
+
this._input = req_input;
|
|
399
|
+
|
|
400
|
+
// Send button
|
|
401
|
+
const send_btn = new controls.Button({
|
|
402
|
+
context,
|
|
403
|
+
text: 'Send Request',
|
|
404
|
+
class: 'api-test-send'
|
|
405
|
+
});
|
|
406
|
+
req_section.add(send_btn);
|
|
407
|
+
this._send_btn = send_btn;
|
|
408
|
+
|
|
409
|
+
// Response section
|
|
410
|
+
const res_section = new controls.div({ context, class: 'api-test-response' });
|
|
411
|
+
this.add(res_section);
|
|
412
|
+
|
|
413
|
+
const res_label = new controls.div({ context, class: 'api-test-label' });
|
|
414
|
+
res_label.add('Response:');
|
|
415
|
+
res_section.add(res_label);
|
|
416
|
+
|
|
417
|
+
const res_output = new controls.div({ context, class: 'api-test-output' });
|
|
418
|
+
res_output.add('—');
|
|
419
|
+
res_section.add(res_output);
|
|
420
|
+
this._output = res_output;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
if (!spec.el) { compose(); }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
activate() {
|
|
427
|
+
if (!this.__active) {
|
|
428
|
+
super.activate();
|
|
429
|
+
const route = this.spec.route;
|
|
430
|
+
const method = this.spec.method || 'POST';
|
|
431
|
+
|
|
432
|
+
this._send_btn.el.addEventListener('click', async () => {
|
|
433
|
+
try {
|
|
434
|
+
const body = this._input.el.value;
|
|
435
|
+
const response = await fetch(route, {
|
|
436
|
+
method: method,
|
|
437
|
+
headers: { 'Content-Type': 'application/json' },
|
|
438
|
+
body: body || undefined
|
|
439
|
+
});
|
|
440
|
+
const text = await response.text();
|
|
441
|
+
let display = `Status: ${response.status}\n\n`;
|
|
442
|
+
try {
|
|
443
|
+
display += JSON.stringify(JSON.parse(text), null, 2);
|
|
444
|
+
} catch {
|
|
445
|
+
display += text;
|
|
446
|
+
}
|
|
447
|
+
this._output.el.innerText = display;
|
|
448
|
+
this._output.el.className = response.ok
|
|
449
|
+
? 'api-test-output response-ok'
|
|
450
|
+
: 'api-test-output response-error';
|
|
451
|
+
} catch (err) {
|
|
452
|
+
this._output.el.innerText = `Error: ${err.message}`;
|
|
453
|
+
this._output.el.className = 'api-test-output response-error';
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
API_Test_Panel.css = `
|
|
461
|
+
.api_test_panel {
|
|
462
|
+
background: #F8F6F2;
|
|
463
|
+
border: 1px solid #D0C8B8;
|
|
464
|
+
border-radius: 3px;
|
|
465
|
+
padding: 12px;
|
|
466
|
+
margin: 6px 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.api-test-label {
|
|
470
|
+
font-size: 9px;
|
|
471
|
+
font-weight: 500;
|
|
472
|
+
color: #606060;
|
|
473
|
+
margin-bottom: 4px;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.api-test-input {
|
|
477
|
+
width: 100%;
|
|
478
|
+
font-family: 'Consolas', monospace;
|
|
479
|
+
font-size: 10px;
|
|
480
|
+
padding: 6px;
|
|
481
|
+
border: 1px solid #ACA899;
|
|
482
|
+
border-radius: 2px;
|
|
483
|
+
background: linear-gradient(to bottom, #E8E4DD 0%, #FFFFFF 3%, #FFFFFF 100%);
|
|
484
|
+
resize: vertical;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.api-test-send {
|
|
488
|
+
margin-top: 8px;
|
|
489
|
+
padding: 4px 16px;
|
|
490
|
+
font-size: 9px;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.api-test-response {
|
|
494
|
+
margin-top: 12px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.api-test-output {
|
|
498
|
+
font-family: 'Consolas', monospace;
|
|
499
|
+
font-size: 9px;
|
|
500
|
+
padding: 8px;
|
|
501
|
+
border: 1px solid #D0C8B8;
|
|
502
|
+
border-radius: 2px;
|
|
503
|
+
background: #FFFFFF;
|
|
504
|
+
white-space: pre-wrap;
|
|
505
|
+
max-height: 200px;
|
|
506
|
+
overflow-y: auto;
|
|
507
|
+
color: #2A4060;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.response-ok { border-left: 3px solid #48B848; }
|
|
511
|
+
.response-error { border-left: 3px solid #CC4444; }
|
|
512
|
+
`;
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Adapter — `get_routes_list()`
|
|
518
|
+
|
|
519
|
+
The Admin Module must track routes as they are registered. Since the Router doesn't natively expose a route list, we intercept `set_route`:
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
get_routes_list() {
|
|
523
|
+
// Filter out admin routes for cleaner display (optional)
|
|
524
|
+
const user_routes = this._route_registry.filter(
|
|
525
|
+
r => r.category !== 'admin'
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
count: user_routes.length,
|
|
530
|
+
routes: user_routes.map(r => ({
|
|
531
|
+
path: r.path,
|
|
532
|
+
method: this._infer_method_for_route(r),
|
|
533
|
+
handler_type: r.handler_type,
|
|
534
|
+
handler_name: r.handler_name,
|
|
535
|
+
category: r.category,
|
|
536
|
+
description: this._describe_handler_for_route(r)
|
|
537
|
+
}))
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
_infer_method_for_route(route) {
|
|
542
|
+
if (route.category === 'api') return 'POST';
|
|
543
|
+
if (route.category === 'observable') return 'GET';
|
|
544
|
+
if (route.category === 'sse') return 'GET';
|
|
545
|
+
return 'GET';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
_describe_handler_for_route(route) {
|
|
549
|
+
switch (route.handler_type) {
|
|
550
|
+
case 'Static_Route_HTTP_Responder':
|
|
551
|
+
if (route.path.endsWith('.js')) return 'Client JS bundle';
|
|
552
|
+
if (route.path.endsWith('.css')) return 'Extracted CSS bundle';
|
|
553
|
+
if (route.path === '/' || route.path === '/admin') return 'HTML page';
|
|
554
|
+
return 'Static file';
|
|
555
|
+
case 'HTTP_Function_Publisher':
|
|
556
|
+
return `server.publish('${route.handler_name || route.path}')`;
|
|
557
|
+
case 'Observable_Publisher':
|
|
558
|
+
return 'Observable → SSE';
|
|
559
|
+
case 'HTTP_SSE_Publisher':
|
|
560
|
+
return 'Server-Sent Events';
|
|
561
|
+
default:
|
|
562
|
+
return route.handler_type;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## Route Filtering
|
|
570
|
+
|
|
571
|
+
The Route_Table supports a simple filter bar for large route sets:
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
// In activate():
|
|
575
|
+
const filter_input = document.querySelector('.route-filter-input');
|
|
576
|
+
if (filter_input) {
|
|
577
|
+
filter_input.addEventListener('input', (e) => {
|
|
578
|
+
const query = e.target.value.toLowerCase();
|
|
579
|
+
const rows = document.querySelectorAll('.route-row');
|
|
580
|
+
rows.forEach(row => {
|
|
581
|
+
const path = row.querySelector('.route-path')?.innerText?.toLowerCase() || '';
|
|
582
|
+
row.style.display = path.includes(query) ? '' : 'none';
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
```
|