jsgui3-server 0.0.148 → 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/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/AGENTS.md +4 -0
- package/README.md +215 -3
- package/admin-ui/client.js +81 -51
- 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/dev-status.svg +139 -0
- package/docs/admin-extension-guide.md +345 -0
- package/docs/api-reference.md +301 -43
- 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/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comparison-report-express-plex-cpanel.md +549 -0
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/docs/troubleshooting.md +84 -53
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/server.js +9 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +12 -0
- package/package.json +12 -2
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-sse-publisher.js +341 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +129 -33
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +442 -259
- package/server.js +288 -13
- package/tests/README.md +71 -4
- package/tests/admin-ui-jsgui-controls.test.js +581 -0
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +14 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Chapter 9: Domain Controls — Log Viewer & Activity Feed
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Log Viewer is a scrolling, real-time log display that streams server activity — HTTP request/response entries, resource state changes, build events, and warnings. In the design reference, it appears as the "Recent Activity" group box and also as a dedicated "Logs" tab within the tabbed panel.
|
|
6
|
+
|
|
7
|
+
The control supports:
|
|
8
|
+
- Timestamped entries with color-coded severity
|
|
9
|
+
- Auto-scroll to bottom (with pause on manual scroll)
|
|
10
|
+
- Filtering by log level and source
|
|
11
|
+
- Search across visible entries
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Log_Viewer Control
|
|
16
|
+
|
|
17
|
+
### Spec
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
{
|
|
21
|
+
__type_name: 'log_viewer',
|
|
22
|
+
title: 'Recent Activity',
|
|
23
|
+
max_lines: 500, // Maximum lines in buffer (FIFO)
|
|
24
|
+
auto_scroll: true, // Auto-scroll to bottom
|
|
25
|
+
show_timestamps: true, // Show timestamp column
|
|
26
|
+
initial_entries: [ // Pre-populated entries (from recent events)
|
|
27
|
+
{
|
|
28
|
+
timestamp: 1739567000000,
|
|
29
|
+
level: 'info',
|
|
30
|
+
source: 'request',
|
|
31
|
+
message: 'GET / — 200 OK (14ms)'
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Visual Anatomy
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
┌─ Recent Activity ──────────────────────────────────────┐
|
|
41
|
+
│ │
|
|
42
|
+
│ 14:23:07 File change detected: client.js — rebuild │
|
|
43
|
+
│ 14:23:08 Bundle built successfully (245 KB, 1.2s) │
|
|
44
|
+
│ 14:23:08 CSS extracted (12 KB, 12 controls) │
|
|
45
|
+
│ 14:23:09 ⚠ Warning: unused variable in helpers.js:42 │
|
|
46
|
+
│ 14:22:45 GET / — 200 OK (14ms) │
|
|
47
|
+
│ 14:22:38 POST /api/validateUser — 200 OK (3ms) │
|
|
48
|
+
│ │
|
|
49
|
+
└────────────────────────────────────────────────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Constructor
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
class Log_Viewer extends jsgui.Control {
|
|
56
|
+
constructor(spec = {}) {
|
|
57
|
+
spec.__type_name = spec.__type_name || 'log_viewer';
|
|
58
|
+
super(spec);
|
|
59
|
+
const { context } = this;
|
|
60
|
+
|
|
61
|
+
this._max_lines = spec.max_lines || 500;
|
|
62
|
+
this._auto_scroll = spec.auto_scroll !== false;
|
|
63
|
+
this._show_timestamps = spec.show_timestamps !== false;
|
|
64
|
+
this._entries = [];
|
|
65
|
+
this._paused = false; // User-scrolled pause
|
|
66
|
+
|
|
67
|
+
const compose = () => {
|
|
68
|
+
// Optional group box wrapper
|
|
69
|
+
if (spec.title) {
|
|
70
|
+
const group = new Group_Box({ context, title: spec.title });
|
|
71
|
+
this.add(group);
|
|
72
|
+
this._container = group;
|
|
73
|
+
} else {
|
|
74
|
+
this._container = this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter bar (collapsible)
|
|
78
|
+
if (spec.show_filter !== false) {
|
|
79
|
+
const filter_bar = new controls.div({ context, class: 'log-filter-bar' });
|
|
80
|
+
this._container.add(filter_bar);
|
|
81
|
+
this._filter_bar = filter_bar;
|
|
82
|
+
|
|
83
|
+
// Level filter chips
|
|
84
|
+
const levels = ['all', 'info', 'warn', 'error', 'debug'];
|
|
85
|
+
levels.forEach(level => {
|
|
86
|
+
const chip = new controls.span({
|
|
87
|
+
context,
|
|
88
|
+
class: `log-filter-chip ${level === 'all' ? 'chip-active' : ''}`
|
|
89
|
+
});
|
|
90
|
+
chip.dom.attributes['data-level'] = level;
|
|
91
|
+
chip.add(level);
|
|
92
|
+
filter_bar.add(chip);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Search input
|
|
96
|
+
const search = new controls.div({ context, class: 'log-search' });
|
|
97
|
+
filter_bar.add(search);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Log output area
|
|
101
|
+
const output = new controls.div({ context, class: 'log-output' });
|
|
102
|
+
this._container.add(output);
|
|
103
|
+
this._output = output;
|
|
104
|
+
|
|
105
|
+
// Pre-populate initial entries
|
|
106
|
+
if (spec.initial_entries) {
|
|
107
|
+
spec.initial_entries.forEach(entry => this._render_entry(entry));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (!spec.el) { compose(); }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_render_entry(entry) {
|
|
115
|
+
const { context } = this;
|
|
116
|
+
const line = new controls.div({
|
|
117
|
+
context,
|
|
118
|
+
class: `log-line log-${entry.level || 'info'}`
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Timestamp
|
|
122
|
+
if (this._show_timestamps) {
|
|
123
|
+
const time = new controls.span({ context, class: 'log-timestamp' });
|
|
124
|
+
time.add(format_time(entry.timestamp));
|
|
125
|
+
line.add(time);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Message
|
|
129
|
+
const msg = new controls.span({ context, class: 'log-message' });
|
|
130
|
+
msg.add(entry.message || '');
|
|
131
|
+
line.add(msg);
|
|
132
|
+
|
|
133
|
+
this._output.add(line);
|
|
134
|
+
this._entries.push(entry);
|
|
135
|
+
|
|
136
|
+
// Trim if over max
|
|
137
|
+
if (this._entries.length > this._max_lines) {
|
|
138
|
+
this._entries.shift();
|
|
139
|
+
if (this._output.el && this._output.el.firstChild) {
|
|
140
|
+
this._output.el.removeChild(this._output.el.firstChild);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Client-side: append a new log entry
|
|
146
|
+
append(entry) {
|
|
147
|
+
this._render_entry(entry);
|
|
148
|
+
|
|
149
|
+
// Auto-scroll to bottom
|
|
150
|
+
if (this._auto_scroll && !this._paused && this._output.el) {
|
|
151
|
+
this._output.el.scrollTop = this._output.el.scrollHeight;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Client-side: append from SSE data
|
|
156
|
+
append_request(req_data) {
|
|
157
|
+
const status_color = req_data.status >= 400 ? 'error'
|
|
158
|
+
: req_data.status >= 300 ? 'warn' : 'info';
|
|
159
|
+
|
|
160
|
+
this.append({
|
|
161
|
+
timestamp: req_data.timestamp,
|
|
162
|
+
level: status_color,
|
|
163
|
+
source: 'request',
|
|
164
|
+
message: `${req_data.method} ${req_data.url} — ${req_data.status} (${req_data.duration_ms}ms)`
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
append_resource_event(event_data) {
|
|
169
|
+
const level = event_data.to === 'crashed' ? 'error'
|
|
170
|
+
: event_data.to === 'unhealthy' ? 'warn' : 'info';
|
|
171
|
+
|
|
172
|
+
this.append({
|
|
173
|
+
timestamp: event_data.timestamp || Date.now(),
|
|
174
|
+
level: level,
|
|
175
|
+
source: 'resource',
|
|
176
|
+
message: `Resource "${event_data.resourceName}": ${event_data.from || '?'} → ${event_data.to || '?'}`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
append_build_event(build_data) {
|
|
181
|
+
const js_item = build_data.items?.find(i => i.type === 'js');
|
|
182
|
+
const css_item = build_data.items?.find(i => i.type === 'css');
|
|
183
|
+
|
|
184
|
+
this.append({
|
|
185
|
+
timestamp: build_data.built_at || Date.now(),
|
|
186
|
+
level: 'info',
|
|
187
|
+
source: 'build',
|
|
188
|
+
message: `Bundle built: JS ${format_bytes(js_item?.size_identity || 0)}, CSS ${format_bytes(css_item?.size_identity || 0)}`
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
clear() {
|
|
193
|
+
this._entries = [];
|
|
194
|
+
if (this._output && this._output.el) {
|
|
195
|
+
this._output.el.innerHTML = '';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
activate() {
|
|
200
|
+
if (!this.__active) {
|
|
201
|
+
super.activate();
|
|
202
|
+
|
|
203
|
+
// Pause auto-scroll when user scrolls up
|
|
204
|
+
if (this._output && this._output.el) {
|
|
205
|
+
this._output.el.addEventListener('scroll', () => {
|
|
206
|
+
const el = this._output.el;
|
|
207
|
+
const at_bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30;
|
|
208
|
+
this._paused = !at_bottom;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Filter chip click handling
|
|
213
|
+
const chips = document.querySelectorAll('.log-filter-chip');
|
|
214
|
+
chips.forEach(chip => {
|
|
215
|
+
chip.addEventListener('click', () => {
|
|
216
|
+
chips.forEach(c => c.classList.remove('chip-active'));
|
|
217
|
+
chip.classList.add('chip-active');
|
|
218
|
+
const level = chip.getAttribute('data-level');
|
|
219
|
+
this._apply_filter(level);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_apply_filter(level) {
|
|
226
|
+
if (!this._output || !this._output.el) return;
|
|
227
|
+
const lines = this._output.el.querySelectorAll('.log-line');
|
|
228
|
+
lines.forEach(line => {
|
|
229
|
+
if (level === 'all') {
|
|
230
|
+
line.style.display = '';
|
|
231
|
+
} else {
|
|
232
|
+
line.style.display = line.classList.contains(`log-${level}`) ? '' : 'none';
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### CSS
|
|
240
|
+
|
|
241
|
+
```css
|
|
242
|
+
.log_viewer {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.log-filter-bar {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 4px;
|
|
251
|
+
padding: 6px 8px;
|
|
252
|
+
border-bottom: 1px solid #E0DCD4;
|
|
253
|
+
background: #F4F2EC;
|
|
254
|
+
flex-wrap: wrap;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.log-filter-chip {
|
|
258
|
+
font-size: 8px;
|
|
259
|
+
padding: 2px 8px;
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
background: transparent;
|
|
263
|
+
color: #808080;
|
|
264
|
+
border: 1px solid transparent;
|
|
265
|
+
transition: all 0.15s;
|
|
266
|
+
text-transform: uppercase;
|
|
267
|
+
letter-spacing: 0.5px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.log-filter-chip:hover {
|
|
271
|
+
background: rgba(68, 136, 204, 0.1);
|
|
272
|
+
color: #4488CC;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.log-filter-chip.chip-active {
|
|
276
|
+
background: rgba(68, 136, 204, 0.15);
|
|
277
|
+
border-color: #4488CC;
|
|
278
|
+
color: #2A5A8A;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.log-output {
|
|
282
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
283
|
+
font-size: 8.5px;
|
|
284
|
+
line-height: 1.8;
|
|
285
|
+
overflow-y: auto;
|
|
286
|
+
max-height: 300px;
|
|
287
|
+
padding: 4px 8px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.log-line {
|
|
291
|
+
display: flex;
|
|
292
|
+
gap: 12px;
|
|
293
|
+
padding: 1px 0;
|
|
294
|
+
border-bottom: 0.5px solid transparent;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.log-line:hover {
|
|
298
|
+
background: rgba(0, 0, 0, 0.02);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.log-timestamp {
|
|
302
|
+
color: #808080;
|
|
303
|
+
min-width: 60px;
|
|
304
|
+
flex-shrink: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.log-message {
|
|
308
|
+
flex: 1;
|
|
309
|
+
word-break: break-word;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Level-based coloring */
|
|
313
|
+
.log-info .log-message { color: #2A4060; }
|
|
314
|
+
.log-warn .log-message { color: #D8A020; }
|
|
315
|
+
.log-error .log-message { color: #CC4444; }
|
|
316
|
+
.log-debug .log-message { color: #808080; }
|
|
317
|
+
|
|
318
|
+
.log-info .log-timestamp { color: #808080; }
|
|
319
|
+
.log-warn .log-timestamp { color: #B08800; }
|
|
320
|
+
.log-error .log-timestamp { color: #AA3333; }
|
|
321
|
+
|
|
322
|
+
/* Green highlights for success messages */
|
|
323
|
+
.log-line.log-success .log-message { color: #2A6A2A; }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Activity Feed Entries
|
|
329
|
+
|
|
330
|
+
The log viewer receives entries from multiple SSE event types:
|
|
331
|
+
|
|
332
|
+
### Request Entries
|
|
333
|
+
|
|
334
|
+
From SSE event `request`:
|
|
335
|
+
```javascript
|
|
336
|
+
event_source.addEventListener('request', (e) => {
|
|
337
|
+
const data = JSON.parse(e.data);
|
|
338
|
+
log_viewer.append_request(data);
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Rendered as:
|
|
343
|
+
```
|
|
344
|
+
14:22:45 GET / — 200 OK (14ms)
|
|
345
|
+
14:22:38 POST /api/validateUser — 200 OK (3ms)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Resource State Change Entries
|
|
349
|
+
|
|
350
|
+
From SSE event `resource_state_change`:
|
|
351
|
+
```javascript
|
|
352
|
+
event_source.addEventListener('resource_state_change', (e) => {
|
|
353
|
+
const data = JSON.parse(e.data);
|
|
354
|
+
log_viewer.append_resource_event(data);
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Rendered as:
|
|
359
|
+
```
|
|
360
|
+
14:20:12 Resource "Bundle Builder": stopped → running
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Build Entries
|
|
364
|
+
|
|
365
|
+
From SSE event `build_complete`:
|
|
366
|
+
```javascript
|
|
367
|
+
event_source.addEventListener('build_complete', (e) => {
|
|
368
|
+
const data = JSON.parse(e.data);
|
|
369
|
+
log_viewer.append_build_event(data);
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Rendered as:
|
|
374
|
+
```
|
|
375
|
+
14:23:08 Bundle built: JS 245 KB, CSS 12 KB
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Warning/Error Entries
|
|
379
|
+
|
|
380
|
+
From SSE events `unhealthy`, `crashed`:
|
|
381
|
+
```javascript
|
|
382
|
+
event_source.addEventListener('crashed', (e) => {
|
|
383
|
+
const data = JSON.parse(e.data);
|
|
384
|
+
log_viewer.append({
|
|
385
|
+
timestamp: data.timestamp,
|
|
386
|
+
level: 'error',
|
|
387
|
+
source: 'resource',
|
|
388
|
+
message: `Resource "${data.resourceName}" crashed (exit code: ${data.code})`
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Full Log View
|
|
396
|
+
|
|
397
|
+
When the user navigates to the "Logs" sidebar item, a dedicated full-page Log_Viewer is shown with:
|
|
398
|
+
- Larger output area (full content height)
|
|
399
|
+
- Search bar
|
|
400
|
+
- Export button
|
|
401
|
+
- Source filter (request, resource, build, all)
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const full_log_viewer = new Log_Viewer({
|
|
405
|
+
context,
|
|
406
|
+
max_lines: 2000,
|
|
407
|
+
show_timestamps: true,
|
|
408
|
+
auto_scroll: true,
|
|
409
|
+
show_filter: true
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Search
|
|
414
|
+
|
|
415
|
+
The search function filters visible entries by text match:
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
_search(query) {
|
|
419
|
+
if (!this._output || !this._output.el) return;
|
|
420
|
+
const lines = this._output.el.querySelectorAll('.log-line');
|
|
421
|
+
const q = query.toLowerCase();
|
|
422
|
+
lines.forEach(line => {
|
|
423
|
+
const msg = line.querySelector('.log-message');
|
|
424
|
+
if (!msg) return;
|
|
425
|
+
const text = msg.innerText.toLowerCase();
|
|
426
|
+
line.style.display = text.includes(q) ? '' : 'none';
|
|
427
|
+
// Highlight match (simple approach)
|
|
428
|
+
if (q && text.includes(q)) {
|
|
429
|
+
line.style.borderBottom = '0.5px solid rgba(68, 136, 204, 0.3)';
|
|
430
|
+
} else {
|
|
431
|
+
line.style.borderBottom = '';
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Log Entry Schema
|
|
440
|
+
|
|
441
|
+
All log entries follow a consistent internal schema:
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
interface Log_Entry {
|
|
445
|
+
timestamp: number; // Unix milliseconds
|
|
446
|
+
level: 'info' | 'warn' | 'error' | 'debug' | 'success';
|
|
447
|
+
source: 'request' | 'resource' | 'build' | 'system' | 'user';
|
|
448
|
+
message: string; // Human-readable message
|
|
449
|
+
metadata?: { // Optional structured data
|
|
450
|
+
method?: string;
|
|
451
|
+
url?: string;
|
|
452
|
+
status?: number;
|
|
453
|
+
duration_ms?: number;
|
|
454
|
+
resource_name?: string;
|
|
455
|
+
[key: string]: any;
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
This schema ensures consistent rendering regardless of the entry source.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Performance Considerations
|
|
465
|
+
|
|
466
|
+
- **Circular buffer**: Entries beyond `max_lines` are dropped from both the array and the DOM. Default: 500 lines for the dashboard widget, 2000 for the full log view.
|
|
467
|
+
- **DOM recycling**: When entries exceed the max, the first child is removed from the DOM before appending a new one. This prevents unbounded DOM growth.
|
|
468
|
+
- **Throttling**: When SSE events arrive rapidly (e.g., during load testing), the log viewer batches DOM updates with `requestAnimationFrame` to avoid layout thrashing.
|
|
469
|
+
- **Auto-scroll pause**: Auto-scrolling is paused when the user scrolls up, to allow reading historical entries without being yanked to the bottom.
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
// Throttled append for high-frequency updates
|
|
473
|
+
_throttled_append(entry) {
|
|
474
|
+
this._pending_entries = this._pending_entries || [];
|
|
475
|
+
this._pending_entries.push(entry);
|
|
476
|
+
|
|
477
|
+
if (!this._flush_scheduled) {
|
|
478
|
+
this._flush_scheduled = true;
|
|
479
|
+
requestAnimationFrame(() => {
|
|
480
|
+
this._pending_entries.forEach(e => this._render_entry(e));
|
|
481
|
+
this._pending_entries = [];
|
|
482
|
+
this._flush_scheduled = false;
|
|
483
|
+
|
|
484
|
+
if (this._auto_scroll && !this._paused && this._output.el) {
|
|
485
|
+
this._output.el.scrollTop = this._output.el.scrollHeight;
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|