jsgui3-server 0.0.149 → 0.0.151
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/.github/instructions/copilot.instructions.md +1 -0
- package/AGENTS.md +6 -0
- package/README.md +185 -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/api-reference.md +383 -303
- 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/comprehensive-documentation.md +220 -220
- package/docs/configuration-reference.md +227 -204
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/middleware-guide.md +236 -0
- package/docs/system-architecture.md +24 -18
- package/docs/troubleshooting.md +84 -53
- package/middleware/compression.js +217 -0
- package/middleware/index.js +15 -0
- package/module.js +19 -11
- package/package.json +1 -1
- package/serve-factory.js +29 -0
- package/server.js +280 -20
- 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
package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# Chapter 10: Domain Controls — Build Status & Bundle Inspector
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Build Status panel surfaces information about the JavaScript and CSS bundles produced by jsgui3-server's ESBuild bundling pipeline. As documented in Chapter 3 (Server Introspection), the publisher system generates bundles and exposes some size/path data through the adapter layer.
|
|
6
|
+
|
|
7
|
+
In the design reference this appears as the "BUILD OUTPUT" group box containing file sizes and timing.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Available Build Data
|
|
12
|
+
|
|
13
|
+
From Chapter 3, the following build information is available or capturable:
|
|
14
|
+
|
|
15
|
+
| Datum | Source | Available Now |
|
|
16
|
+
|-------|--------|:------------:|
|
|
17
|
+
| JS bundle path | `HTTP_Webpage_Publisher` `js_output_path` | ✅ |
|
|
18
|
+
| CSS bundle path | `HTTP_Webpage_Publisher` `css_output_path` | ✅ |
|
|
19
|
+
| JS file size (identity) | `fs.statSync` on output path | ✅ |
|
|
20
|
+
| CSS file size (identity) | `fs.statSync` on output path | ✅ |
|
|
21
|
+
| JS file size (gzipped) | `zlib.gzipSync` + measure | Adapter |
|
|
22
|
+
| CSS file size (gzipped) | `zlib.gzipSync` + measure | Adapter |
|
|
23
|
+
| Build timestamp | Captured on publisher `ready` | Adapter |
|
|
24
|
+
| Build duration | Captured via timing wrapper | Adapter |
|
|
25
|
+
| Module count | ESBuild `metafile.inputs` | Adapter |
|
|
26
|
+
| Source map status | Server config / publisher options | ✅ |
|
|
27
|
+
| Entry point | `src_path_client_js` | ✅ |
|
|
28
|
+
| Output directory | `build_path` or derived | ✅ |
|
|
29
|
+
| Compression ratios | Computed from identity/gzipped sizes | Adapter |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Adapter Enhancement
|
|
34
|
+
|
|
35
|
+
The `Admin_Module_V1` adapter captures build data when the publisher fires its `ready` event. This was specified in Chapter 4 (`capture_bundle_info`):
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// Inside Admin_Module_V1.init()
|
|
39
|
+
capture_bundle_info(server) {
|
|
40
|
+
const publishers = server.publishers || [];
|
|
41
|
+
|
|
42
|
+
publishers.forEach(pub => {
|
|
43
|
+
if (pub instanceof HTTP_Webpage_Publisher) {
|
|
44
|
+
const capture = () => {
|
|
45
|
+
const info = { publisher_name: pub.name || 'default', items: [] };
|
|
46
|
+
|
|
47
|
+
if (pub.js_output_path && fs.existsSync(pub.js_output_path)) {
|
|
48
|
+
const js_stat = fs.statSync(pub.js_output_path);
|
|
49
|
+
const js_buf = fs.readFileSync(pub.js_output_path);
|
|
50
|
+
const js_gzip = zlib.gzipSync(js_buf);
|
|
51
|
+
|
|
52
|
+
info.items.push({
|
|
53
|
+
type: 'js',
|
|
54
|
+
path: pub.js_output_path,
|
|
55
|
+
size_identity: js_stat.size,
|
|
56
|
+
size_gzip: js_gzip.length,
|
|
57
|
+
compression_ratio: (js_gzip.length / js_stat.size * 100).toFixed(1)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (pub.css_output_path && fs.existsSync(pub.css_output_path)) {
|
|
62
|
+
const css_stat = fs.statSync(pub.css_output_path);
|
|
63
|
+
const css_buf = fs.readFileSync(pub.css_output_path);
|
|
64
|
+
const css_gzip = zlib.gzipSync(css_buf);
|
|
65
|
+
|
|
66
|
+
info.items.push({
|
|
67
|
+
type: 'css',
|
|
68
|
+
path: pub.css_output_path,
|
|
69
|
+
size_identity: css_stat.size,
|
|
70
|
+
size_gzip: css_gzip.length,
|
|
71
|
+
compression_ratio: (css_gzip.length / css_stat.size * 100).toFixed(1)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
info.built_at = Date.now();
|
|
76
|
+
this._build_info = info;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (pub._ready) {
|
|
80
|
+
capture();
|
|
81
|
+
}
|
|
82
|
+
pub.on('ready', capture);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### API Endpoint
|
|
89
|
+
|
|
90
|
+
`GET /api/admin/v1/build`
|
|
91
|
+
|
|
92
|
+
Response:
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"publisher_name": "default",
|
|
96
|
+
"items": [
|
|
97
|
+
{
|
|
98
|
+
"type": "js",
|
|
99
|
+
"path": "/tmp/jsgui-build/bundle.js",
|
|
100
|
+
"size_identity": 254892,
|
|
101
|
+
"size_gzip": 68420,
|
|
102
|
+
"compression_ratio": "26.8"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "css",
|
|
106
|
+
"path": "/tmp/jsgui-build/bundle.css",
|
|
107
|
+
"size_identity": 12480,
|
|
108
|
+
"size_gzip": 3120,
|
|
109
|
+
"compression_ratio": "25.0"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"built_at": 1739567000000,
|
|
113
|
+
"entry_point": "/home/user/app/client.js",
|
|
114
|
+
"source_maps": true,
|
|
115
|
+
"build_path": "/tmp/jsgui-build"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Build_Status Control
|
|
122
|
+
|
|
123
|
+
### Spec
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
{
|
|
127
|
+
__type_name: 'build_status',
|
|
128
|
+
title: 'Build Output'
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Visual Anatomy
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
┌─ BUILD OUTPUT ─────────────────────────────────────────┐
|
|
136
|
+
│ │
|
|
137
|
+
│ JavaScript Bundle │
|
|
138
|
+
│ ━━━━━━━━━━━━━━━━━━━━━━████████ 254 KB → 68 KB (27%) │
|
|
139
|
+
│ │
|
|
140
|
+
│ CSS Bundle │
|
|
141
|
+
│ ━━━━━█████████████████████████ 12 KB → 3 KB (25%) │
|
|
142
|
+
│ │
|
|
143
|
+
│ ┌──────────────────────────────────────────────┐ │
|
|
144
|
+
│ │ Entry: client.js │ │
|
|
145
|
+
│ │ Output: /tmp/jsgui-build/ │ │
|
|
146
|
+
│ │ Maps: Enabled ✓ │ │
|
|
147
|
+
│ │ Built: 2 minutes ago │ │
|
|
148
|
+
│ └──────────────────────────────────────────────┘ │
|
|
149
|
+
│ │
|
|
150
|
+
└────────────────────────────────────────────────────────┘
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Constructor
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
class Build_Status extends jsgui.Control {
|
|
157
|
+
constructor(spec = {}) {
|
|
158
|
+
spec.__type_name = spec.__type_name || 'build_status';
|
|
159
|
+
super(spec);
|
|
160
|
+
const { context } = this;
|
|
161
|
+
|
|
162
|
+
const compose = () => {
|
|
163
|
+
const group = new Group_Box({ context, title: spec.title || 'Build Output' });
|
|
164
|
+
this.add(group);
|
|
165
|
+
this._group = group;
|
|
166
|
+
|
|
167
|
+
// Bundle bars container
|
|
168
|
+
const bars_container = new controls.div({ context, class: 'build-bars' });
|
|
169
|
+
group.add(bars_container);
|
|
170
|
+
this._bars_container = bars_container;
|
|
171
|
+
|
|
172
|
+
// Details summary
|
|
173
|
+
const details = new controls.div({ context, class: 'build-details' });
|
|
174
|
+
group.add(details);
|
|
175
|
+
this._details = details;
|
|
176
|
+
|
|
177
|
+
// Entry point row
|
|
178
|
+
const entry_row = this._make_detail_row('Entry', '—');
|
|
179
|
+
details.add(entry_row);
|
|
180
|
+
this._entry_label = entry_row._value;
|
|
181
|
+
|
|
182
|
+
// Output directory row
|
|
183
|
+
const output_row = this._make_detail_row('Output', '—');
|
|
184
|
+
details.add(output_row);
|
|
185
|
+
this._output_label = output_row._value;
|
|
186
|
+
|
|
187
|
+
// Source maps row
|
|
188
|
+
const maps_row = this._make_detail_row('Maps', '—');
|
|
189
|
+
details.add(maps_row);
|
|
190
|
+
this._maps_label = maps_row._value;
|
|
191
|
+
|
|
192
|
+
// Built timestamp row
|
|
193
|
+
const built_row = this._make_detail_row('Built', '—');
|
|
194
|
+
details.add(built_row);
|
|
195
|
+
this._built_label = built_row._value;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (!spec.el) { compose(); }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_make_detail_row(label, value) {
|
|
202
|
+
const { context } = this;
|
|
203
|
+
const row = new controls.div({ context, class: 'build-detail-row' });
|
|
204
|
+
const lbl = new controls.span({ context, class: 'build-detail-label' });
|
|
205
|
+
lbl.add(label + ':');
|
|
206
|
+
const val = new controls.span({ context, class: 'build-detail-value' });
|
|
207
|
+
val.add(value);
|
|
208
|
+
row.add(lbl);
|
|
209
|
+
row.add(val);
|
|
210
|
+
row._value = val;
|
|
211
|
+
return row;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
update(build_data) {
|
|
215
|
+
if (!build_data) return;
|
|
216
|
+
|
|
217
|
+
// Clear existing bars
|
|
218
|
+
if (this._bars_container && this._bars_container.el) {
|
|
219
|
+
this._bars_container.el.innerHTML = '';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Render bundle bars
|
|
223
|
+
if (build_data.items) {
|
|
224
|
+
build_data.items.forEach(item => {
|
|
225
|
+
this._render_bundle_bar(item);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Update details
|
|
230
|
+
if (build_data.entry_point && this._entry_label) {
|
|
231
|
+
const basename = build_data.entry_point.split(/[/\\]/).pop();
|
|
232
|
+
this._entry_label.el.innerText = basename;
|
|
233
|
+
}
|
|
234
|
+
if (build_data.build_path && this._output_label) {
|
|
235
|
+
this._output_label.el.innerText = build_data.build_path;
|
|
236
|
+
}
|
|
237
|
+
if (this._maps_label) {
|
|
238
|
+
this._maps_label.el.innerText = build_data.source_maps ? 'Enabled ✓' : 'Disabled';
|
|
239
|
+
}
|
|
240
|
+
if (build_data.built_at && this._built_label) {
|
|
241
|
+
this._built_label.el.innerText = format_relative_time(build_data.built_at);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_render_bundle_bar(item) {
|
|
246
|
+
const { context } = this;
|
|
247
|
+
const bar_group = new controls.div({ context, class: `build-bar-group build-bar-${item.type}` });
|
|
248
|
+
|
|
249
|
+
// Label
|
|
250
|
+
const label = new controls.div({ context, class: 'build-bar-label' });
|
|
251
|
+
const type_name = item.type === 'js' ? 'JavaScript Bundle' : 'CSS Bundle';
|
|
252
|
+
label.add(type_name);
|
|
253
|
+
bar_group.add(label);
|
|
254
|
+
|
|
255
|
+
// Bar + sizes
|
|
256
|
+
const bar_row = new controls.div({ context, class: 'build-bar-row' });
|
|
257
|
+
bar_group.add(bar_row);
|
|
258
|
+
|
|
259
|
+
// Visual bar
|
|
260
|
+
const bar_track = new controls.div({ context, class: 'build-bar-track' });
|
|
261
|
+
const bar_fill = new controls.div({ context, class: 'build-bar-fill' });
|
|
262
|
+
bar_track.add(bar_fill);
|
|
263
|
+
bar_row.add(bar_track);
|
|
264
|
+
|
|
265
|
+
// Set fill width proportional to compression ratio
|
|
266
|
+
if (item.compression_ratio) {
|
|
267
|
+
bar_fill.dom.attributes.style = `width: ${item.compression_ratio}%`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Sizes text
|
|
271
|
+
const sizes = new controls.span({ context, class: 'build-bar-sizes' });
|
|
272
|
+
sizes.add(
|
|
273
|
+
`${format_bytes(item.size_identity)} → ${format_bytes(item.size_gzip)} (${item.compression_ratio}%)`
|
|
274
|
+
);
|
|
275
|
+
bar_row.add(sizes);
|
|
276
|
+
|
|
277
|
+
if (this._bars_container.el) {
|
|
278
|
+
// Client-side: direct DOM
|
|
279
|
+
const temp = document.createElement('div');
|
|
280
|
+
temp.innerHTML = bar_group.html();
|
|
281
|
+
while (temp.firstChild) {
|
|
282
|
+
this._bars_container.el.appendChild(temp.firstChild);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
this._bars_container.add(bar_group);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
activate() {
|
|
290
|
+
if (!this.__active) {
|
|
291
|
+
super.activate();
|
|
292
|
+
// Fetch initial build data
|
|
293
|
+
this._fetch_build_data();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async _fetch_build_data() {
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetch('/api/admin/v1/build');
|
|
300
|
+
if (res.ok) {
|
|
301
|
+
const data = await res.json();
|
|
302
|
+
this.update(data);
|
|
303
|
+
}
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.warn('Build status fetch failed:', err);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### CSS
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
.build_status {
|
|
315
|
+
min-width: 280px;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.build-bars {
|
|
319
|
+
padding: 8px 12px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.build-bar-group {
|
|
323
|
+
margin-bottom: 12px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.build-bar-label {
|
|
327
|
+
font-size: 8.5px;
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
color: #2A4060;
|
|
330
|
+
margin-bottom: 4px;
|
|
331
|
+
text-transform: uppercase;
|
|
332
|
+
letter-spacing: 0.3px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.build-bar-row {
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
gap: 8px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.build-bar-track {
|
|
342
|
+
flex: 1;
|
|
343
|
+
height: 8px;
|
|
344
|
+
background: #E8E4DC;
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
overflow: hidden;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.build-bar-fill {
|
|
350
|
+
height: 100%;
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
transition: width 0.3s ease;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.build-bar-js .build-bar-fill {
|
|
356
|
+
background: linear-gradient(90deg, #4488CC, #66AAEE);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.build-bar-css .build-bar-fill {
|
|
360
|
+
background: linear-gradient(90deg, #66AA66, #88CC88);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.build-bar-sizes {
|
|
364
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
365
|
+
font-size: 8px;
|
|
366
|
+
color: #666;
|
|
367
|
+
white-space: nowrap;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Details section */
|
|
371
|
+
.build-details {
|
|
372
|
+
border-top: 1px solid #E0DCD4;
|
|
373
|
+
padding: 8px 12px;
|
|
374
|
+
margin-top: 4px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.build-detail-row {
|
|
378
|
+
display: flex;
|
|
379
|
+
padding: 2px 0;
|
|
380
|
+
font-size: 8.5px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.build-detail-label {
|
|
384
|
+
min-width: 60px;
|
|
385
|
+
color: #808080;
|
|
386
|
+
font-weight: 500;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.build-detail-value {
|
|
390
|
+
color: #2A4060;
|
|
391
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
392
|
+
font-size: 8px;
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Bundle Size Visualization
|
|
399
|
+
|
|
400
|
+
The compression bar communicates two things at a glance:
|
|
401
|
+
|
|
402
|
+
1. **Bar fill width** = the gzipped size as a percentage of the original (the compression ratio)
|
|
403
|
+
2. **Text** = the exact sizes and percentage
|
|
404
|
+
|
|
405
|
+
A heavily compressed file (e.g., 250 KB → 65 KB = 26%) shows a narrow fill bar, indicating efficient compression. A less compressible file shows a wider bar.
|
|
406
|
+
|
|
407
|
+
**Color coding:**
|
|
408
|
+
- JavaScript: blue gradient (`#4488CC` → `#66AAEE`) — matches the server information theme
|
|
409
|
+
- CSS: green gradient (`#66AA66` → `#88CC88`) — matches the style/rendering theme
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Bundle Detail Expansion (Phase 2)
|
|
414
|
+
|
|
415
|
+
In Phase 2, clicking a bundle bar expands to show:
|
|
416
|
+
- Module list (from ESBuild metafile)
|
|
417
|
+
- Individual module sizes
|
|
418
|
+
- Import tree visualization
|
|
419
|
+
- Largest modules sorted by size
|
|
420
|
+
|
|
421
|
+
This requires the adapter to capture ESBuild's `metafile` option:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
// Enhanced adapter: capture metafile during build
|
|
425
|
+
async _build_with_metafile(entry_point, output_path) {
|
|
426
|
+
const result = await esbuild.build({
|
|
427
|
+
entryPoints: [entry_point],
|
|
428
|
+
outfile: output_path,
|
|
429
|
+
bundle: true,
|
|
430
|
+
metafile: true // Request module information
|
|
431
|
+
// ... other options
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
this._metafile = result.metafile;
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Module List Response (Phase 2)
|
|
440
|
+
|
|
441
|
+
`GET /api/admin/v1/build/modules`
|
|
442
|
+
|
|
443
|
+
```json
|
|
444
|
+
{
|
|
445
|
+
"total_modules": 47,
|
|
446
|
+
"modules": [
|
|
447
|
+
{
|
|
448
|
+
"path": "node_modules/jsgui3-html/controls/div.js",
|
|
449
|
+
"size": 12480,
|
|
450
|
+
"percentage": 4.9
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"path": "client/dashboard.js",
|
|
454
|
+
"size": 8200,
|
|
455
|
+
"percentage": 3.2
|
|
456
|
+
}
|
|
457
|
+
],
|
|
458
|
+
"largest": [
|
|
459
|
+
{ "path": "node_modules/jsgui3-html/core/control.js", "size": 45200 }
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Rebuild Trigger (Phase 2)
|
|
467
|
+
|
|
468
|
+
A "Rebuild" button in the Build Status panel triggers a manual rebundle:
|
|
469
|
+
|
|
470
|
+
`POST /api/admin/v1/build/rebuild`
|
|
471
|
+
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"status": "ok",
|
|
475
|
+
"duration_ms": 1240,
|
|
476
|
+
"items": [
|
|
477
|
+
{ "type": "js", "size_identity": 254892, "size_gzip": 68420 },
|
|
478
|
+
{ "type": "css", "size_identity": 12480, "size_gzip": 3120 }
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
This uses the existing publisher's rebuild mechanism if available, or invokes esbuild directly with the same configuration.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Integration with Dashboard
|
|
488
|
+
|
|
489
|
+
On the dashboard overview, the Build Status control is placed in the lower-right quadrant alongside the Route Table. It provides an at-a-glance view of the current bundle state.
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
// In Admin_Dashboard compose()
|
|
493
|
+
const build_status = new Build_Status({ context, title: 'Build Output' });
|
|
494
|
+
content_grid.add(build_status);
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
When the SSE channel delivers a `build_complete` event, the dashboard's Build_Status control auto-refreshes:
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
// In Admin_Dashboard activate()
|
|
501
|
+
event_source.addEventListener('build_complete', (e) => {
|
|
502
|
+
const data = JSON.parse(e.data);
|
|
503
|
+
build_status.update(data);
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Utility Functions
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
function format_bytes(bytes) {
|
|
513
|
+
if (bytes === 0) return '0 B';
|
|
514
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
515
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
516
|
+
return (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0) + ' ' + units[i];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function format_relative_time(timestamp) {
|
|
520
|
+
const diff = Date.now() - timestamp;
|
|
521
|
+
if (diff < 60000) return 'just now';
|
|
522
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + ' minutes ago';
|
|
523
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + ' hours ago';
|
|
524
|
+
return new Date(timestamp).toLocaleString();
|
|
525
|
+
}
|
|
526
|
+
```
|