feat(cms): add recovery history and operational reliability tools
This commit is contained in:
1 parent
89526af344
commit
1ef405be0a
92 files changed
+6384
-267
No files matched your search
@@ -4,6 +4,7 @@ import { getTranslations } from "next-intl/server";
|
||||
|
||||
import Link from "@/components/link";
|
||||
|
||||
import { queryErrorGroups } from "@/lib/error-group-query";
|
||||
import { summarizeErrorGroup } from "@/lib/error-groups";
|
||||
|
||||
import { ErrorStore } from "@/lib/error-monitor";
|
||||
@@ -26,8 +27,6 @@ export default async function CmsErrorsPage({
|
||||
|
||||
const params = await searchParams;
|
||||
|
||||
const query = (params.q ?? "").trim().slice(0, 200).toLowerCase();
|
||||
|
||||
let result: Awaited<ReturnType<ErrorStore["read"]>>;
|
||||
|
||||
try {
|
||||
@@ -40,30 +39,13 @@ export default async function CmsErrorsPage({
|
||||
);
|
||||
}
|
||||
|
||||
const allGroups = new Map<string, typeof result.records>();
|
||||
|
||||
for (const record of result.records) {
|
||||
const events = allGroups.get(record.fingerprint) ?? [];
|
||||
|
||||
events.push(record);
|
||||
allGroups.set(record.fingerprint, events);
|
||||
}
|
||||
|
||||
const releases = [...new Set(result.records.map((r) => r.release))].sort();
|
||||
const queried = queryErrorGroups(result.records, params);
|
||||
const groups = new Map(
|
||||
[...allGroups.entries()].filter(([, events]) => {
|
||||
const latest = summarizeErrorGroup(events).latest;
|
||||
|
||||
return (
|
||||
latest &&
|
||||
(!params.source || latest.source === params.source) &&
|
||||
(params.status !== "open" || !latest.resolved) &&
|
||||
(params.status !== "resolved" || latest.resolved) &&
|
||||
(!query ||
|
||||
events.some((record) =>
|
||||
JSON.stringify(record).toLowerCase().includes(query),
|
||||
))
|
||||
);
|
||||
}),
|
||||
queried.map((group) => [group.fingerprint, group.events]),
|
||||
);
|
||||
const latestByGroup = new Map(
|
||||
queried.map((group) => [group.fingerprint, group.latest]),
|
||||
);
|
||||
|
||||
const filtered = [...groups.values()].flat();
|
||||
@@ -72,14 +54,14 @@ export default async function CmsErrorsPage({
|
||||
1,
|
||||
|
||||
Math.min(
|
||||
Number(params.page) || 1,
|
||||
Math.floor(Number(params.page)) || 1,
|
||||
|
||||
Math.max(1, Math.ceil(groups.size / 25)),
|
||||
),
|
||||
);
|
||||
|
||||
const pageHref = (next: number) =>
|
||||
`?${new URLSearchParams({ q: params.q ?? "", source: params.source ?? "", status: params.status ?? "", page: String(next) })}`;
|
||||
`?${new URLSearchParams({ q: params.q ?? "", source: params.source ?? "", status: params.status ?? "", release: params.release ?? "", period: params.period ?? "", sort: params.sort ?? "", page: String(next) })}`;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@@ -126,13 +108,46 @@ export default async function CmsErrorsPage({
|
||||
<option value="open">{t("open")}</option>
|
||||
<option value="resolved">{t("resolved")}</option>
|
||||
</select>
|
||||
<select
|
||||
name="release"
|
||||
aria-label={t("releaseFilter")}
|
||||
defaultValue={params.release ?? ""}
|
||||
className="input max-w-full"
|
||||
>
|
||||
<option value="">{t("allReleases")}</option>
|
||||
{releases.map((release) => (
|
||||
<option key={release} value={release}>
|
||||
{release}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
name="period"
|
||||
aria-label={t("period")}
|
||||
defaultValue={params.period ?? ""}
|
||||
className="input"
|
||||
>
|
||||
<option value="">{t("allRetained")}</option>
|
||||
<option value="24h">{t("last24h")}</option>
|
||||
<option value="7d">{t("last7d")}</option>
|
||||
</select>
|
||||
<select
|
||||
name="sort"
|
||||
aria-label={t("sort")}
|
||||
defaultValue={params.sort ?? ""}
|
||||
className="input"
|
||||
>
|
||||
<option value="">{t("recentFirst")}</option>
|
||||
<option value="frequency">{t("frequentFirst")}</option>
|
||||
</select>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
{t("search")}
|
||||
</button>
|
||||
</form>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("counts", { count: filtered.length, groups: groups.size })}{" "}
|
||||
{result.truncated ? t("truncated") : ""} {t("retention")}
|
||||
{result.truncated ? t("truncated") : ""} {t("retention")}{" "}
|
||||
{t("filterScope")}
|
||||
</p>
|
||||
{groups.size === 0 ? (
|
||||
<div className="admin-card">{t("empty")}</div>
|
||||
@@ -144,7 +159,7 @@ export default async function CmsErrorsPage({
|
||||
.map(([fingerprint, events]) => {
|
||||
const metrics = summarizeErrorGroup(events);
|
||||
|
||||
const r = metrics.latest;
|
||||
const r = latestByGroup.get(fingerprint);
|
||||
|
||||
if (!r) return null;
|
||||
|
||||
|
||||
Reference in new issue
Block a user