Files
nxdns/admin/src/features/dashboard/UpstreamHealthTable.tsx
T

233 lines
6.6 KiB
TypeScript

import * as stylex from "@stylexjs/stylex";
import { formatAge } from "@/lib/format";
import type { UpstreamHealth, UpstreamHealthEntry, UpstreamPeriodStats } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
const numberFormat = new Intl.NumberFormat();
const styles = stylex.create({
card: {
borderRadius: "0.25rem",
borderWidth: 1,
borderStyle: "solid",
borderColor: colors.border,
backgroundColor: colors.surfaceRaised,
},
heading: {
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
paddingInline: "1rem",
paddingTop: "0.75rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
fontWeight: 600,
},
count: {
fontSize: "0.75rem",
lineHeight: "1rem",
fontWeight: 400,
color: colors.textMuted,
},
empty: {
paddingInline: "1rem",
paddingBlock: "0.75rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
color: colors.textMuted,
},
note: {
paddingInline: "1rem",
paddingBlock: "0.5rem",
fontSize: "0.75rem",
lineHeight: "1rem",
color: colors.textMuted,
},
tableWrap: {
overflowX: "auto",
},
table: {
marginTop: "0.5rem",
width: "100%",
fontSize: "0.875rem",
lineHeight: "1.25rem",
},
/**
* The two live columns are left outside the span: everything under it answers
* for the selected window, and nothing else on this card does.
*/
groupRow: {
fontSize: "0.75rem",
lineHeight: "1rem",
color: colors.textMuted,
},
groupHead: {
borderBottomWidth: 1,
borderBottomStyle: "solid",
borderBottomColor: colors.border,
paddingInline: "1rem",
paddingBottom: "0.25rem",
textAlign: "center",
fontWeight: 500,
},
headRow: {
borderBottomWidth: 1,
borderBottomStyle: "solid",
borderBottomColor: colors.border,
textAlign: "left",
fontSize: "0.75rem",
lineHeight: "1rem",
color: colors.textMuted,
},
th: {
paddingInline: "1rem",
paddingBlock: "0.5rem",
fontWeight: 500,
},
thRight: {
textAlign: "right",
},
/** No hairline under the last row: the card border already closes the table. */
row: {
borderBottomWidth: { default: 1, ":last-child": 0 },
borderBottomStyle: "solid",
borderBottomColor: colors.border,
},
cell: {
paddingInline: "1rem",
paddingBlock: "0.5rem",
},
cellRight: {
textAlign: "right",
},
small: {
fontSize: "0.75rem",
lineHeight: "1rem",
},
muted: {
color: colors.textMuted,
},
bad: {
color: colors.danger,
},
});
/** Live pool state in one word. Configuration first: a disabled upstream is not backing off. */
function statusNow(upstream: UpstreamHealthEntry): "Available" | "Backing off" | "Disabled" {
if (!upstream.enabled) return "Disabled";
return upstream.available ? "Available" : "Backing off";
}
/**
* `success_rate` is null exactly when the window holds no attempt, and that must
* not read as perfect reliability — hence the em-dash rather than `100.0%`.
*/
function successRate(period: UpstreamPeriodStats): string {
return period.success_rate === null ? "—" : `${(period.success_rate * 100).toFixed(1)}%`;
}
/**
* The age is formatted once, when the row renders; nothing here ticks. It is
* measured against the browser's clock rather than the response's `until`, so a
* cached response ages visibly instead of freezing at the moment it was served.
*/
function lastFailure(period: UpstreamPeriodStats, nowSeconds: number): string {
if (period.last_failure_at === null) return "—";
const age = formatAge(Math.max(0, nowSeconds - period.last_failure_at));
const error = period.last_failure_error;
return error === null || error === "" ? age : `${error} · ${age}`;
}
export default function UpstreamHealthTable({ health }: { health: UpstreamHealth }) {
const nowSeconds = Math.floor(Date.now() / 1000);
const idle = health.upstreams.length > 0 && health.upstreams.every(({ period }) => period.attempts === 0);
return (
<section {...stylex.props(styles.card)}>
<h2 {...stylex.props(styles.heading)}>
Upstreams
<span {...stylex.props(styles.count, shared.tabularNums)}>
{health.available}/{health.total} available
</span>
</h2>
{health.upstreams.length === 0 ? (
<p {...stylex.props(styles.empty)}>No upstreams configured.</p>
) : (
<div {...stylex.props(styles.tableWrap)}>
<table {...stylex.props(styles.table)}>
<thead>
<tr {...stylex.props(styles.groupRow)}>
<td colSpan={2} />
<th scope="colgroup" colSpan={4} {...stylex.props(styles.groupHead)}>
Selected period · {health.period}
</th>
</tr>
<tr {...stylex.props(styles.headRow)}>
<th scope="col" {...stylex.props(styles.th)}>
Upstream
</th>
<th scope="col" {...stylex.props(styles.th)}>
Status now
</th>
<th scope="col" {...stylex.props(styles.th, styles.thRight)}>
Attempts
</th>
<th scope="col" {...stylex.props(styles.th, styles.thRight)}>
Failures
</th>
<th scope="col" {...stylex.props(styles.th, styles.thRight)}>
Success rate
</th>
<th scope="col" {...stylex.props(styles.th)}>
Last failure
</th>
</tr>
</thead>
<tbody>
{health.upstreams.map((upstream) => {
const status = statusNow(upstream);
return (
<tr key={upstream.url} {...stylex.props(styles.row)}>
<td {...stylex.props(styles.cell, styles.small, shared.mono)}>
{upstream.url}
</td>
<td {...stylex.props(styles.cell)}>
<span
{...stylex.props(
status === "Backing off" && styles.bad,
status === "Disabled" && styles.muted,
)}
>
{status}
</span>
</td>
<td {...stylex.props(styles.cell, styles.cellRight, shared.tabularNums)}>
{numberFormat.format(upstream.period.attempts)}
</td>
<td {...stylex.props(styles.cell, styles.cellRight, shared.tabularNums)}>
{numberFormat.format(upstream.period.failures)}
</td>
<td {...stylex.props(styles.cell, styles.cellRight, shared.tabularNums)}>
{successRate(upstream.period)}
</td>
<td {...stylex.props(styles.cell, styles.small, styles.muted)}>
{lastFailure(upstream.period, nowSeconds)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
{idle && <p {...stylex.props(styles.note)}>No upstream attempts in this period.</p>}
{!health.complete && (
<p {...stylex.props(styles.note)}>
History incomplete: outcomes were dropped in this window, so these counts are a lower bound.
</p>
)}
</section>
);
}