swarm-ui: alerts and the issue report are folded cards on a reports tab
The alerts and issues tabs are one reports tab in the nav. Each page, unchanged inside, is a card that starts shut and reads nothing until opened; the alerts header's link, button and refresh picker move into the card's body. /alerts and /issues redirect to /reports. Docs follow.
This commit is contained in:
parent
11914e3e9a
commit
599747bacb
6 changed files with 96 additions and 43 deletions
|
|
@ -6,14 +6,13 @@ import { Shell } from "./shell/Shell.js";
|
|||
import { useGuardedLocation } from "./shell/useGuardedLocation.js";
|
||||
import { AgentsPage } from "./pages/agents/AgentsPage.js";
|
||||
import { AgentTerminalPage } from "./pages/agents/AgentTerminalPage.js";
|
||||
import { AlertsPage } from "./pages/AlertsPage.js";
|
||||
import { MessagesPage } from "./pages/messages/MessagesPage.js";
|
||||
import { NEW_FLOW_PATH } from "./pages/notifications/FlowDraft.js";
|
||||
import { FlowEditorPage } from "./pages/notifications/FlowEditorPage.js";
|
||||
import { NotificationsPage } from "./pages/notifications/NotificationsPage.js";
|
||||
import { ComponentsPage } from "./pages/ComponentsPage.js";
|
||||
import { JobsPage } from "./pages/JobsPage.js";
|
||||
import { IssueReportPage } from "./pages/IssueReportPage.js";
|
||||
import { ReportsPage } from "./pages/reports/ReportsPage.js";
|
||||
import { StartPage } from "./pages/StartPage.js";
|
||||
import { Panel } from "./ui/panel/Panel.js";
|
||||
|
||||
|
|
@ -38,7 +37,10 @@ export function App() {
|
|||
<Shell>
|
||||
<Switch>
|
||||
<Route path="/" component={StartPage} />
|
||||
<Route path="/alerts" component={AlertsPage} />
|
||||
<Route path="/reports" component={ReportsPage} />
|
||||
<Route path="/alerts">
|
||||
<Redirect to="/reports" replace />
|
||||
</Route>
|
||||
<Route path="/agents" component={AgentsPage} />
|
||||
<Route path="/agents/:name/terminal" component={AgentTerminalPage} />
|
||||
<Route
|
||||
|
|
@ -56,7 +58,9 @@ export function App() {
|
|||
<FlowEditorPage params={{ flowId: "" }} isNew />
|
||||
</Route>
|
||||
<Route path="/notifications/:flowId" component={FlowEditorPage} />
|
||||
<Route path="/issues" component={IssueReportPage} />
|
||||
<Route path="/issues">
|
||||
<Redirect to="/reports" replace />
|
||||
</Route>
|
||||
<Route path="/review">
|
||||
<Redirect to="/jobs" replace />
|
||||
</Route>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// <AlertsPage> — `/alerts`' content: the alerts the swarm's Grafana holds
|
||||
// <AlertsCard> — a card of the reports tab: the alerts the swarm's Grafana holds
|
||||
// now, from swarm-controller's `GET /api/alerts`. Only the firing ones show at
|
||||
// first; a button in the header adds the pending, no-data and errored ones.
|
||||
// Same shape as `HivesCard`: one fetch on a `RefreshIntervalPicker` cadence,
|
||||
|
|
@ -7,7 +7,7 @@ import { useState } from "preact/hooks";
|
|||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||
import { Button } from "../ui/button/Button.js";
|
||||
import { Panel } from "../ui/panel/Panel.js";
|
||||
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
||||
import { RelativeTime } from "../ui/relative-time/RelativeTime.js";
|
||||
import {
|
||||
RefreshIntervalPicker,
|
||||
|
|
@ -71,7 +71,7 @@ const COLUMNS: TableColumn<Alert>[] = [
|
|||
|
||||
const DEFAULT_INTERVAL_MS: RefreshIntervalMs = 30_000;
|
||||
|
||||
export function AlertsPage() {
|
||||
function AlertsBody() {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [intervalMs, setIntervalMs] =
|
||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||
|
|
@ -82,31 +82,22 @@ export function AlertsPage() {
|
|||
const hidden = alerts && listed ? alerts.length - listed.length : 0;
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title="alerts"
|
||||
icon="🚨"
|
||||
actions={
|
||||
<>
|
||||
<a
|
||||
href={GRAFANA_ALERTS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Grafana's alert list
|
||||
</a>
|
||||
{showAll || hidden > 0 ? (
|
||||
<Button onClick={() => setShowAll(!showAll)}>
|
||||
{showAll ? "only firing" : `show ${hidden} more`}
|
||||
</Button>
|
||||
) : null}
|
||||
<RefreshIntervalPicker
|
||||
id="alerts-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<div class="ui-alerts-bar">
|
||||
<a href={GRAFANA_ALERTS_URL} target="_blank" rel="noopener noreferrer">
|
||||
Grafana's alert list
|
||||
</a>
|
||||
{showAll || hidden > 0 ? (
|
||||
<Button onClick={() => setShowAll(!showAll)}>
|
||||
{showAll ? "only firing" : `show ${hidden} more`}
|
||||
</Button>
|
||||
) : null}
|
||||
<RefreshIntervalPicker
|
||||
id="alerts-refresh"
|
||||
value={intervalMs}
|
||||
onChange={setIntervalMs}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<ApiErrorPanel
|
||||
context="failed to load Grafana's alerts"
|
||||
|
|
@ -125,6 +116,19 @@ export function AlertsPage() {
|
|||
storageKey="swarm-ui:alerts:table-filters"
|
||||
/>
|
||||
) : null}
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// A folded card on the reports tab: read and polled only once it is opened.
|
||||
export function AlertsCard() {
|
||||
return (
|
||||
<LazyDisclosure
|
||||
title="alerts"
|
||||
icon="🚨"
|
||||
hint="what the swarm's Grafana holds now"
|
||||
>
|
||||
<AlertsBody />
|
||||
</LazyDisclosure>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// <IssueReportPage> — mara's custom issue report: browse open issues
|
||||
// <IssueReportCard> — a card of the reports tab: mara's custom issue report: browse open issues
|
||||
// across every repo that has one, sortable/filterable her own way, with
|
||||
// "hide blocked (open dependency)" as the headline filter and a rank
|
||||
// column for how many other open issues depend on each one. Backed by
|
||||
|
|
@ -32,7 +32,7 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
|||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { useLocalSetting } from "@hive/shared/settings-storage.js";
|
||||
import { Panel } from "../ui/panel/Panel.js";
|
||||
import { LazyDisclosure } from "../ui/disclosure/LazyDisclosure.js";
|
||||
import { CheckboxField } from "../ui/checkbox-field/CheckboxField.js";
|
||||
import { SelectField } from "../ui/select-field/SelectField.js";
|
||||
import { Table, type TableColumn } from "../ui/table/Table.js";
|
||||
|
|
@ -128,7 +128,7 @@ function SortHeader({
|
|||
);
|
||||
}
|
||||
|
||||
export function IssueReportPage() {
|
||||
function IssueReportBody() {
|
||||
const [repos, setRepos] = useState<string[] | null>(null);
|
||||
const [repoFilter, setRepoFilter] = useLocalSetting(
|
||||
REPO_FILTER_KEY,
|
||||
|
|
@ -370,7 +370,7 @@ export function IssueReportPage() {
|
|||
];
|
||||
|
||||
return (
|
||||
<Panel title="issue report" icon="📋">
|
||||
<>
|
||||
<div class="issue-report-controls">
|
||||
<SelectField
|
||||
id="issue-report-repo"
|
||||
|
|
@ -408,6 +408,20 @@ export function IssueReportPage() {
|
|||
storageKey={TABLE_FILTERS_KEY}
|
||||
/>
|
||||
) : null}
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// A folded card on the reports tab: the repo list and the report are read only
|
||||
// once it is opened.
|
||||
export function IssueReportCard() {
|
||||
return (
|
||||
<LazyDisclosure
|
||||
title="issue report"
|
||||
icon="📋"
|
||||
hint="open issues across every repo, ranked by what they block"
|
||||
>
|
||||
<IssueReportBody />
|
||||
</LazyDisclosure>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
16
frontend/packages/swarm-ui/src/pages/reports/ReportsPage.css
Normal file
16
frontend/packages/swarm-ui/src/pages/reports/ReportsPage.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* The reports tab: the folded cards, one gap apart. */
|
||||
.ui-reports-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
/* The bar at the top of the alerts card: the Grafana link, the show-more
|
||||
button and the refresh picker, to the right. */
|
||||
.ui-alerts-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5em 0.75em;
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
15
frontend/packages/swarm-ui/src/pages/reports/ReportsPage.tsx
Normal file
15
frontend/packages/swarm-ui/src/pages/reports/ReportsPage.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// <ReportsPage> — `/reports`: what the swarm reports about itself, as two
|
||||
// folded cards: the alerts Grafana holds now, and mara's issue report. Neither
|
||||
// reads anything until it is opened.
|
||||
import { AlertsCard } from "../AlertsPage.js";
|
||||
import { IssueReportCard } from "../IssueReportPage.js";
|
||||
import "./ReportsPage.css";
|
||||
|
||||
export function ReportsPage() {
|
||||
return (
|
||||
<div class="ui-reports-page">
|
||||
<AlertsCard />
|
||||
<IssueReportCard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -51,11 +51,10 @@ const MOTION_KEY = "swarm-ui:motion-override";
|
|||
|
||||
const NAV_ITEMS: { href: string; label: string; accent: string }[] = [
|
||||
{ href: "/agents", label: "agents", accent: "var(--green)" },
|
||||
{ href: "/alerts", label: "alerts", accent: "var(--red)" },
|
||||
{ href: "/reports", label: "reports", accent: "var(--red)" },
|
||||
{ href: "/jobs", label: "jobs", accent: "var(--pink)" },
|
||||
{ href: "/messages", label: "messages", accent: "var(--purple)" },
|
||||
{ href: "/notifications", label: "notifications", accent: "var(--yellow)" },
|
||||
{ href: "/issues", label: "issues", accent: "var(--yellow)" },
|
||||
{ href: "/components", label: "components", accent: "var(--blue)" },
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue