rename web/ to admin/, along with the web-named build and cli identifiers
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { useState } from "react";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import * as stylex from "@stylexjs/stylex";
|
||||
import { healthQuery, statsQuery, timeseriesQuery, upstreamHealthQuery } from "@/lib/queries";
|
||||
import type { Period } from "@/lib/types";
|
||||
import InlineError from "@/lib/InlineError";
|
||||
import { styles as shared } from "@/ui/styles";
|
||||
import { colors } from "@/ui/tokens.stylex";
|
||||
import DiskCard from "./DiskCard";
|
||||
import HealthBanners from "./HealthBanners";
|
||||
import StatCards from "./StatCards";
|
||||
import TimeseriesChart from "./TimeseriesChart";
|
||||
import UpstreamHealthTable from "./UpstreamHealthTable";
|
||||
|
||||
const PERIODS: Period[] = ["1h", "24h", "7d", "30d"];
|
||||
|
||||
const styles = stylex.create({
|
||||
page: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "1rem",
|
||||
},
|
||||
titleRow: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: "0.75rem",
|
||||
},
|
||||
heading: {
|
||||
fontSize: "1.5rem",
|
||||
lineHeight: "2rem",
|
||||
fontWeight: 600,
|
||||
},
|
||||
periodGroup: {
|
||||
display: "flex",
|
||||
gap: "0.25rem",
|
||||
},
|
||||
period: {
|
||||
borderStyle: "none",
|
||||
borderRadius: "0.25rem",
|
||||
paddingInline: "0.625rem",
|
||||
paddingBlock: "0.25rem",
|
||||
fontSize: "0.875rem",
|
||||
lineHeight: "1.25rem",
|
||||
},
|
||||
/** The pressed fill is heavier than `surfaceHover`, so a hover cannot mimic it. */
|
||||
periodSelected: {
|
||||
backgroundColor: {
|
||||
default: "oklch(92% 0.004 286.32)",
|
||||
"@media (prefers-color-scheme: dark)": "oklch(37% 0.013 285.805)",
|
||||
},
|
||||
color: colors.text,
|
||||
fontWeight: 500,
|
||||
},
|
||||
periodIdle: {
|
||||
backgroundColor: { default: "transparent", ":hover": colors.surfaceHover },
|
||||
color: colors.textSecondary,
|
||||
},
|
||||
/** Dynamic: the caller sizes the placeholder to the widget it stands in for. */
|
||||
skeletonHeight: (height: number) => ({ height }),
|
||||
skeleton: {
|
||||
borderRadius: "0.25rem",
|
||||
backgroundColor: {
|
||||
default: "oklch(92% 0.004 286.32)",
|
||||
"@media (prefers-color-scheme: dark)": "oklch(27.4% 0.006 286.033)",
|
||||
},
|
||||
},
|
||||
/** The chart takes two thirds beside the disk card from `lg`, one column below. */
|
||||
panelGrid: {
|
||||
display: "grid",
|
||||
gap: "1rem",
|
||||
gridTemplateColumns: {
|
||||
default: "repeat(1, minmax(0, 1fr))",
|
||||
"@media (min-width: 1024px)": "2fr 1fr",
|
||||
},
|
||||
},
|
||||
panel: {
|
||||
borderRadius: "0.25rem",
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surfaceRaised,
|
||||
paddingInline: "1rem",
|
||||
paddingBlock: "0.75rem",
|
||||
},
|
||||
panelHeading: {
|
||||
marginBottom: "0.75rem",
|
||||
fontSize: "0.875rem",
|
||||
lineHeight: "1.25rem",
|
||||
fontWeight: 600,
|
||||
},
|
||||
});
|
||||
|
||||
function PeriodPicker({ period, onChange }: { period: Period; onChange: (period: Period) => void }) {
|
||||
return (
|
||||
<div role="group" aria-label="Period" {...stylex.props(styles.periodGroup)}>
|
||||
{PERIODS.map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
type="button"
|
||||
aria-pressed={option === period}
|
||||
onClick={() => onChange(option)}
|
||||
{...stylex.props(
|
||||
styles.period,
|
||||
option === period ? styles.periodSelected : styles.periodIdle,
|
||||
shared.focusRing,
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Skeleton({ height }: { height: number }) {
|
||||
return <div aria-hidden="true" {...stylex.props(styles.skeleton, styles.skeletonHeight(height), shared.pulse)} />;
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [period, setPeriod] = useState<Period>("24h");
|
||||
const stats = useQuery({ ...statsQuery(period), placeholderData: keepPreviousData });
|
||||
const timeseries = useQuery({ ...timeseriesQuery(period), placeholderData: keepPreviousData });
|
||||
const health = useQuery(healthQuery());
|
||||
const upstreamHealth = useQuery(upstreamHealthQuery());
|
||||
|
||||
return (
|
||||
<section {...stylex.props(styles.page)}>
|
||||
<div {...stylex.props(styles.titleRow)}>
|
||||
<h1 {...stylex.props(styles.heading)}>Dashboard</h1>
|
||||
<PeriodPicker period={period} onChange={setPeriod} />
|
||||
</div>
|
||||
|
||||
{health.data !== undefined && <HealthBanners health={health.data} />}
|
||||
|
||||
{stats.isError ? (
|
||||
<InlineError error={stats.error} onRetry={() => void stats.refetch()} />
|
||||
) : stats.data === undefined ? (
|
||||
<Skeleton height={76} />
|
||||
) : (
|
||||
<StatCards stats={stats.data} />
|
||||
)}
|
||||
|
||||
<div {...stylex.props(styles.panelGrid)}>
|
||||
<section {...stylex.props(styles.panel)}>
|
||||
<h2 {...stylex.props(styles.panelHeading)}>Queries over time</h2>
|
||||
{timeseries.isError ? (
|
||||
<InlineError error={timeseries.error} onRetry={() => void timeseries.refetch()} />
|
||||
) : timeseries.data === undefined ? (
|
||||
<Skeleton height={240} />
|
||||
) : (
|
||||
<TimeseriesChart data={timeseries.data} />
|
||||
)}
|
||||
</section>
|
||||
{health.data === undefined ? <Skeleton height={160} /> : <DiskCard disk={health.data.disk} />}
|
||||
</div>
|
||||
|
||||
{upstreamHealth.isError ? (
|
||||
<InlineError error={upstreamHealth.error} onRetry={() => void upstreamHealth.refetch()} />
|
||||
) : upstreamHealth.data === undefined ? (
|
||||
<Skeleton height={120} />
|
||||
) : (
|
||||
<UpstreamHealthTable health={upstreamHealth.data} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user