domma-cms 1.6.60 → 1.7.0
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/CHANGELOG.md +4344 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Dashboard Rebuild Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-06
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
**Scope:** Replace the current admin dashboard with a site-aware command centre that surfaces traffic, journeys, spikes, content state, and combined site/server health.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
The existing admin dashboard (`admin/js/views/dashboard.js`) shows three count cards (Total / Published / Drafts) and a Recent Pages table. It tells the operator nothing about how the site is performing - no hits, no journeys, no spike detection, no health signal. For a single-site CMS this is the natural landing page; it should be the place the operator opens first to know whether the site is OK and where attention is needed.
|
|
10
|
+
|
|
11
|
+
## Goals
|
|
12
|
+
|
|
13
|
+
- One-glance answer to: *Is the site healthy? What's getting traffic? Where are spikes? What's stale?*
|
|
14
|
+
- Single network call on mount; auto-refresh only the volatile tiles (KPIs + spike feed).
|
|
15
|
+
- Each widget self-contained so we can add/remove without touching the orchestrator.
|
|
16
|
+
- Privacy-preserving - no IP, no UA, no PII; honour DNT; sessionStorage-only session IDs.
|
|
17
|
+
|
|
18
|
+
## Non-Goals (YAGNI)
|
|
19
|
+
|
|
20
|
+
- Geographic / IP-derived data
|
|
21
|
+
- Browser / device breakdown (no UA parsing)
|
|
22
|
+
- Multi-day cohort retention or returning-visitor tracking
|
|
23
|
+
- Per-user custom widget layouts
|
|
24
|
+
- A pluggable widget framework (defer until a 2nd plugin actually wants to contribute widgets)
|
|
25
|
+
|
|
26
|
+
## Layout
|
|
27
|
+
|
|
28
|
+
12-column grid, four rows:
|
|
29
|
+
|
|
30
|
+
| Row | Content | Cols |
|
|
31
|
+
|-----|---------------------------------------------------------------------------------------|------|
|
|
32
|
+
| 1 | KPI strip: Hits today (Δ vs yesterday) · Live sessions · Submissions/week · Health | 4×3 |
|
|
33
|
+
| 2 | Traffic chart (30d trend + today hourly overlay, spike points annotated) · Spike feed | 8 / 4 |
|
|
34
|
+
| 3 | Top pages (sparkline + Δ%) · User journeys (entry / exit / top 2-step paths) | 6 / 6 |
|
|
35
|
+
| 4 | Site & server health detail · Activity feed (edits, publishes, submissions) | 6 / 6 |
|
|
36
|
+
|
|
37
|
+
## Data Model - Analytics Plugin Extensions
|
|
38
|
+
|
|
39
|
+
### Beacon
|
|
40
|
+
|
|
41
|
+
`public/inject-body.html` currently posts `{url, dnt}`. Extend to:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{ "url": "/about", "dnt": false, "ref": "https://example.com/", "sid": "<uuid>" }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `sid` - generated client-side via `crypto.randomUUID()` on first hit, stored in `sessionStorage` under `dm_sid`. Clears on tab close. No cross-session tracking.
|
|
48
|
+
- `ref` - `document.referrer || ''`. Stripped to origin + path (no query, no fragment) before send.
|
|
49
|
+
|
|
50
|
+
### Storage
|
|
51
|
+
|
|
52
|
+
Two new files in `plugins/analytics/`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
journeys.json { "YYYY-MM-DD": [ { sid, t, url, ref } ] }
|
|
56
|
+
spikes-cache.json { "lastBuiltAt": iso, "items": [ { url, ratio, hits, baseline } ] }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `journeys.json` is daily-capped (configurable, default **5,000 events/day**). When the cap is hit for the day, further events are dropped silently - keeps disk bounded.
|
|
60
|
+
- `spikes-cache.json` is rebuilt on demand by `/spikes` and cached for 60s; avoids re-scanning daily files on every dashboard refresh.
|
|
61
|
+
|
|
62
|
+
Existing `lifetime.json` and `daily.json` are unchanged - extension is additive.
|
|
63
|
+
|
|
64
|
+
### New Endpoints (mounted at `/api/plugins/analytics`)
|
|
65
|
+
|
|
66
|
+
| Method | Path | Auth | Purpose |
|
|
67
|
+
|--------|-------------|-------|----------------------------------------------------------------------------------|
|
|
68
|
+
| GET | `/journeys` | Admin | `?range=7d` - top entry pages, top exit pages, top 2-step paths, bounce rate |
|
|
69
|
+
| GET | `/realtime` | Admin | Distinct `sid` count active in last 5 min |
|
|
70
|
+
| GET | `/spikes` | Admin | Current-hour hits per URL vs trailing 7-day same-hour baseline; flag where ratio > 2σ |
|
|
71
|
+
|
|
72
|
+
#### Journeys derivation
|
|
73
|
+
|
|
74
|
+
For the requested range, group `journeys.json` events by `sid`, sort by `t`. Then:
|
|
75
|
+
- **Entry page** = first URL in each session
|
|
76
|
+
- **Exit page** = last URL in each session
|
|
77
|
+
- **2-step path** = each consecutive `(url[i], url[i+1])` pair
|
|
78
|
+
- **Bounce rate** = sessions of length 1 ÷ total sessions
|
|
79
|
+
|
|
80
|
+
Aggregate; return top 10 of each category plus the bounce-rate scalar.
|
|
81
|
+
|
|
82
|
+
#### Spike detection
|
|
83
|
+
|
|
84
|
+
For each URL with hits in the current hour:
|
|
85
|
+
- Baseline = mean hits at this hour-of-day across the previous 7 days
|
|
86
|
+
- StdDev = sample stddev across same window
|
|
87
|
+
- If `currentHits > baseline + 2*stddev` AND `currentHits >= 5` (noise floor), flag
|
|
88
|
+
- Return up to 5 flagged URLs sorted by `currentHits / baseline` ratio descending
|
|
89
|
+
|
|
90
|
+
### Privacy
|
|
91
|
+
|
|
92
|
+
- `sid` lives only in `sessionStorage` - closes with the tab; no cookie banner triggered.
|
|
93
|
+
- No IP logging, no UA, no fingerprinting.
|
|
94
|
+
- DNT continues to short-circuit at the beacon.
|
|
95
|
+
- Reset button (existing) wipes `journeys.json` along with the others.
|
|
96
|
+
|
|
97
|
+
## Data Model - Core Dashboard Endpoint
|
|
98
|
+
|
|
99
|
+
### `GET /api/dashboard/summary`
|
|
100
|
+
|
|
101
|
+
**Auth:** authenticate + requireAdmin
|
|
102
|
+
|
|
103
|
+
**Response:**
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"traffic": {
|
|
108
|
+
"today": 1234,
|
|
109
|
+
"yesterday": 980,
|
|
110
|
+
"deltaPct": 25.9,
|
|
111
|
+
"weekToDate": 8200,
|
|
112
|
+
"previousWeek": 7600,
|
|
113
|
+
"hourlyToday": [{ "hour": 0, "hits": 12 }, ...]
|
|
114
|
+
},
|
|
115
|
+
"topPages": [
|
|
116
|
+
{ "url": "/about", "hits": 432, "deltaPct": 12.4, "spark": [10, 14, ...] }
|
|
117
|
+
],
|
|
118
|
+
"journeys": {
|
|
119
|
+
"entry": [{ "url": "/", "count": 800 }, ...],
|
|
120
|
+
"exit": [{ "url": "/contact", "count": 120 }, ...],
|
|
121
|
+
"paths": [{ "from": "/", "to": "/pricing", "count": 90 }, ...],
|
|
122
|
+
"bounceRate": 0.42
|
|
123
|
+
},
|
|
124
|
+
"spikes": [{ "url": "/pricing", "ratio": 4.12, "hits": 312, "baseline": 76 }],
|
|
125
|
+
"realtime": { "activeSessions": 7 },
|
|
126
|
+
"health": {
|
|
127
|
+
"status": "warn",
|
|
128
|
+
"checks": [
|
|
129
|
+
{ "id": "uptime", "level": "ok", "label": "Uptime", "value": "3d 4h" },
|
|
130
|
+
{ "id": "disk-content", "level": "ok", "label": "Content disk", "value": "142 MB" },
|
|
131
|
+
{ "id": "memory", "level": "ok", "label": "Heap", "value": "84 / 512 MB" },
|
|
132
|
+
{ "id": "stale-pages", "level": "warn", "label": "Stale pages (>90d)", "value": 4, "detail": ["/about", "/legal", ...] },
|
|
133
|
+
{ "id": "missing-meta", "level": "warn", "label": "Pages missing description", "value": 2 },
|
|
134
|
+
{ "id": "last-form", "level": "ok", "label": "Last form submission", "value": "2h ago" },
|
|
135
|
+
{ "id": "smtp", "level": "ok", "label": "Last email send", "value": "2026-05-06 09:12" },
|
|
136
|
+
{ "id": "plugin-load", "level": "ok", "label": "Plugin failures", "value": 0 }
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"activity": [
|
|
140
|
+
{ "type": "edit", "at": "2026-05-06T09:14:00Z", "actor": "darryl", "target": "/about" },
|
|
141
|
+
{ "type": "publish", "at": "...", "actor": "...", "target": "..." },
|
|
142
|
+
{ "type": "form", "at": "...", "form": "contact" }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Aggregation sources
|
|
148
|
+
|
|
149
|
+
| Section | Source |
|
|
150
|
+
|--------------|-------------------------------------------------------------------|
|
|
151
|
+
| traffic | analytics plugin `daily.json` (today, yesterday, weeks) |
|
|
152
|
+
| topPages | analytics plugin `daily.json` (last 7d, sparkline = per-day) |
|
|
153
|
+
| journeys | analytics plugin `journeys.json` via internal helper |
|
|
154
|
+
| spikes | analytics plugin `spikes-cache.json` |
|
|
155
|
+
| realtime | analytics plugin `journeys.json` (sids in last 5 min) |
|
|
156
|
+
| health | new `server/services/health.js` (see below) |
|
|
157
|
+
| activity | versions service + form submissions (no auth audit log exists yet) |
|
|
158
|
+
|
|
159
|
+
### `server/services/health.js`
|
|
160
|
+
|
|
161
|
+
Computes all health checks. Each check returns `{ id, level: 'ok'\|'warn'\|'fail', label, value, detail? }`. Top-level `status` = worst level across checks (`fail` > `warn` > `ok`).
|
|
162
|
+
|
|
163
|
+
Checks implemented:
|
|
164
|
+
|
|
165
|
+
- **uptime** - `process.uptime()`, formatted
|
|
166
|
+
- **disk-content** - recursive size of `content/` directory
|
|
167
|
+
- **memory** - `process.memoryUsage().heapUsed / heapTotal`
|
|
168
|
+
- **stale-pages** - pages whose `updatedAt` is >90 days ago; counts + lists up to 10
|
|
169
|
+
- **missing-meta** - pages without `description` in frontmatter
|
|
170
|
+
- **last-form** - newest submission across `content/forms/*/submissions/`; warn if >7 days, fail if no submissions ever AND any forms exist
|
|
171
|
+
- **smtp** - read last result from `server/services/email.js` (add a small `lastSendResult` export)
|
|
172
|
+
- **plugin-load** - count of plugins that failed manifest validation at startup (already logged; expose via `plugins.js`)
|
|
173
|
+
|
|
174
|
+
Cross-cutting: results cached for 30s in-memory inside the service to avoid recomputing on every poll.
|
|
175
|
+
|
|
176
|
+
### Cross-plugin coupling
|
|
177
|
+
|
|
178
|
+
The dashboard endpoint imports the analytics plugin's helpers directly. To avoid making `server/` depend on `plugins/analytics/` (which violates the upstream-replaced boundary), the analytics plugin **registers an internal aggregator on the Fastify instance**:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
fastify.decorate('analytics', {
|
|
182
|
+
getTraffic, getTopPages, getJourneys, getSpikes, getRealtime
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`/api/dashboard/summary` reads `request.server.analytics?.getTraffic(...)` and tolerates the analytics plugin being absent (returns `null` for those sections; the dashboard renders empty-state placeholders).
|
|
187
|
+
|
|
188
|
+
## Frontend Architecture
|
|
189
|
+
|
|
190
|
+
### File structure
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
admin/js/views/dashboard.js Orchestrator: fetch summary, mount widgets, schedule refresh
|
|
194
|
+
admin/js/templates/dashboard.html Grid skeleton with named slot divs
|
|
195
|
+
admin/js/views/dashboard/widgets/
|
|
196
|
+
kpi-strip.js / kpi-strip.html Row 1
|
|
197
|
+
traffic-chart.js / traffic-chart.html Row 2 left
|
|
198
|
+
spike-feed.js / spike-feed.html Row 2 right
|
|
199
|
+
top-pages.js / top-pages.html Row 3 left
|
|
200
|
+
journeys.js / journeys.html Row 3 right
|
|
201
|
+
health-detail.js / health-detail.html Row 4 left
|
|
202
|
+
activity-feed.js / activity-feed.html Row 4 right
|
|
203
|
+
admin/css/dashboard.css Grid + tile styling
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Widget contract
|
|
207
|
+
|
|
208
|
+
Each widget exports:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
export default {
|
|
212
|
+
selector: '#dash-kpi-strip',
|
|
213
|
+
templateUrl: '/admin/js/templates/dashboard/kpi-strip.html',
|
|
214
|
+
render(data) { /* receives the relevant slice of /summary; returns nothing, mutates DOM via Domma */ }
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The orchestrator loads the template into the slot once, then calls `render(slice)` on initial mount and each refresh. Widgets do not fetch their own data - the orchestrator hands them what they need.
|
|
219
|
+
|
|
220
|
+
### Refresh
|
|
221
|
+
|
|
222
|
+
Single `setInterval(30_000)` in the orchestrator hits `/api/dashboard/summary?lite=1` (which returns only `traffic`, `realtime`, `spikes`, `health.status`). Re-renders KPI strip, spike feed, and the health pill. Other widgets are static after mount.
|
|
223
|
+
|
|
224
|
+
### Charts
|
|
225
|
+
|
|
226
|
+
Reuse the existing analytics-plugin Chart.js cache pattern (CDN with one-time load + module-level promise so we don't double-load). Sparklines in the top-pages widget use inline SVG (lightweight; Chart.js for one mini chart is overkill).
|
|
227
|
+
|
|
228
|
+
### Empty states
|
|
229
|
+
|
|
230
|
+
Every widget must render a zero-state when its data slice is empty (no hits yet, no journeys yet, no spikes detected). Show "No data yet" with a hint, not an empty box.
|
|
231
|
+
|
|
232
|
+
## Error Handling
|
|
233
|
+
|
|
234
|
+
- `/api/dashboard/summary` returns 200 with partial data even if a sub-aggregator throws. Each section is wrapped in `try/catch`; on failure, that key is `null` and a `warnings: ["traffic: <message>"]` array is populated.
|
|
235
|
+
- Frontend orchestrator surfaces warnings as a single dismissable banner above the grid, not per-widget toasts.
|
|
236
|
+
- Widget render functions must tolerate `null` slices (render the empty state).
|
|
237
|
+
|
|
238
|
+
## Testing
|
|
239
|
+
|
|
240
|
+
- `server/services/health.js` - unit tests for each check with mocked filesystem / process state.
|
|
241
|
+
- Analytics plugin journey aggregation - unit tests with seeded `journeys.json` fixtures (entry/exit/paths/bounce).
|
|
242
|
+
- Spike detector - unit tests across edge cases (no baseline, all zeros, single high hour).
|
|
243
|
+
- `/api/dashboard/summary` - integration test asserting graceful degradation when analytics plugin is disabled (sections are `null`, status is 200).
|
|
244
|
+
- Frontend widget rendering - manual browser verification per the user's standing rule (no "verified" claims without a real browser load).
|
|
245
|
+
|
|
246
|
+
## Implementation Order
|
|
247
|
+
|
|
248
|
+
1. **Analytics extensions** - beacon update, `journeys.json` storage, `/journeys` `/realtime` `/spikes` endpoints, internal `fastify.analytics` decorator. Ship and verify with the existing analytics admin view (still works).
|
|
249
|
+
2. **Health service** - `server/services/health.js` with all checks + caching.
|
|
250
|
+
3. **Dashboard summary endpoint** - `server/routes/api/dashboard.js` aggregating all sources.
|
|
251
|
+
4. **Dashboard frontend** - new template + orchestrator + 7 widgets + CSS. Last because it depends on the API surface above.
|
|
252
|
+
|
|
253
|
+
Each step is independently verifiable.
|
|
254
|
+
|
|
255
|
+
## Open Questions
|
|
256
|
+
|
|
257
|
+
None - all scope decisions resolved during brainstorm.
|