mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
* Revert "revert: roll source tree back to v1.4.0 to restore production"
This reverts commit e444a90b10.
* fix(v1.4.2): summaries.WEIGHT + medications array guards + healthcheck IPv4
Three production bugs surfaced after the v1.4.0 deploy stabilized:
1. `TypeError: undefined is not an object (evaluating 'E?.summaries.WEIGHT')`
on /dashboard and /insights for users without weight readings —
the optional chain stopped at `data` when it needed to also cover
`data.summaries`. Fixed: `data?.summaries?.WEIGHT` (one level deeper).
2. `TypeError: y?.filter is not a function` on /medications — the
medications query value was non-array (likely a stale
service-worker response from an older API shape). Replaced with
`Array.isArray(medications) ? medications : []` so the page is
immune to any future shape drift.
3. Healthcheck-induced 503 outage (already in 1.4.1 — included here
so 1.4.2 is the single coherent release). The docker-compose
healthcheck used `localhost` which busybox-wget resolves to IPv6
::1; Next.js standalone listens on IPv4 0.0.0.0 only. Pinned
to 127.0.0.1.
This release also includes everything from v1.4.1 (which never
published a clean GHCR tag): the per-section admin extraction, the
testcontainers integration suite, the Playwright + axe-core E2E
foundation, the moodLog SSRF fix, the central error-redaction in
WideEventBuilder + Glitchtip, the medical-citation drift fixes
(ESH 2023, Saint-Maurice attribution, steps source), and the docs
sync.
Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
156 lines
4.5 KiB
TypeScript
156 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Activity,
|
|
AlertTriangle,
|
|
Bell,
|
|
BellRing,
|
|
Bug,
|
|
Clock,
|
|
Cog,
|
|
Database,
|
|
Globe,
|
|
Key,
|
|
Loader2,
|
|
Server,
|
|
Users,
|
|
} from "lucide-react";
|
|
import { formatDateTime } from "@/lib/format";
|
|
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
|
import { StatusItem, useSystemStatus } from "./_shared";
|
|
|
|
export function SystemStatusSection({ id }: { id: string }) {
|
|
const { t } = useTranslations();
|
|
const fmt = useFormatters();
|
|
const { data: status } = useSystemStatus();
|
|
|
|
return (
|
|
<div
|
|
id={id}
|
|
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<Server className="text-primary h-5 w-5" />
|
|
<h2 className="text-lg font-semibold">{t("admin.systemStatus")}</h2>
|
|
</div>
|
|
{status ? (
|
|
<div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<StatusItem
|
|
icon={Database}
|
|
label={t("admin.database")}
|
|
value={
|
|
status.database === "connected"
|
|
? t("admin.databaseConnected")
|
|
: t("admin.databaseError")
|
|
}
|
|
className={
|
|
status.database === "connected"
|
|
? "text-dracula-green"
|
|
: "text-destructive"
|
|
}
|
|
/>
|
|
<StatusItem
|
|
icon={Clock}
|
|
label={t("admin.startedAt")}
|
|
value={formatDateTime(status.startTime)}
|
|
/>
|
|
<StatusItem
|
|
icon={Users}
|
|
label={t("admin.users")}
|
|
value={String(status.counts.users)}
|
|
/>
|
|
<StatusItem
|
|
icon={Activity}
|
|
label={t("admin.measurementsCount")}
|
|
value={fmt.integer(status.counts.measurements)}
|
|
/>
|
|
<StatusItem
|
|
icon={Key}
|
|
label={t("admin.activeTokens")}
|
|
value={String(status.counts.activeTokens)}
|
|
/>
|
|
<StatusItem
|
|
icon={Globe}
|
|
label={t("admin.activeSessions")}
|
|
value={String(status.counts.activeSessions)}
|
|
/>
|
|
<StatusItem
|
|
icon={Cog}
|
|
label={t("admin.workerStatus")}
|
|
value={
|
|
status.worker.running
|
|
? t("admin.workerRunning")
|
|
: t("admin.workerStopped")
|
|
}
|
|
className={
|
|
status.worker.running ? "text-dracula-green" : "text-destructive"
|
|
}
|
|
/>
|
|
{status.worker.lastReminderCheck && (
|
|
<StatusItem
|
|
icon={Bell}
|
|
label={t("admin.lastReminderCheck")}
|
|
value={formatDateTime(status.worker.lastReminderCheck)}
|
|
/>
|
|
)}
|
|
{status.integrations.umami && (
|
|
<StatusItem
|
|
icon={Activity}
|
|
label="Umami"
|
|
value={
|
|
status.integrations.umami.enabled
|
|
? t("common.active")
|
|
: t("common.disabled")
|
|
}
|
|
className={
|
|
status.integrations.umami.enabled
|
|
? "text-dracula-green"
|
|
: "text-destructive"
|
|
}
|
|
/>
|
|
)}
|
|
{status.integrations.glitchtip && (
|
|
<StatusItem
|
|
icon={AlertTriangle}
|
|
label="GlitchTip"
|
|
value={
|
|
status.integrations.glitchtip.enabled
|
|
? t("common.active")
|
|
: t("common.disabled")
|
|
}
|
|
className={
|
|
status.integrations.glitchtip.enabled
|
|
? "text-dracula-green"
|
|
: "text-destructive"
|
|
}
|
|
/>
|
|
)}
|
|
{status.integrations.webPush && (
|
|
<StatusItem
|
|
icon={BellRing}
|
|
label={t("admin.integrationWebPush")}
|
|
value={t("admin.configured")}
|
|
className="text-dracula-green"
|
|
/>
|
|
)}
|
|
{status.integrations.bugReport && (
|
|
<StatusItem
|
|
icon={Bug}
|
|
label={t("admin.integrationBugReport")}
|
|
value={t("admin.configured")}
|
|
className="text-dracula-green"
|
|
/>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="mt-4 flex items-center gap-2">
|
|
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
|
|
<span className="text-muted-foreground text-sm">
|
|
{t("admin.loadingStatus")}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|