Import source: web/src/App
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { AnimatePresence, LayoutGroup, motion } from "motion/react";
|
||||||
|
import { useMonitor } from "./hooks/useMonitor";
|
||||||
|
import { springs, UIProvider, useUI } from "./lib/ui";
|
||||||
|
import type { CheckKind } from "./lib/types";
|
||||||
|
import { fmtNumber } from "./lib/format";
|
||||||
|
import { Header } from "./components/Header";
|
||||||
|
import { Boot } from "./components/Boot";
|
||||||
|
import { TargetCard } from "./components/TargetCard";
|
||||||
|
import { TargetDetail } from "./components/TargetDetail";
|
||||||
|
import { OrbitalOverview } from "./components/OrbitalOverview";
|
||||||
|
import { RadialDay } from "./components/RadialDay";
|
||||||
|
import { IncidentList } from "./components/IncidentList";
|
||||||
|
import { PublicView } from "./components/PublicView";
|
||||||
|
|
||||||
|
const MIN_BOOT_MS = 1700;
|
||||||
|
|
||||||
|
type Filter = "all" | CheckKind;
|
||||||
|
|
||||||
|
function FilterChips({
|
||||||
|
targets,
|
||||||
|
filter,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
targets: { type: CheckKind }[];
|
||||||
|
filter: Filter;
|
||||||
|
onChange: (f: Filter) => void;
|
||||||
|
}) {
|
||||||
|
const { t, lang, reduced } = useUI();
|
||||||
|
const kinds = useMemo(() => {
|
||||||
|
const present = new Set(targets.map((x) => x.type));
|
||||||
|
return (["http", "tls", "tcp", "icmp", "docker"] as CheckKind[]).filter((k) =>
|
||||||
|
present.has(k),
|
||||||
|
);
|
||||||
|
}, [targets]);
|
||||||
|
|
||||||
|
if (kinds.length < 2) return null;
|
||||||
|
|
||||||
|
const chip = (value: Filter, label: string, count: number) => {
|
||||||
|
const active = filter === value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={value}
|
||||||
|
onClick={() => onChange(value)}
|
||||||
|
aria-pressed={active}
|
||||||
|
className={`relative px-2.5 py-[3px] rounded-full font-mono text-[9.5px] tracking-[0.12em] uppercase cursor-pointer transition-colors ${
|
||||||
|
active ? "text-ink-950" : "text-paper-faint hover:text-paper-dim"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{active && (
|
||||||
|
<motion.span
|
||||||
|
layoutId="filter-chip"
|
||||||
|
className="absolute inset-0 rounded-full bg-brass"
|
||||||
|
transition={reduced ? { duration: 0 } : springs.snappy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="relative z-10">
|
||||||
|
{label} <span className="opacity-65 tabular">{fmtNumber(lang, count)}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1 flex-wrap">
|
||||||
|
{chip("all", t.all, targets.length)}
|
||||||
|
{kinds.map((k) =>
|
||||||
|
chip(k, k, targets.filter((x) => x.type === k).length),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Shell() {
|
||||||
|
const monitor = useMonitor();
|
||||||
|
const { t, mode, reduced } = useUI();
|
||||||
|
const [selected, setSelected] = useState<string | null>(
|
||||||
|
() => decodeURIComponent(location.hash.slice(1)) || null,
|
||||||
|
);
|
||||||
|
const [filter, setFilter] = useState<Filter>("all");
|
||||||
|
const [bootElapsed, setBootElapsed] = useState(reduced);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const id = setTimeout(() => setBootElapsed(true), reduced ? 0 : MIN_BOOT_MS);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [reduced]);
|
||||||
|
|
||||||
|
const booted = monitor.ready && bootElapsed;
|
||||||
|
const selectedTarget = monitor.targets.find((x) => x.id === selected) ?? null;
|
||||||
|
|
||||||
|
// The selected target lives in the URL — deep-linkable, back-button friendly.
|
||||||
|
const select = (id: string | null) => {
|
||||||
|
setSelected(id);
|
||||||
|
history.replaceState(null, "", id ? `#${encodeURIComponent(id)}` : location.pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") select(null);
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", onKey);
|
||||||
|
return () => window.removeEventListener("keydown", onKey);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const visibleTargets =
|
||||||
|
filter === "all"
|
||||||
|
? monitor.targets
|
||||||
|
: monitor.targets.filter((x) => x.type === filter);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative z-[1] min-h-screen">
|
||||||
|
<AnimatePresence>{!booted && <Boot />}</AnimatePresence>
|
||||||
|
|
||||||
|
{booted && (
|
||||||
|
<>
|
||||||
|
<Header connected={monitor.connected} targets={monitor.targets} />
|
||||||
|
<div className="rule-fade mx-6 sm:mx-10" />
|
||||||
|
|
||||||
|
{mode === "public" ? (
|
||||||
|
<PublicView monitor={monitor} />
|
||||||
|
) : (
|
||||||
|
<LayoutGroup>
|
||||||
|
<main className="px-6 sm:px-10 pb-16">
|
||||||
|
{/* hero: orbit + day dial */}
|
||||||
|
<section className="grid gap-10 lg:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)] items-start pt-8">
|
||||||
|
<OrbitalOverview
|
||||||
|
targets={monitor.targets}
|
||||||
|
onSelect={select}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<RadialDay
|
||||||
|
targets={monitor.targets}
|
||||||
|
dayBuckets={monitor.dayBuckets}
|
||||||
|
/>
|
||||||
|
<IncidentList
|
||||||
|
incidents={monitor.incidents.slice(0, 6)}
|
||||||
|
targets={monitor.targets}
|
||||||
|
onSelect={select}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* targets grid */}
|
||||||
|
<section className="mt-12">
|
||||||
|
<div className="flex items-baseline gap-4 mb-5 flex-wrap">
|
||||||
|
<h2 className="microlabel">{t.targets}</h2>
|
||||||
|
<div className="rule-fade flex-1 min-w-16" />
|
||||||
|
<FilterChips
|
||||||
|
targets={monitor.targets}
|
||||||
|
filter={filter}
|
||||||
|
onChange={setFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4 [grid-template-columns:repeat(auto-fill,minmax(215px,1fr))]">
|
||||||
|
<AnimatePresence mode="popLayout" initial={false}>
|
||||||
|
{visibleTargets.map((target, i) => (
|
||||||
|
<TargetCard
|
||||||
|
key={target.id}
|
||||||
|
target={target}
|
||||||
|
index={i}
|
||||||
|
onSelect={select}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{selectedTarget && (
|
||||||
|
<TargetDetail
|
||||||
|
target={selectedTarget}
|
||||||
|
buckets={monitor.dayBuckets[selectedTarget.id] ?? []}
|
||||||
|
incidents={monitor.incidents.filter(
|
||||||
|
(i) => i.target_id === selectedTarget.id,
|
||||||
|
)}
|
||||||
|
onClose={() => select(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</LayoutGroup>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<UIProvider>
|
||||||
|
<Shell />
|
||||||
|
</UIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user