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.
Files changed (106) hide show
  1. package/.github/agents/Mobile Developer.agent.md +89 -0
  2. package/.github/instructions/copilot.instructions.md +1 -0
  3. package/AGENTS.md +6 -0
  4. package/README.md +185 -0
  5. package/admin-ui/client.js +73 -43
  6. package/admin-ui/v1/admin_auth_service.js +197 -0
  7. package/admin-ui/v1/admin_user_store.js +71 -0
  8. package/admin-ui/v1/client.js +17 -0
  9. package/admin-ui/v1/controls/admin_shell.js +1399 -0
  10. package/admin-ui/v1/controls/group_box.js +84 -0
  11. package/admin-ui/v1/controls/stat_card.js +125 -0
  12. package/admin-ui/v1/server.js +658 -0
  13. package/admin-ui/v1/utils/formatters.js +68 -0
  14. package/docs/admin-extension-guide.md +345 -0
  15. package/docs/api-reference.md +383 -303
  16. package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
  17. package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
  18. package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
  19. package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
  20. package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
  21. package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
  22. package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
  23. package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
  24. package/docs/books/adaptive-control-improvements/README.md +66 -0
  25. package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
  26. package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
  27. package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
  28. package/docs/books/admin-ui-authentication/README.md +25 -0
  29. package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
  30. package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
  31. package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
  32. package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
  33. package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
  34. package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
  35. package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
  36. package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
  37. package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
  38. package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
  39. package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
  40. package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
  41. package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
  42. package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
  43. package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
  44. package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
  45. package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
  46. package/docs/books/creating-a-new-admin-ui/README.md +68 -0
  47. package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
  48. package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
  49. package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
  50. package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
  51. package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
  52. package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
  53. package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
  54. package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
  55. package/docs/books/device-adaptive-composition/README.md +47 -0
  56. package/docs/comparison-report-express-plex-cpanel.md +549 -0
  57. package/docs/comprehensive-documentation.md +220 -220
  58. package/docs/configuration-reference.md +227 -204
  59. package/docs/designs/server-admin-interface-aero.svg +611 -0
  60. package/docs/middleware-guide.md +236 -0
  61. package/docs/system-architecture.md +24 -18
  62. package/docs/troubleshooting.md +84 -53
  63. package/middleware/compression.js +217 -0
  64. package/middleware/index.js +15 -0
  65. package/module.js +19 -11
  66. package/package.json +1 -1
  67. package/serve-factory.js +29 -0
  68. package/server.js +280 -20
  69. package/tests/README.md +5 -0
  70. package/tests/admin-ui-jsgui-controls.test.js +581 -0
  71. package/tests/test-runner.js +1 -0
  72. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
  73. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
  74. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
  75. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
  76. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
  77. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
  78. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
  79. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
  80. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
  81. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
  82. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
  83. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
  84. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
  85. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
  86. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
  87. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
  88. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
  89. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
  90. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
  91. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
  92. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
  93. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
  94. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
  95. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
  96. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
  97. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
  98. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
  99. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
  100. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
  101. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
  102. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
  103. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
  104. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
  105. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
  106. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +0 -39
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: Mobile Developer
3
+
4
+ description: Device-adaptive UI specialist for jsgui3-html. Uses the Device-Adaptive Composition book to guide responsive layout, composition, theming, and testing across phone, tablet, and desktop.
5
+
6
+ tools: [vscode, execute, read, agent, edit, search, web, 'playwright/*', todo]
7
+
8
+
9
+ argument-hint: A responsive/adaptive UI task, layout question, or multi-device composition problem.
10
+ ---
11
+ # Mission
12
+ For any work involving responsive layout, multi-device composition, adaptive styling, or mobile/tablet support in jsgui3-html, anchor all decisions in the **Device-Adaptive Composition & Styling** book at `docs/books/device-adaptive-composition/`.
13
+
14
+ # Reference Book — Required Reading
15
+
16
+ Before starting any task, read the relevant chapters:
17
+
18
+ | Chapter | File | When to consult |
19
+ |---------|------|----------------|
20
+ | 1 — Platform Feature Audit | `docs/books/device-adaptive-composition/01-platform-feature-audit.md` | Understanding what layout primitives, tokens, and MVVM infrastructure already exist |
21
+ | 2 — Responsive Composition Model | `docs/books/device-adaptive-composition/02-responsive-composition-model.md` | Designing adaptive shells, choosing between CSS and JS composition, the four-layer model |
22
+ | 3 — Data Model vs View Model | `docs/books/device-adaptive-composition/03-data-model-vs-view-model.md` | Deciding where adaptive state lives (view.data.model, never data.model) |
23
+ | 4 — Styling, Themes, and Breakpoints | `docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md` | Token overrides, density modes, mode attributes, touch target sizing |
24
+ | 5 — Showcase App Assessment | `docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md` | Understanding how the showcase app should adapt per device category |
25
+ | 6 — Implementation Patterns and APIs | `docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md` | Using View_Environment, compose_adaptive(), responsive params, container-aware utilities |
26
+ | 7 — Testing Harness and Quality Gates | `docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md` | Writing viewport-matrix Playwright tests, assertion categories (P0/P1/P2) |
27
+ | 8 — Roadmap and Adoption Plan | `docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md` | Phased rollout priorities, what to build vs what to skip |
28
+
29
+ # Non-negotiables
30
+
31
+ - **Consult the book first**: before proposing any adaptive/responsive solution, read the relevant chapter(s) and identify which patterns apply.
32
+ - **Use the four-layer model** (Chapter 2): separate Domain Composition, View Composition, Adaptive Resolution, and Concrete Render concerns.
33
+ - **Keep adaptive state in view models** (Chapter 3): never put layout_mode, density, viewport dimensions, or panel visibility in `data.model`.
34
+ - **Use mode attributes over scattered breakpoints** (Chapter 4): prefer `[data-layout-mode="phone"]` CSS selectors over raw `@media` queries.
35
+ - **Follow the snake_case convention**: all variables, methods, and file names use snake_case per AGENTS.md.
36
+ - **Name the pattern(s)** from the book that you're applying and cite the chapter.
37
+
38
+ # When this applies
39
+
40
+ - Any change involving responsive layout, adaptive composition, or multi-device support
41
+ - Adding or modifying layout primitives (Stack, Drawer, Grid_Gap, Split_Pane)
42
+ - Implementing density modes, touch target sizing, or interaction-mode awareness
43
+ - Writing viewport-matrix Playwright tests
44
+ - Theme profile work involving density or layout-mode token overrides
45
+ - Assessing how a control or app behaves on phone, tablet, or desktop
46
+
47
+ # Key Concepts from the Book
48
+
49
+ ## Four-Layer Composition (Chapter 2)
50
+ - **Layer A** (Domain): business data — device-agnostic, lives in `data.model`
51
+ - **Layer B** (View): regions and component hierarchy — expresses adaptive intent
52
+ - **Layer C** (Adaptive Resolution): environment service resolves mode from viewport/input/preferences
53
+ - **Layer D** (Concrete Render): resolved CSS classes, token values, DOM attributes
54
+
55
+ ## Environment Contract (Chapters 2, 6)
56
+ ```js
57
+ context.view_environment = {
58
+ viewport: { width, height, orientation },
59
+ layout_mode: 'phone' | 'tablet' | 'desktop',
60
+ density_mode: 'compact' | 'cozy' | 'comfortable',
61
+ interaction_mode: 'touch' | 'pointer' | 'hybrid',
62
+ motion_mode: 'normal' | 'reduced'
63
+ };
64
+
65
+ Composition Helper (Chapter 6)
66
+
67
+ compose_adaptive(this, {
68
+ phone: () => this.compose_phone_shell(),
69
+ tablet: () => this.compose_tablet_shell(),
70
+ desktop: () => this.compose_desktop_shell()
71
+ });
72
+
73
+ Viewport Test Matrix (Chapter 7)
74
+ Phone portrait (390×844), Phone landscape (844×390), Tablet portrait (768×1024), Tablet landscape (1024×768), Desktop narrow (1280×720), Desktop wide (1920×1080).
75
+
76
+ If the book doesn't cover it, consult:
77
+ AGENTS.md — project-wide naming conventions, testing patterns, coding style
78
+ docs/accessibility_and_semantics.md — WCAG/ARIA guidance
79
+ control_mixins/keyboard_navigation.js — orientation-aware keyboard handling
80
+ css/jsgui-tokens.css — current token definitions
81
+ controls/organised/AGENT.md — control creation and theming guide
82
+ Output format for adaptive UI tasks
83
+ Include:
84
+
85
+ Book reference: which chapter(s) and pattern(s) apply
86
+ Layer analysis: which of the four layers (A/B/C/D) are affected
87
+ Model placement: what state goes in data.model vs view.data.model vs view.model
88
+ Composition approach: CSS-only (continuous) vs JS composition (discrete) vs both
89
+ Test coverage: which viewport profiles to test, which assertion categories (P0/P1/P2)
@@ -176,5 +176,6 @@ model.count++; // Triggers change event
176
176
  | Comprehensive API | `docs/comprehensive-documentation.md` |
177
177
  | Control development | `docs/controls-development.md` |
178
178
  | Publisher system | `docs/publishers-guide.md` |
179
+ | Middleware & compression | `docs/middleware-guide.md` |
179
180
  | Troubleshooting | `docs/troubleshooting.md` |
180
181
  | **Broken stuff** | `docs/agent-development-guide.md` |
package/AGENTS.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - **[docs/controls-development.md](docs/controls-development.md)** - Guide for developing custom JSGUI3 controls
20
20
  - **[docs/publishers-guide.md](docs/publishers-guide.md)** - Guide for publishers and content serving
21
21
  - **[docs/resources-guide.md](docs/resources-guide.md)** - Guide for resources and data abstraction
22
+ - **[docs/middleware-guide.md](docs/middleware-guide.md)** - Middleware pipeline and built-in compression middleware
22
23
 
23
24
  ### Specialized Documentation
24
25
  - **[docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md](docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md)** - Comprehensive guide to agentic workflows and autonomous task execution
@@ -36,6 +37,9 @@
36
37
  - **[docs/agent-development-guide.md](docs/agent-development-guide.md)** - Guide for AI agents working on this codebase
37
38
  - **[docs/broken-functionality-tracker.md](docs/broken-functionality-tracker.md)** - Tracker for broken/incomplete functionality
38
39
 
40
+ ### Admin UI
41
+ - **[docs/admin-extension-guide.md](docs/admin-extension-guide.md)** - Admin UI extension API: custom sections, endpoints, plugins, exported classes
42
+
39
43
  ### Review and Maintenance
40
44
  - **[docs/documentation-review/CURRENT_REVIEW.md](docs/documentation-review/CURRENT_REVIEW.md)** - Current documentation review status and known issues
41
45
 
@@ -58,6 +62,8 @@
58
62
  - **Custom control development** → `docs/controls-development.md`
59
63
  - **Publisher system** → `docs/publishers-guide.md`
60
64
  - **Resource management** → `docs/resources-guide.md`
65
+ - **Middleware and compression** → `docs/middleware-guide.md`
66
+ - **Admin UI extensions** → `docs/admin-extension-guide.md`
61
67
 
62
68
  ### Agent Development
63
69
  - **Agentic workflow patterns** → `docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md`
package/README.md CHANGED
@@ -168,6 +168,191 @@ sse_publisher.broadcast('resource_update', { running: 3, total: 5 });
168
168
 
169
169
  > **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.
170
170
 
171
+ ## Admin UI Dashboard
172
+
173
+ Every jsgui3-server instance includes a built-in admin dashboard at `/admin/v1` with live stats, resource inspection, route listing, and SSE-driven heartbeat updates. The dashboard is session-authenticated (default dev credentials: `admin` / `admin`).
174
+
175
+ The admin shell is implemented with jsgui controls for navigation and dynamic panel rendering (control-first composition), and is covered by the interaction regression suite in `tests/admin-ui-jsgui-controls.test.js`.
176
+
177
+ ### Disabling the Admin UI
178
+
179
+ ```javascript
180
+ Server.serve({ Ctrl: MyControl, admin: false });
181
+
182
+ // or
183
+ Server.serve({ Ctrl: MyControl, admin: { enabled: false } });
184
+ ```
185
+
186
+ ### Adding Custom Sections
187
+
188
+ Custom sections appear in the admin sidebar. When clicked, the shell fetches data from the section's API endpoint and auto-renders it as a table (arrays) or key-value panel (objects).
189
+
190
+ ```javascript
191
+ const server = await Server.serve(MyControl);
192
+
193
+ server.admin_v1.add_section({
194
+ id: 'crawlers',
195
+ label: 'Crawlers',
196
+ icon: '\uD83D\uDD77\uFE0F',
197
+ api_path: '/api/admin/v1/crawlers',
198
+ handler: (req, res) => {
199
+ res.writeHead(200, { 'Content-Type': 'application/json' });
200
+ res.end(JSON.stringify([
201
+ { name: 'Site A', status: 'running', pages: 1234 },
202
+ { name: 'Site B', status: 'idle', pages: 0 }
203
+ ]));
204
+ }
205
+ });
206
+ ```
207
+
208
+ ### Adding Custom Protected Endpoints
209
+
210
+ ```javascript
211
+ server.admin_v1.add_endpoint({
212
+ path: '/api/admin/v1/crawlers/start',
213
+ role: 'admin_write',
214
+ handler: (req, res) => {
215
+ // start crawler logic
216
+ res.writeHead(200, { 'Content-Type': 'application/json' });
217
+ res.end(JSON.stringify({ ok: true }));
218
+ }
219
+ });
220
+ ```
221
+
222
+ ### Plugin Pattern
223
+
224
+ ```javascript
225
+ server.admin_v1.use((admin) => {
226
+ admin.add_section({
227
+ id: 'logs',
228
+ label: 'Logs',
229
+ icon: '\uD83D\uDCDC',
230
+ api_path: '/api/admin/v1/logs',
231
+ handler: (req, res) => {
232
+ res.writeHead(200, { 'Content-Type': 'application/json' });
233
+ res.end(JSON.stringify({ recent: ['log1', 'log2'] }));
234
+ }
235
+ });
236
+ });
237
+ ```
238
+
239
+ ### Admin UI Regression Test
240
+
241
+ ```bash
242
+ node tests/test-runner.js --test=admin-ui-jsgui-controls.test.js
243
+ ```
244
+
245
+ ### Declarative Configuration via `Server.serve()`
246
+
247
+ Custom sections and endpoints can also be declared in the `Server.serve()` call:
248
+
249
+ ```javascript
250
+ Server.serve({
251
+ Ctrl: MyControl,
252
+ port: 8080,
253
+ admin: {
254
+ sections: [
255
+ {
256
+ id: 'jobs',
257
+ label: 'Jobs',
258
+ icon: '\u2699\uFE0F',
259
+ api_path: '/api/admin/v1/jobs',
260
+ handler: (req, res) => {
261
+ res.writeHead(200, { 'Content-Type': 'application/json' });
262
+ res.end(JSON.stringify([{ name: 'nightly-sync', status: 'complete' }]));
263
+ }
264
+ }
265
+ ],
266
+ endpoints: [
267
+ {
268
+ path: '/api/admin/v1/jobs/run',
269
+ role: 'admin_write',
270
+ handler: (req, res) => {
271
+ res.writeHead(200, { 'Content-Type': 'application/json' });
272
+ res.end(JSON.stringify({ ok: true }));
273
+ }
274
+ }
275
+ ]
276
+ }
277
+ });
278
+ ```
279
+
280
+ ### Exported Admin Classes
281
+
282
+ For advanced customisation, the admin classes are exported from the package:
283
+
284
+ ```javascript
285
+ const Server = require('jsgui3-server');
286
+
287
+ // Available on the Server constructor:
288
+ Server.Admin_Module_V1 // Admin adapter (sections, endpoints, auth, SSE)
289
+ Server.Admin_Auth_Service // Session management, cookie handling, role checking
290
+ Server.Admin_User_Store // In-memory user credential store (scrypt)
291
+
292
+ // Also available from the npm module entry:
293
+ const jsgui = require('jsgui3-server');
294
+ jsgui.Admin_Module_V1
295
+ jsgui.Admin_Auth_Service
296
+ jsgui.Admin_User_Store
297
+ ```
298
+
299
+ See [Admin Extension Guide](docs/admin-extension-guide.md) for detailed API reference.
300
+
301
+ ## Middleware
302
+
303
+ jsgui3-server includes an Express-style `server.use()` middleware pipeline. Middleware runs before every request reaches the router.
304
+
305
+ ### Built-in Compression
306
+
307
+ Enable gzip/deflate/brotli compression for JSON, HTML, CSS, and JS responses:
308
+
309
+ ```javascript
310
+ const { compression } = require('jsgui3-server/middleware');
311
+
312
+ const server = new Server({ Ctrl: MyControl, src_path_client_js: __dirname + '/client.js' });
313
+ server.use(compression());
314
+ server.on('ready', () => server.start(8080));
315
+ ```
316
+
317
+ Or via `Server.serve()`:
318
+
319
+ ```javascript
320
+ Server.serve({
321
+ Ctrl: MyControl,
322
+ compression: true, // enable with defaults
323
+ port: 8080
324
+ });
325
+
326
+ // With options:
327
+ Server.serve({
328
+ Ctrl: MyControl,
329
+ compression: { threshold: 512 },
330
+ port: 8080
331
+ });
332
+ ```
333
+
334
+ ### Custom Middleware
335
+
336
+ ```javascript
337
+ // Request logger
338
+ server.use((req, res, next) => {
339
+ console.log(`${req.method} ${req.url}`);
340
+ next();
341
+ });
342
+
343
+ // Multiple middleware via Server.serve()
344
+ Server.serve({
345
+ Ctrl: MyControl,
346
+ middleware: [
347
+ (req, res, next) => { console.log(req.url); next(); },
348
+ compression({ threshold: 512 })
349
+ ],
350
+ port: 8080
351
+ });
352
+ ```
353
+
354
+ See [Middleware Guide](docs/middleware-guide.md) for the full API reference and response-wrapping patterns.
355
+
171
356
  ## Architecture Overview
172
357
 
173
358
  The server operates as a bridge between server-side JavaScript applications and browser clients, offering:
@@ -2,15 +2,19 @@ const jsgui = require('jsgui3-client');
2
2
  const { controls, Control, mixins } = jsgui;
3
3
  const Active_HTML_Document = require('../controls/Active_HTML_Document');
4
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
- }
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
+ this._menu_items = [];
12
+ this._section_labels = Object.create(null);
13
+ this._active_section = 'overview';
14
+
15
+ if (typeof this.body.add_class === 'function') {
16
+ this.body.add_class('admin-page');
17
+ }
14
18
 
15
19
  const compose = () => {
16
20
  // Sidebar
@@ -63,16 +67,60 @@ class Admin_Page extends Active_HTML_Document {
63
67
  }
64
68
  }
65
69
 
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
- }
70
+ _add_menu_item(label, id, active = false) {
71
+ const item = new controls.div({
72
+ context: this.context,
73
+ class: `menu-item ${active ? 'active' : ''}`
74
+ });
75
+ item.dom.attributes['data-id'] = id;
76
+ item.add(label);
77
+ this.menu.add(item);
78
+
79
+ this._section_labels[id] = label;
80
+ this._menu_items.push({
81
+ id,
82
+ label,
83
+ control: item
84
+ });
85
+
86
+ item.on('click', () => {
87
+ this._activate_menu_item(id);
88
+ });
89
+
90
+ return item;
91
+ }
92
+
93
+ _set_control_text(control, text) {
94
+ if (!control) return;
95
+ if (typeof control.clear === 'function') {
96
+ control.clear();
97
+ }
98
+ if (typeof control.add === 'function') {
99
+ control.add(String(text == null ? '' : text));
100
+ }
101
+ }
102
+
103
+ _set_active_menu_item(id) {
104
+ this._menu_items.forEach((menu_item) => {
105
+ if (!menu_item.control) return;
106
+ if (menu_item.id === id) {
107
+ menu_item.control.add_class('active');
108
+ } else {
109
+ menu_item.control.remove_class('active');
110
+ }
111
+ });
112
+ }
113
+
114
+ _activate_menu_item(id) {
115
+ this._active_section = id;
116
+ this._set_active_menu_item(id);
117
+
118
+ const label = this._section_labels[id] || id;
119
+ this._set_control_text(this.page_title, label);
120
+
121
+ // Placeholder navigation logic
122
+ console.log('Navigate to:', id);
123
+ }
76
124
 
77
125
  _render_overview() {
78
126
  // Clear main content area (content_panel is the admin-content div)
@@ -86,30 +134,12 @@ class Admin_Page extends Active_HTML_Document {
86
134
  this.content_panel.add(welcome);
87
135
  }
88
136
 
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
- }
137
+ activate() {
138
+ if (!this.__active) {
139
+ super.activate();
140
+ }
141
+ }
142
+ }
113
143
 
114
144
  Admin_Page.css = `
115
145
  * { box-sizing: border-box; margin: 0; padding: 0; }
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ class Admin_Auth_Service {
6
+ constructor(spec = {}) {
7
+ this.user_store = spec.user_store;
8
+ this.session_ttl_ms = Number.isFinite(spec.session_ttl_ms) ? spec.session_ttl_ms : (8 * 60 * 60 * 1000);
9
+ this.cookie_name = spec.cookie_name || 'jsgui_admin_v1_sid';
10
+ this.sessions = new Map();
11
+ }
12
+
13
+ _read_json_body(req) {
14
+ return new Promise((resolve, reject) => {
15
+ const chunks = [];
16
+ req.on('data', (chunk) => chunks.push(chunk));
17
+ req.on('end', () => {
18
+ if (chunks.length === 0) return resolve({});
19
+ try {
20
+ const text = Buffer.concat(chunks).toString('utf8');
21
+ resolve(text ? JSON.parse(text) : {});
22
+ } catch (error) {
23
+ reject(error);
24
+ }
25
+ });
26
+ req.on('error', reject);
27
+ });
28
+ }
29
+
30
+ _parse_cookies(req) {
31
+ const cookies = {};
32
+ const header = req && req.headers ? req.headers.cookie : '';
33
+ if (!header) return cookies;
34
+
35
+ const parts = header.split(';');
36
+ parts.forEach((part) => {
37
+ const idx = part.indexOf('=');
38
+ if (idx === -1) return;
39
+ const key = part.slice(0, idx).trim();
40
+ const value = part.slice(idx + 1).trim();
41
+ cookies[key] = decodeURIComponent(value);
42
+ });
43
+ return cookies;
44
+ }
45
+
46
+ _set_session_cookie(res, session_id) {
47
+ const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
48
+ const cookie_value = this.cookie_name + '=' + encodeURIComponent(session_id) + '; Path=/; HttpOnly; SameSite=Lax; Max-Age=' + Math.floor(this.session_ttl_ms / 1000) + secure;
49
+ res.setHeader('Set-Cookie', cookie_value);
50
+ }
51
+
52
+ _clear_session_cookie(res) {
53
+ const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
54
+ res.setHeader('Set-Cookie', this.cookie_name + '=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0' + secure);
55
+ }
56
+
57
+ _new_session_id() {
58
+ return crypto.randomBytes(24).toString('hex');
59
+ }
60
+
61
+ _cleanup_expired() {
62
+ const now = Date.now();
63
+ for (const [id, session] of this.sessions.entries()) {
64
+ if (!session || session.expires_at <= now) {
65
+ this.sessions.delete(id);
66
+ }
67
+ }
68
+ }
69
+
70
+ get_session(req) {
71
+ this._cleanup_expired();
72
+ const cookies = this._parse_cookies(req);
73
+ const sid = cookies[this.cookie_name];
74
+ if (!sid) return null;
75
+
76
+ const session = this.sessions.get(sid);
77
+ if (!session) return null;
78
+ if (session.expires_at <= Date.now()) {
79
+ this.sessions.delete(sid);
80
+ return null;
81
+ }
82
+ return {
83
+ session_id: sid,
84
+ user: session.user,
85
+ expires_at: session.expires_at
86
+ };
87
+ }
88
+
89
+ is_authenticated(req) {
90
+ return !!this.get_session(req);
91
+ }
92
+
93
+ has_role(req, role_name) {
94
+ const session = this.get_session(req);
95
+ if (!session || !session.user) return false;
96
+ const roles = Array.isArray(session.user.roles) ? session.user.roles : [];
97
+ return roles.includes(role_name);
98
+ }
99
+
100
+ has_any_role(req, role_names) {
101
+ const session = this.get_session(req);
102
+ if (!session || !session.user) return false;
103
+ const roles = Array.isArray(session.user.roles) ? session.user.roles : [];
104
+ if (!Array.isArray(role_names) || role_names.length === 0) return false;
105
+ return role_names.some((role_name) => roles.includes(role_name));
106
+ }
107
+
108
+ create_session(user, res) {
109
+ const session_id = this._new_session_id();
110
+ const expires_at = Date.now() + this.session_ttl_ms;
111
+ this.sessions.set(session_id, {
112
+ user,
113
+ created_at: Date.now(),
114
+ expires_at
115
+ });
116
+ this._set_session_cookie(res, session_id);
117
+ return { session_id, expires_at, user };
118
+ }
119
+
120
+ destroy_session(req, res) {
121
+ const cookies = this._parse_cookies(req);
122
+ const sid = cookies[this.cookie_name];
123
+ if (sid) this.sessions.delete(sid);
124
+ this._clear_session_cookie(res);
125
+ }
126
+
127
+ async handle_login(req, res) {
128
+ if (String(req.method || 'GET').toUpperCase() !== 'POST') {
129
+ res.writeHead(405, { 'Content-Type': 'application/json' });
130
+ res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
131
+ return;
132
+ }
133
+
134
+ let body;
135
+ try {
136
+ body = await this._read_json_body(req);
137
+ } catch (error) {
138
+ res.writeHead(400, { 'Content-Type': 'application/json' });
139
+ res.end(JSON.stringify({ ok: false, error: 'Invalid JSON body' }));
140
+ return;
141
+ }
142
+
143
+ const username = body.username;
144
+ const password = body.password;
145
+ const user = this.user_store.verify_credentials(username, password);
146
+ if (!user) {
147
+ res.writeHead(401, { 'Content-Type': 'application/json' });
148
+ res.end(JSON.stringify({ ok: false, error: 'Invalid credentials' }));
149
+ return;
150
+ }
151
+
152
+ const session = this.create_session(user, res);
153
+ res.writeHead(200, { 'Content-Type': 'application/json' });
154
+ res.end(JSON.stringify({
155
+ ok: true,
156
+ user: session.user,
157
+ expires_at: session.expires_at
158
+ }));
159
+ }
160
+
161
+ handle_logout(req, res) {
162
+ if (String(req.method || 'GET').toUpperCase() !== 'POST') {
163
+ res.writeHead(405, { 'Content-Type': 'application/json' });
164
+ res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
165
+ return;
166
+ }
167
+
168
+ this.destroy_session(req, res);
169
+ res.writeHead(200, { 'Content-Type': 'application/json' });
170
+ res.end(JSON.stringify({ ok: true }));
171
+ }
172
+
173
+ handle_session(req, res) {
174
+ if (String(req.method || 'GET').toUpperCase() !== 'GET') {
175
+ res.writeHead(405, { 'Content-Type': 'application/json' });
176
+ res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
177
+ return;
178
+ }
179
+
180
+ const session = this.get_session(req);
181
+ if (!session) {
182
+ res.writeHead(200, { 'Content-Type': 'application/json' });
183
+ res.end(JSON.stringify({ ok: true, authenticated: false }));
184
+ return;
185
+ }
186
+
187
+ res.writeHead(200, { 'Content-Type': 'application/json' });
188
+ res.end(JSON.stringify({
189
+ ok: true,
190
+ authenticated: true,
191
+ user: session.user,
192
+ expires_at: session.expires_at
193
+ }));
194
+ }
195
+ }
196
+
197
+ module.exports = Admin_Auth_Service;