milestone 26: upstream health answers for the selected period
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import * as stylex from "@stylexjs/stylex";
|
||||
import type { UpstreamHealth } from "@/lib/types";
|
||||
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",
|
||||
@@ -34,6 +37,13 @@ const styles = stylex.create({
|
||||
lineHeight: "1.25rem",
|
||||
color: colors.textMuted,
|
||||
},
|
||||
note: {
|
||||
paddingInline: "1rem",
|
||||
paddingBlock: "0.5rem",
|
||||
fontSize: "0.75rem",
|
||||
lineHeight: "1rem",
|
||||
color: colors.textMuted,
|
||||
},
|
||||
tableWrap: {
|
||||
overflowX: "auto",
|
||||
},
|
||||
@@ -43,6 +53,24 @@ const styles = stylex.create({
|
||||
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",
|
||||
@@ -85,12 +113,36 @@ const styles = stylex.create({
|
||||
},
|
||||
});
|
||||
|
||||
function YesNo({ value, badValue }: { value: boolean; badValue: boolean }) {
|
||||
const bad = value === badValue;
|
||||
return <span {...stylex.props(bad && styles.bad)}>{value ? "yes" : "no"}</span>;
|
||||
/** 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)}>
|
||||
@@ -105,15 +157,21 @@ export default function UpstreamHealthTable({ health }: { health: UpstreamHealth
|
||||
<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)}>
|
||||
URL
|
||||
Upstream
|
||||
</th>
|
||||
<th scope="col" {...stylex.props(styles.th)}>
|
||||
Enabled
|
||||
Status now
|
||||
</th>
|
||||
<th scope="col" {...stylex.props(styles.th)}>
|
||||
Available
|
||||
<th scope="col" {...stylex.props(styles.th, styles.thRight)}>
|
||||
Attempts
|
||||
</th>
|
||||
<th scope="col" {...stylex.props(styles.th, styles.thRight)}>
|
||||
Failures
|
||||
@@ -122,35 +180,53 @@ export default function UpstreamHealthTable({ health }: { health: UpstreamHealth
|
||||
Success rate
|
||||
</th>
|
||||
<th scope="col" {...stylex.props(styles.th)}>
|
||||
Last error
|
||||
Last failure
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{health.upstreams.map((upstream) => (
|
||||
<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)}>
|
||||
<YesNo value={upstream.enabled} badValue={false} />
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell)}>
|
||||
<YesNo value={upstream.available} badValue={false} />
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.cellRight, shared.tabularNums)}>
|
||||
{upstream.total_failures}
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.cellRight, shared.tabularNums)}>
|
||||
{(upstream.success_rate * 100).toFixed(1)}%
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.small, styles.muted)}>
|
||||
{upstream.last_error || "—"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user