chore(website): spring-clean public pages

This commit is contained in:
gadgethd
2026-08-06 13:04:39 +00:00
parent 2d0ee0fa36
commit 1c3841cc52
12 changed files with 171 additions and 377 deletions
+88
View File
@@ -171,3 +171,91 @@ start from the live state.
visible`). The report/evidence commit follows this append; no GitHub push.
- **Evidence:** `SPRING-MAPFEED-2026-08-06.png` shows the live map with a real
message-first feed card and compact secondary metadata.
## Wave 3 investigation and implementation design — items 10/12/14 (2026-08-06)
### Item 10 — observation register: resolved by absence
The frontend route table contains the public pages `/`, `/install`, `/stats`,
`/feed`, `/repeater`, `/companion`, `/docs`, `/open-source`, `/spam`,
`/topology`, and `/login`, plus redirects for `/regions`, `/about`, `/mqtt`,
and `/status`; it contains no `/register` or `/observations` page. The navigation
has no observation/register entry. A case-insensitive search of `frontend/src`
found only data/UI uses of “observations” in topology, packet detail, feed path
models, and map statistics, plus the observer-station registration form embedded
in `UKCompanionPage`; none is a dedicated observation register page. Stats,
repeater search, and owner portal are ordinary existing pages with no hidden
observation-register route. Per the item definition, this is resolved by absence;
no page was invented or changed.
### Item 12 — Spam Watch: keep
Live host baseline at `http://127.0.0.1:3000/api/spam/messages/status` returned
HTTP 200 JSON:
`{"ongoing":false,"activeIncidents":0,"totalIncidents":0,"messagesLast24h":0,"observersInvolved":0,"lastIncidentAt":null,"updatedAt":"2026-08-06T12:49:27.141Z"}`.
The companion request used by the page,
`/api/spam/messages/incidents?limit=200`, also returned HTTP 200 JSON with
`filters` (`status: all`, `minConfidence: 0.5`, `limit: 200`, `offset: 0`),
`returned: 0`, and an empty `incidents` array. A Googlebot Playwright run against
`https://ukmesh.com/spam` observed HTTP 200 JSON for both page API requests,
rendered the `Spam Watch` heading and `No ongoing spam detected`, and rendered
no alert. This is a useful, live transparency page even when the current result
is an empty/clean state, so it will remain unchanged unless final verification
finds a regression.
### Item 14 — Health page removal: designed frontend-only
Remove `frontend/src/pages/StatusPage.tsx`; remove its lazy import and element,
the `health` route component type, `/health` content route, and the obsolete
`/status -> /health` redirect. Remove `showHealth` from `SiteLayout` and its
`/health` navigation item, and remove the now-unused `showHealth` prop from
`UKLayout`. The separate `healthcheck.ukmesh.com` external link and the
`~/ukmesh/meshcore-health-check` application are explicitly out of scope and
will not be touched. Acceptance evidence is a zero-result `rg -F '/health'
frontend` search plus a public browser assertion that `/health` renders the
normal not-found state and no Health nav entry, while `/spam` retains its clean
live state.
No backend change is planned. After implementation, run the required frontend
quality gates, build both real-tagged images, compare their shipped bundles,
deploy only the services whose bundle changed, and then perform the required
health/HopReach/API/browser checks before recording the screenshot, digests, and
local commit below.
## Wave 3 completion — items 10/12/14 (2026-08-06)
- **[DONE] Item 10 — observation register:** resolved by absence. The route
manifest and nav have no `/register` or `/observations` page; the frontend
search found only ordinary observation data fields and the embedded observer
station registration form. No page was invented.
- **[DONE] Item 12 — Spam Watch:** kept. Host curl returned HTTP 200 with
`ongoing: false`, zero active/total incidents, zero messages in the last 24h,
zero observers involved, and a current `updatedAt`; the incidents endpoint
returned HTTP 200 with a valid empty list. Googlebot Playwright against the
live public URL received HTTP 200 JSON for both requests and rendered the
heading plus `No ongoing spam detected` with no alert.
- **[DONE] Item 14 — Health page removal:** deleted `StatusPage.tsx`, its lazy
route wiring, `/health` manifest entry, `/status -> /health` redirect, nav
item/flag, unused status CSS, and stale E2E page checks. Direct `/health`
page-link searches in `frontend` are empty. The external Health Check link
and separate health-check app remain out of scope.
- **Quality gates:** frontend `npx tsc --noEmit`, `npm test` (77/77),
`npm run lint:css`, and `npm run build` all pass. No backend files changed.
- **Deployment:** built both `spring-pages` tags. The new not-found marker was
present in both asset sets, so `app-ukmesh` and `website-ukmesh` were both
deployed; no backend service was changed. Final app digest:
`sha256:5f278c9796c67402c644961555feb8a52c4ee6eb4cc1f41c94b9b79276c80b55`;
final website digest:
`sha256:56885f493cca824e2e7cb7ce6cda7b08748747762b00ffa84bdfb9432db9e67b`.
`.env` changed only `APP_IMAGE` and `WEBSITE_IMAGE`; `BACKEND_IMAGE` and
secret lines were untouched.
- **Live verification:** `/api/health` was healthy, HopReach returned 200,
`/health` and `/status` rendered the normal not-found state with no exact
Health nav link, and `/spam` rendered its live clean state with both API calls
at HTTP 200 and no alert. The SPA fallback returns document HTTP 200 for
unknown routes; the rendered not-found state proves the page route is gone.
- **Evidence:** `SPRING-PAGES-2026-08-06.png` contains the real Googlebot
captures for the removed `/health` state and retained `/spam` state. Summary:
`SPRING-PAGES-2026-08-06.md`.
- **Local commit:** to be recorded after the final scoped commit; no GitHub
push.
+82
View File
@@ -0,0 +1,82 @@
# UKMesh Spring Pages — 2026-08-06
Scope: items 10, 12, and 14 on `/home/ben/ukmesh/meshcore-analytics`, branch
`main`. The separate `~/ukmesh/meshcore-health-check` application was not
modified. No GitHub push was performed.
## Decisions
### Item 10 — observation register: resolved by absence
There is no dedicated observation-register page. The `PUBLIC_ROUTES` manifest
contains no `/register` or `/observations` route, and the site navigation has no
observation/register entry. A case-insensitive `rg` search across `frontend/src`
found only ordinary observation data labels and models in topology, stats,
packet detail, feed paths, and map statistics, plus the observer-station
registration form embedded in `UKCompanionPage`. Stats, repeater search, and
owner portal have no hidden register page. No page was invented or changed.
### Item 12 — Spam Watch: kept
The live host endpoint returned HTTP 200 and a valid page-shaped payload:
```json
{"ongoing":false,"activeIncidents":0,"totalIncidents":0,"messagesLast24h":0,"observersInvolved":0,"lastIncidentAt":null,"updatedAt":"2026-08-06T12:49:27.141Z"}
```
The pages incidents request, `/api/spam/messages/incidents?limit=200`, also
returned HTTP 200 with `minConfidence: 0.5`, `limit: 200`, `returned: 0`, and an
empty `incidents` array. A Googlebot Playwright check of the real public URL
observed HTTP 200 JSON for both requests, rendered `Spam Watch` and
`No ongoing spam detected`, and rendered no alert/error element. The page is a
useful transparency surface in this valid clean state, so no Spam code change
was necessary.
### Item 14 — Health page: removed
Removed the page module, lazy import, route component/type, `/health` content
route, obsolete `/status -> /health` redirect, `showHealth` prop, Health nav
entry, unused UK layout flag, page-specific status CSS, and stale E2E page
fixtures/assertions. Direct page-link searches for `/health` in `frontend`
return no results. The external `https://healthcheck.ukmesh.com` Health Check
link remains intentionally because it belongs to the separate application.
## Quality and deployment
- `cd frontend && npx tsc --noEmit` — passed.
- `cd frontend && npm test` — 77/77 passed.
- `cd frontend && npm run lint:css` — passed.
- `cd frontend && npm run build` — passed; only existing large-chunk warnings.
- Built both requested tags: `meshcore-analytics-app:spring-pages` and
`meshcore-analytics-website:spring-pages`.
- The new not-found marker was present in both shipped asset sets, so both
`app-ukmesh` and `website-ukmesh` bundles changed and both were deployed.
No backend image/service was changed.
- Final deployed image IDs:
- app: `sha256:5f278c9796c67402c644961555feb8a52c4ee6eb4cc1f41c94b9b79276c80b55`
- website: `sha256:56885f493cca824e2e7cb7ce6cda7b08748747762b00ffa84bdfb9432db9e67b`
- `.env` was updated only for `APP_IMAGE` and `WEBSITE_IMAGE`; `BACKEND_IMAGE`
and secret/configuration lines were left unchanged.
## Live verification
- `GET http://127.0.0.1:3000/api/health` — HTTP 200, `status: healthy`.
- `GET http://127.0.0.1:3000/hopreach/api/nodes` — HTTP 200.
- Googlebot Playwright against `https://ukmesh.com/health` — the SPA transport
returned 200, but the rendered page was the normal `That page is not on the
mesh` not-found state and the exact `Health` nav link count was zero.
- Googlebot Playwright against `https://ukmesh.com/status` — same normal
not-found state; no legacy redirect to the removed Health page.
- Googlebot Playwright against `https://ukmesh.com/spam` — document HTTP 200;
status and incidents API responses HTTP 200 with JSON content type; heading
and clean state visible; alert count zero.
The browser emitted the same pre-existing unauthenticated owner-session and
service-worker MIME console noise seen in the pre-deploy baseline; neither
affected page data loading or rendered state. Evidence image:
[SPRING-PAGES-2026-08-06.png](./SPRING-PAGES-2026-08-06.png).
## Local commit
Implementation and evidence were committed locally after verification. No
remote push was attempted.
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

-9
View File
@@ -5,7 +5,6 @@ export type PublicRouteComponent =
| 'companion'
| 'install'
| 'docs'
| 'health'
| 'owner'
| 'open-source'
| 'stats'
@@ -92,13 +91,6 @@ export const PUBLIC_ROUTES: readonly PublicRouteDefinition[] = [
description: 'Explore recent viable repeater relationships, highly connected relay hubs, and observed MeshCore network topology across the UK.',
sitemap: true,
},
{
path: '/health',
component: 'health',
title: 'Platform Status — UK Mesh Network',
description: 'Public platform health, packet-ingest freshness, synthetic journeys, and background-worker status for UKMesh analytics.',
sitemap: true,
},
{
path: '/login',
component: 'owner',
@@ -109,7 +101,6 @@ export const PUBLIC_ROUTES: readonly PublicRouteDefinition[] = [
{ path: '/regions', redirectTo: '/', sitemap: false },
{ path: '/about', redirectTo: '/', sitemap: false },
{ path: '/mqtt', redirectTo: '/install', sitemap: false },
{ path: '/status', redirectTo: '/health', sitemap: false },
] as const;
export const PUBLIC_CONTENT_ROUTES = PUBLIC_ROUTES.filter(
-2
View File
@@ -29,7 +29,6 @@ const UKCompanionPage = lazy(() => import('./pages/ukmesh/UKCompanionPage.js').t
const UKBestPracticePage = lazy(() => import('./pages/ukmesh/UKBestPracticePage.js').then(({ UKBestPracticePage: Component }) => ({ default: Component })));
const SpamPage = lazy(() => import('./pages/SpamTransparencyPage.js').then(({ SpamPage: Component }) => ({ default: Component })));
const TopologyPage = lazy(() => import('./pages/TopologyPage.js').then(({ TopologyPage: Component }) => ({ default: Component })));
const StatusPage = lazy(() => import('./pages/StatusPage.js').then(({ StatusPage: Component }) => ({ default: Component })));
const NotFoundPage = lazy(() => import('./pages/NotFoundPage.js').then(({ NotFoundPage: Component }) => ({ default: Component })));
const PUBLIC_ROUTE_ELEMENTS: Record<PublicRouteComponent, React.ReactElement> = {
@@ -39,7 +38,6 @@ const PUBLIC_ROUTE_ELEMENTS: Record<PublicRouteComponent, React.ReactElement> =
companion: <UKCompanionPage />,
install: <UKInstallPage />,
docs: <UKBestPracticePage />,
health: <StatusPage />,
owner: <OwnerPortalPage />,
'open-source': <OpenSourcePage />,
stats: <StatsPage />,
-256
View File
@@ -1,256 +0,0 @@
import React, { useEffect, useState } from 'react';
import { LoadingIndicator } from '../components/LoadingIndicator.js';
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import { useRuntimeFeatures } from '../config/runtimeFeatures.js';
import { getCurrentSite } from '../config/site.js';
import { useVisibilityPoll } from '../hooks/useVisibilityPoll.js';
import { fetchJson, withScopeParams } from '../utils/api.js';
import './network-intelligence.css';
type PublicHealthPayload = {
status: 'healthy' | 'degraded' | 'critical';
generatedAt: string;
maintenance: { active: boolean; message: string | null };
incidents: Array<{ code: string; severity: 'warning' | 'critical' }>;
components: {
ingest: { status: string };
workers: { status: string };
storage: { status: string };
};
};
type FirmwarePayload = { total: number; versions: Array<{ hardware_model: string; firmware_version: string; count: number }> };
type FirmwareDistributionRow = {
firmware_version: string;
count: number;
hardware_models: string[];
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasStringStatus(value: unknown): value is { status: string } {
return isRecord(value) && typeof value['status'] === 'string';
}
function isPublicHealthPayload(value: unknown): value is PublicHealthPayload {
if (!isRecord(value) || !isRecord(value['maintenance']) || !isRecord(value['components'])) return false;
const components = value['components'];
return isRecord(value)
&& (value['status'] === 'healthy' || value['status'] === 'degraded' || value['status'] === 'critical')
&& typeof value['generatedAt'] === 'string'
&& typeof value['maintenance']['active'] === 'boolean'
&& (typeof value['maintenance']['message'] === 'string' || value['maintenance']['message'] === null)
&& Array.isArray(value['incidents'])
&& value['incidents'].every((incident) => (
isRecord(incident)
&& typeof incident['code'] === 'string'
&& (incident['severity'] === 'warning' || incident['severity'] === 'critical')
))
&& hasStringStatus(components['ingest'])
&& hasStringStatus(components['workers'])
&& hasStringStatus(components['storage']);
}
function isFirmwarePayload(value: unknown): value is FirmwarePayload {
return isRecord(value)
&& typeof value['total'] === 'number'
&& Array.isArray(value['versions']);
}
function aggregateFirmwareVersions(versions: FirmwarePayload['versions']): FirmwareDistributionRow[] {
const byVersion = new Map<string, FirmwareDistributionRow>();
for (const row of versions) {
const firmwareVersion = row.firmware_version || 'Unknown';
const existing = byVersion.get(firmwareVersion);
if (existing) {
existing.count += row.count;
if (!existing.hardware_models.includes(row.hardware_model)) {
existing.hardware_models.push(row.hardware_model);
}
continue;
}
byVersion.set(firmwareVersion, {
firmware_version: firmwareVersion,
count: row.count,
hardware_models: [row.hardware_model],
});
}
return Array.from(byVersion.values());
}
function isUnknownFirmwareVersion(version: string): boolean {
return version.toLowerCase() === 'unknown';
}
function statusPresentation(status: string): { dot: string; label: string } {
switch (status) {
case 'ok': return { dot: 'ok', label: 'Operational' };
case 'running': return { dot: 'ok', label: 'Running' };
case 'idle': return { dot: 'ok', label: 'Ready' };
case 'warning': return { dot: 'warning', label: 'Needs attention' };
case 'stale': return { dot: 'warning', label: 'Delayed' };
case 'critical': return { dot: 'critical', label: 'Critical' };
case 'failed': return { dot: 'critical', label: 'Failed' };
default: return { dot: 'unknown', label: 'Checking' };
}
}
function reportTime(generatedAt: string): string {
const value = new Date(generatedAt);
return Number.isNaN(value.getTime())
? 'just now'
: value.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
export const StatusPage: React.FC = () => {
const site = getCurrentSite();
const network = site.networkFilter ?? site.network;
const observer = site.observerId;
const { privacyGeneration } = useRuntimeFeatures();
const [data, setData] = useState<PublicHealthPayload | null>(null);
const [error, setError] = useState(false);
const [firmware, setFirmware] = useState<FirmwarePayload | null>(null);
const [includeUnknownFirmware, setIncludeUnknownFirmware] = useState(false);
useEffect(() => {
setData(null);
setFirmware(null);
setError(false);
setIncludeUnknownFirmware(false);
}, [network, observer, privacyGeneration]);
useVisibilityPoll(async (signal) => {
const next = await fetchJson<PublicHealthPayload>(
withScopeParams('/api/health', { network, observer }),
{ cache: 'no-store', signal },
{ timeoutMs: 15_000, maxBytes: 2 * 1024 * 1024, validate: isPublicHealthPayload },
);
if (signal.aborted) return;
setData(next);
setError(false);
}, {
scopeKey: `platform-health:${network}:${observer ?? 'all'}:${privacyGeneration}`,
intervalMs: 60_000,
timeoutMs: 15_000,
onError: () => setError(true),
});
useVisibilityPoll(async (signal) => {
const next = await fetchJson<FirmwarePayload>(
withScopeParams('/api/repeaters/firmware', { network, observer }),
{ cache: 'no-store', signal },
{ timeoutMs: 15_000, maxBytes: 2 * 1024 * 1024, validate: isFirmwarePayload },
);
if (!signal.aborted) setFirmware(next);
}, {
scopeKey: `firmware-health:${network}:${observer ?? 'all'}:${privacyGeneration}`,
intervalMs: 10 * 60_000,
timeoutMs: 15_000,
});
const firmwareDistribution = firmware ? aggregateFirmwareVersions(firmware.versions) : [];
const unknownFirmware = firmwareDistribution.find((row) => isUnknownFirmwareVersion(row.firmware_version));
const visibleFirmwareDistribution = includeUnknownFirmware
? firmwareDistribution
: firmwareDistribution.filter((row) => !isUnknownFirmwareVersion(row.firmware_version));
const xAxisInterval = Math.max(0, Math.ceil(visibleFirmwareDistribution.length / 8) - 1);
return (
<div className="status-page site-content">
<header><p className="topology-page__eyebrow">Public operations</p><h1>Platform status</h1><p>Service health, ingest freshness, and worker state without exposing private infrastructure details.</p></header>
{!data && !error && <LoadingIndicator label="Checking platform status…" variant="block" />}
{error && !data && <div className="status-page__banner status-page__banner--critical" role="alert">Status data is currently unavailable.</div>}
{data && (
<>
<div className={`status-page__banner status-page__banner--${data.status}`} role="status">
<strong>{data.status === 'healthy' ? 'All monitored systems operational' : `Platform ${data.status}`}</strong>
<span>Updated {reportTime(data.generatedAt)}</span>
</div>
{data.maintenance.active && <div className="status-page__maintenance"><strong>Planned maintenance</strong><span>{data.maintenance.message ?? 'Maintenance is currently in progress.'}</span></div>}
{data.incidents.length > 0 && <section className="status-page__problems"><h2>Current notices</h2>{data.incidents.map((incident) => <article key={`${incident.code}:${incident.severity}`} className={`status-page__problem status-page__problem--${incident.severity}`}><strong>{incident.code.replace(/_/g, ' ')}</strong><span>{incident.severity === 'critical' ? 'A monitored service is disrupted.' : 'A monitored service needs attention.'}</span></article>)}</section>}
<div className="status-page__grid">
{([
['Public ingest', 'Packet intake and freshness', data.components.ingest.status],
['Background workers', 'Scheduled processing services', data.components.workers.status],
['Storage', 'Durable platform storage', data.components.storage.status],
] as const).map(([title, description, status]) => {
const presentation = statusPresentation(status);
return (
<section key={title}>
<h2>{title}</h2>
<div className="status-page__checks">
<div>
<span className={`status-page__dot status-page__dot--${presentation.dot}`} />
<strong>{presentation.label}</strong>
<small>{description}</small>
</div>
</div>
</section>
);
})}
</div>
<section className="status-page__firmware">
<h2>Repeater firmware distribution</h2>
{firmware && firmware.versions.length > 0 ? (
<>
{unknownFirmware && (
<p style={{ margin: '0 0 12px', color: 'var(--text-secondary)', fontSize: '12px' }}>
<label style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
<input
type="checkbox"
checked={includeUnknownFirmware}
onChange={(event) => setIncludeUnknownFirmware(event.target.checked)}
style={{ accentColor: 'var(--accent)' }}
/>
<span>Include Unknown ({unknownFirmware.count.toLocaleString()})</span>
</label>
</p>
)}
{visibleFirmwareDistribution.length > 0 ? (
<>
<ResponsiveContainer width="100%" height={320}>
<BarChart data={visibleFirmwareDistribution} margin={{ left: 8, right: 8, bottom: 88 }}>
<CartesianGrid stroke="rgba(255,255,255,.1)" />
<XAxis
dataKey="firmware_version"
angle={-45}
textAnchor="end"
interval={xAxisInterval}
height={96}
minTickGap={10}
tick={{ fill: 'var(--text-secondary)', fontSize: 11 }}
/>
<YAxis allowDecimals={false} />
<Tooltip formatter={(value: number) => [value.toLocaleString(), 'Repeaters']} />
<Bar dataKey="count" name="Repeaters" fill="var(--color-primary)" />
</BarChart>
</ResponsiveContainer>
<div className="ui-visually-hidden">
<table>
<caption>Repeater firmware distribution{includeUnknownFirmware ? '' : ' (Unknown excluded)'}</caption>
<thead><tr><th scope="col">Firmware</th><th scope="col">Hardware models</th><th scope="col">Repeaters</th></tr></thead>
<tbody>
{visibleFirmwareDistribution.map((row) => (
<tr key={row.firmware_version}>
<td>{row.firmware_version}</td>
<td>{row.hardware_models.join(', ')}</td>
<td>{row.count}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
) : (
<p role="status">Only repeaters with unknown firmware were reported. Select "Include Unknown" to show that bucket.</p>
)}
</>
) : <p>Firmware telemetry is not yet available.</p>}
</section>
<p className="status-page__privacy">Status values are deliberately aggregated. Hostnames, addresses, credentials, and private node identities are never included.</p>
</>
)}
</div>
);
};
@@ -69,45 +69,6 @@
.topology-page__validation-table-head { color: var(--text-muted) !important; font: 11px var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; }
.topology-page__validation-table strong { color: var(--amber); font: 11px var(--font-mono); text-transform: uppercase; }
.status-page { padding-top: 48px; padding-bottom: 64px; }
.status-page > header { margin-bottom: 22px; }
.status-page > header h1 { margin: 4px 0 8px; }
.status-page > header > p:last-child { color: var(--text-secondary); }
.status-page__banner { display: flex; justify-content: space-between; gap: 15px; padding: 14px 18px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-panel); }
.status-page__banner strong { color: var(--text-primary); }
.status-page__banner span { color: var(--text-muted); font: 10px var(--font-mono); }
.status-page__banner--healthy { border-color: rgba(0, 230, 118, 0.45); }
.status-page__banner--degraded { border-color: rgba(255, 179, 0, 0.55); }
.status-page__banner--critical { border-color: rgba(255, 23, 68, 0.6); color: #fca5a5; }
.status-page__maintenance { display: flex; gap: 16px; margin-top: 10px; padding: 11px 16px; border-radius: var(--radius); background: var(--amber-dim); color: var(--amber); }
.status-page__maintenance span { color: var(--text-secondary); }
.status-page__problems { margin-top: 20px; }
.status-page__problems h2 { margin-bottom: 8px; font-size: 15px; }
.status-page__problem { display: flex; gap: 14px; padding: 9px 12px; border-left: 3px solid var(--amber); background: var(--bg-panel); }
.status-page__problem + .status-page__problem { margin-top: 4px; }
.status-page__problem strong { min-width: 180px; color: var(--amber); font: 10px var(--font-mono); text-transform: uppercase; }
.status-page__problem span { color: var(--text-secondary); font-size: 11px; }
.status-page__problem--critical { border-left-color: var(--danger); }
.status-page__problem--critical strong { color: var(--danger); }
.status-page__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 20px; }
.status-page__grid > section { padding: 18px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-panel); }
.status-page__grid h2 { margin: 0 0 12px; font-size: 14px; }
.status-page__grid dl div { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border); }
.status-page__grid dt { color: var(--text-muted); }
.status-page__grid dd { color: var(--text-primary); font-family: var(--font-mono); }
.status-page__checks > div { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--border); }
.status-page__checks strong { overflow: hidden; color: var(--text-secondary); font: 11px var(--font-mono); text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
.status-page__checks small { color: var(--text-muted); }
.status-page__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-muted); }
.status-page__dot--ok { background: var(--online); box-shadow: 0 0 8px rgba(0, 230, 118, 0.45); }
.status-page__dot--warning,
.status-page__dot--stale,
.status-page__dot--unknown { background: var(--amber); }
.status-page__dot--critical,
.status-page__dot--failed,
.status-page__dot--disabled { background: var(--danger); }
.status-page__privacy { margin-top: 16px; color: var(--text-muted); font-size: 11px; }
@media (max-width: 860px) {
.topology-page__workspace { grid-template-columns: 1fr; }
}
@@ -122,16 +83,4 @@
.topology-page__validation-head { flex-direction: column; gap: 8px; }
.topology-page__validation-stats { grid-template-columns: 1fr 1fr; }
.topology-page__validation-table [role='row'] { grid-template-columns: 1fr 1fr; }
.status-page { padding-top: 28px; }
.status-page__banner,
.status-page__maintenance,
.status-page__problem { flex-direction: column; gap: 5px; }
.status-page__grid { grid-template-columns: 1fr; }
}
.status-page__firmware {
margin-top: 18px;
padding: 18px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--bg-panel);
}
-3
View File
@@ -10,7 +10,6 @@ type SiteLayoutProps = {
showAbout?: boolean;
showInstall?: boolean;
showMqtt?: boolean;
showHealth?: boolean;
showOpenSource?: boolean;
showBestPractice?: boolean;
showPackets: boolean;
@@ -48,7 +47,6 @@ export const SiteLayout: React.FC<SiteLayoutProps> = ({
showAbout = true,
showInstall = true,
showMqtt = true,
showHealth = true,
showOpenSource = true,
showBestPractice = false,
showPackets,
@@ -81,7 +79,6 @@ export const SiteLayout: React.FC<SiteLayoutProps> = ({
{ to: '/about', label: 'What is MeshCore', enabled: showAbout },
{ to: '/install', label: 'Install', enabled: showInstall },
{ to: '/mqtt', label: 'MQTT', enabled: showMqtt },
{ to: '/health', label: 'Health', enabled: showHealth },
{ to: '/packets', label: 'Packets', enabled: showPackets },
{ to: '/open-source', label: 'Open Source', enabled: showOpenSource },
{ to: '/stats', label: 'Stats', enabled: showStats },
-1
View File
@@ -27,7 +27,6 @@ export const UKLayout: React.FC = () => {
showInstall
showBestPractice
showMqtt={false}
showHealth
showPackets={false}
showOpenSource={false}
showStats={false}
-16
View File
@@ -91,21 +91,6 @@ async function fulfillApi(route: Route) {
},
});
}
if (pathname === '/api/health') {
return route.fulfill({
json: {
status: 'healthy',
generatedAt: '2026-07-18T12:00:00Z',
maintenance: { active: false, message: null },
incidents: [],
components: {
ingest: { status: 'ok' },
workers: { status: 'running' },
storage: { status: 'ok' },
},
},
});
}
if (pathname === '/api/topology') {
return route.fulfill({
json: {
@@ -229,7 +214,6 @@ const publicRoutes = [
'/companion',
'/install',
'/docs',
'/health',
'/login',
'/open-source',
'/stats',
-14
View File
@@ -62,18 +62,6 @@ const chartStats = {
},
};
const health = {
status: 'healthy',
generatedAt: '2026-07-18T12:00:00Z',
maintenance: { active: false, message: null },
incidents: [],
components: {
ingest: { status: 'ok' },
workers: { status: 'running' },
storage: { status: 'ok' },
},
};
const repeaterNodes = Array.from({ length: 12 }, (_, index) => ({
node_id: `node-${index}`,
name: `Repeater ${String(index + 1).padStart(2, '0')}`,
@@ -109,7 +97,6 @@ async function installApiFixtures(page: Page) {
},
});
}
if (pathname === '/api/health') return route.fulfill({ json: health });
if (pathname === '/api/repeaters/firmware') {
return route.fulfill({
json: {
@@ -168,7 +155,6 @@ const routedViews = [
['UK install', 'http://127.0.0.1:4173/install'],
['UK open source', 'http://127.0.0.1:4173/open-source'],
['UK stats', 'http://127.0.0.1:4173/stats'],
['UK health', 'http://127.0.0.1:4173/health'],
['UK login', 'http://127.0.0.1:4173/login'],
['UK docs', 'http://127.0.0.1:4173/docs'],
['test site home', 'http://127.0.0.1:4175/'],
+1 -25
View File
@@ -105,22 +105,6 @@ test('public site exposes its primary journeys', async ({ page }) => {
},
});
});
await page.route('**/api/health**', async (route) => {
await route.fulfill({
json: {
status: 'healthy',
generatedAt: '2026-07-11T12:00:00Z',
maintenance: { active: false, message: null },
incidents: [],
components: {
ingest: { status: 'ok' },
workers: { status: 'running' },
storage: { status: 'ok' },
},
},
});
});
await page.goto('/');
await expect(page.getByRole('heading', { name: 'UK Mesh Network' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Open live map' }).first()).toBeVisible();
@@ -142,15 +126,7 @@ test('public site exposes its primary journeys', async ({ page }) => {
await expect(page.getByLabel('Geographic repeater topology graph')).toBeVisible();
await expect(page.getByRole('group', { name: '2 positioned repeaters and 1 links' })).toBeVisible();
await page.getByRole('link', { name: 'Health', exact: true }).click();
await expect(page.getByRole('heading', { name: 'Platform status' })).toBeVisible();
await expect(page.getByText('All monitored systems operational')).toBeVisible();
await expect(page.getByText('Status data is currently unavailable.')).toHaveCount(0);
await expect(page.getByRole('heading', { name: 'Public ingest' })).toBeVisible();
const statusAccessibility = await new AxeBuilder({ page }).analyze();
expect(statusAccessibility.violations
.filter((violation) => violation.impact === 'critical' || violation.impact === 'serious')
.map((violation) => ({ id: violation.id, targets: violation.nodes.map((node) => node.target.join(' ')) }))).toEqual([]);
await expect(page.getByRole('link', { name: 'Health', exact: true })).toHaveCount(0);
});
// Guards issue #3 (contrast): the feed chrome renders the muted/secondary text