swarm-ui: the schedules are a folded card on the messages tab
The schedules tab is gone from the nav. Its page, unchanged inside, is a card on /messages under the broadcast card that starts shut and reads nothing until opened; its add button and refresh picker move into the card's body. /schedules redirects to /messages. Docs follow.
This commit is contained in:
parent
d269c06d58
commit
bdef828df3
6 changed files with 65 additions and 42 deletions
|
|
@ -20,19 +20,18 @@ the highlighted one. **Escape**, **Tab** or a press elsewhere shut the list with
|
||||||
changing anything. Typing the start of an option's name picks it, also with the list
|
changing anything. Typing the start of an option's name picks it, also with the list
|
||||||
shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't cut it off.
|
shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't cut it off.
|
||||||
|
|
||||||
| route | what |
|
| route | what |
|
||||||
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `/` | the start page: the swarm's name over a digital-rain backdrop, plus a paperclip offering to set up an agent while no `ruth` exists (it opens the create form with `ruth` filled in); the swarm name in the header links here |
|
| `/` | the start page: the swarm's name over a digital-rain backdrop, plus a paperclip offering to set up an agent while no `ruth` exists (it opens the create form with `ruth` filled in); the swarm name in the header links here |
|
||||||
| `/agents` | every agent, under a folded **hives** card with the hive directory and each hive's last reported status: status, config PR, wanted state, inference settings, resource caps, permissions; create agents (with their permissions, optionally from an [archetype](archetypes.md)), link forge, matrix and GitHub accounts |
|
| `/agents` | every agent, under a folded **hives** card with the hive directory and each hive's last reported status: status, config PR, wanted state, inference settings, resource caps, permissions; create agents (with their permissions, optionally from an [archetype](archetypes.md)), link forge, matrix and GitHub accounts |
|
||||||
| `/agents/<name>/terminal` | one agent's live terminal |
|
| `/agents/<name>/terminal` | one agent's live terminal |
|
||||||
| `/agents/<name>/subagents/<subagent>/terminal` | one of that agent's subagents' live terminal |
|
| `/agents/<name>/subagents/<subagent>/terminal` | one of that agent's subagents' live terminal |
|
||||||
| `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token |
|
| `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token |
|
||||||
| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds of agent config PRs, with what nix printed when one failed |
|
| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds of agent config PRs, with what nix printed when one failed |
|
||||||
| `/schedules` | the scheduled prompts: add, edit, pause, resume and remove |
|
| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once, and a folded **schedules** card holds the scheduled prompts: add, edit, pause, resume and remove |
|
||||||
| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once |
|
| `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows |
|
||||||
| `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows |
|
| `/issues` | a cross-repo issue report |
|
||||||
| `/issues` | a cross-repo issue report |
|
| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
|
||||||
| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
|
|
||||||
|
|
||||||
Everything it shows comes from [`swarm-controller`](../../swarm-controller/README.md).
|
Everything it shows comes from [`swarm-controller`](../../swarm-controller/README.md).
|
||||||
Pages that refresh on a timer share one polling rule: the page sends no new
|
Pages that refresh on a timer share one polling rule: the page sends no new
|
||||||
|
|
@ -219,7 +218,9 @@ has no API key field: admins set each runtime's key in the bao UI.
|
||||||
|
|
||||||
### Scheduled prompts
|
### Scheduled prompts
|
||||||
|
|
||||||
`/schedules` has one card per scheduled prompt. The **name** is the card's
|
The **schedules** card on the **messages** tab holds one card per scheduled
|
||||||
|
prompt. It starts shut and reads nothing until you open it; the old `/schedules` address
|
||||||
|
redirects to the messages tab. The **name** is the card's
|
||||||
title, with the **agents** it fires for, the message, how often it repeats and
|
title, with the **agents** it fires for, the message, how often it repeats and
|
||||||
when it fires next. **edit** turns the card into its own form; **pause** and
|
when it fires next. **edit** turns the card into its own form; **pause** and
|
||||||
**resume** stop and restart it; **remove** deletes it after a confirmation.
|
**resume** stop and restart it; **remove** deletes it after a confirmation.
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import { ComponentsPage } from "./pages/ComponentsPage.js";
|
||||||
import { JobsPage } from "./pages/JobsPage.js";
|
import { JobsPage } from "./pages/JobsPage.js";
|
||||||
import { IssueReportPage } from "./pages/IssueReportPage.js";
|
import { IssueReportPage } from "./pages/IssueReportPage.js";
|
||||||
import { ReviewPage } from "./pages/review/ReviewPage.js";
|
import { ReviewPage } from "./pages/review/ReviewPage.js";
|
||||||
import { SchedulesPage } from "./pages/schedules/SchedulesPage.js";
|
|
||||||
import { StartPage } from "./pages/StartPage.js";
|
import { StartPage } from "./pages/StartPage.js";
|
||||||
import { Panel } from "./ui/panel/Panel.js";
|
import { Panel } from "./ui/panel/Panel.js";
|
||||||
|
|
||||||
|
|
@ -48,7 +47,9 @@ export function App() {
|
||||||
component={AgentTerminalPage}
|
component={AgentTerminalPage}
|
||||||
/>
|
/>
|
||||||
<Route path="/jobs" component={JobsPage} />
|
<Route path="/jobs" component={JobsPage} />
|
||||||
<Route path="/schedules" component={SchedulesPage} />
|
<Route path="/schedules">
|
||||||
|
<Redirect to="/messages" replace />
|
||||||
|
</Route>
|
||||||
<Route path="/builds" component={BuildsMoved} />
|
<Route path="/builds" component={BuildsMoved} />
|
||||||
<Route path="/messages" component={MessagesPage} />
|
<Route path="/messages" component={MessagesPage} />
|
||||||
<Route path="/notifications" component={NotificationsPage} />
|
<Route path="/notifications" component={NotificationsPage} />
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// operator, reminders and compaction to an agent, as the swarm's controller
|
// operator, reminders and compaction to an agent, as the swarm's controller
|
||||||
// streams them (`GET /api/messages/stream`), across all hives. Above the
|
// streams them (`GET /api/messages/stream`), across all hives. Above the
|
||||||
// list, a folded **broadcast** card holds the composer that sends one message
|
// list, a folded **broadcast** card holds the composer that sends one message
|
||||||
// to several agents.
|
// to several agents, and a folded **schedules** card holds the scheduled prompts.
|
||||||
//
|
//
|
||||||
// The stream sends the chosen window of history first, then the live tail. A
|
// The stream sends the chosen window of history first, then the live tail. A
|
||||||
// message an agent drained with an in-turn `recv` is missing, because it starts
|
// message an agent drained with an in-turn `recv` is missing, because it starts
|
||||||
|
|
@ -14,6 +14,7 @@ import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||||
import { Row } from "@hive/shared/term-row.js";
|
import { Row } from "@hive/shared/term-row.js";
|
||||||
import { BroadcastComposer } from "../broadcast/BroadcastComposer.js";
|
import { BroadcastComposer } from "../broadcast/BroadcastComposer.js";
|
||||||
|
import { SchedulesCard } from "../schedules/SchedulesPage.js";
|
||||||
import { Disclosure } from "../../ui/disclosure/Disclosure.js";
|
import { Disclosure } from "../../ui/disclosure/Disclosure.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import { Panel } from "../../ui/panel/Panel.js";
|
||||||
import { SelectField } from "../../ui/select-field/SelectField.js";
|
import { SelectField } from "../../ui/select-field/SelectField.js";
|
||||||
|
|
@ -96,6 +97,7 @@ export function MessagesPage() {
|
||||||
>
|
>
|
||||||
{composerBuilt ? <BroadcastComposer /> : null}
|
{composerBuilt ? <BroadcastComposer /> : null}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
<SchedulesCard />
|
||||||
<Panel
|
<Panel
|
||||||
title="messages"
|
title="messages"
|
||||||
icon="💬"
|
icon="💬"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* The bar at the top of the schedules card: the add button, then the refresh
|
||||||
|
picker, to the right. */
|
||||||
|
.ui-schedules-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5em 0.75em;
|
||||||
|
margin-block-end: 0.5em;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// <SchedulesPage> — the swarm's scheduled prompts, one card per schedule.
|
// <SchedulesCard> — the swarm's scheduled prompts, one card per schedule, folded on the messages tab.
|
||||||
// Backed by swarm-controller's `GET/POST /api/schedules` and
|
// Backed by swarm-controller's `GET/POST /api/schedules` and
|
||||||
// `PATCH/DELETE /api/schedules/{id}`; the controller is the only writer of
|
// `PATCH/DELETE /api/schedules/{id}`; the controller is the only writer of
|
||||||
// the schedule store and computes each schedule's `next_fire_unix`, so
|
// the schedule store and computes each schedule's `next_fire_unix`, so
|
||||||
|
|
@ -15,7 +15,8 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||||
import { Button } from "../../ui/button/Button.js";
|
import { Button } from "../../ui/button/Button.js";
|
||||||
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
||||||
import { FilterableView } from "../../ui/filterable-view/FilterableView.js";
|
import { FilterableView } from "../../ui/filterable-view/FilterableView.js";
|
||||||
import { Panel } from "../../ui/panel/Panel.js";
|
import "./SchedulesPage.css";
|
||||||
|
import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
|
||||||
import {
|
import {
|
||||||
RefreshIntervalPicker,
|
RefreshIntervalPicker,
|
||||||
type RefreshIntervalMs,
|
type RefreshIntervalMs,
|
||||||
|
|
@ -57,7 +58,7 @@ const COLUMNS: TableColumn<Schedule>[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SchedulesPage() {
|
function SchedulesBody() {
|
||||||
const [intervalMs, setIntervalMs] =
|
const [intervalMs, setIntervalMs] =
|
||||||
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
|
||||||
const {
|
const {
|
||||||
|
|
@ -145,26 +146,21 @@ export function SchedulesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<>
|
||||||
title="schedules"
|
<div class="ui-schedules-bar">
|
||||||
icon="⏰"
|
<Button
|
||||||
actions={
|
variant="primary"
|
||||||
<>
|
onClick={() => setCreating(true)}
|
||||||
<Button
|
disabled={creating}
|
||||||
variant="primary"
|
>
|
||||||
onClick={() => setCreating(true)}
|
+ schedule
|
||||||
disabled={creating}
|
</Button>
|
||||||
>
|
<RefreshIntervalPicker
|
||||||
+ schedule
|
id="schedules-refresh"
|
||||||
</Button>
|
value={intervalMs}
|
||||||
<RefreshIntervalPicker
|
onChange={setIntervalMs}
|
||||||
id="schedules-refresh"
|
/>
|
||||||
value={intervalMs}
|
</div>
|
||||||
onChange={setIntervalMs}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<ApiErrorPanel context="failed to load the schedules" problem={error} />
|
<ApiErrorPanel context="failed to load the schedules" problem={error} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -226,6 +222,20 @@ export function SchedulesPage() {
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</Panel>
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A folded card on the messages tab: the scheduled prompts, read and polled
|
||||||
|
// only once it is opened.
|
||||||
|
export function SchedulesCard() {
|
||||||
|
return (
|
||||||
|
<LazyDisclosure
|
||||||
|
title="schedules"
|
||||||
|
icon="⏰"
|
||||||
|
hint="prompts sent to agents at set times: add, edit, pause, remove"
|
||||||
|
>
|
||||||
|
<SchedulesBody />
|
||||||
|
</LazyDisclosure>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ const NAV_ITEMS: { href: string; label: string; accent: string }[] = [
|
||||||
{ href: "/agents", label: "agents", accent: "var(--green)" },
|
{ href: "/agents", label: "agents", accent: "var(--green)" },
|
||||||
{ href: "/alerts", label: "alerts", accent: "var(--red)" },
|
{ href: "/alerts", label: "alerts", accent: "var(--red)" },
|
||||||
{ href: "/jobs", label: "jobs", accent: "var(--pink)" },
|
{ href: "/jobs", label: "jobs", accent: "var(--pink)" },
|
||||||
{ href: "/schedules", label: "schedules", accent: "var(--cyan)" },
|
|
||||||
{ href: "/messages", label: "messages", accent: "var(--purple)" },
|
{ href: "/messages", label: "messages", accent: "var(--purple)" },
|
||||||
{ href: "/notifications", label: "notifications", accent: "var(--yellow)" },
|
{ href: "/notifications", label: "notifications", accent: "var(--yellow)" },
|
||||||
{ href: "/issues", label: "issues", accent: "var(--yellow)" },
|
{ href: "/issues", label: "issues", accent: "var(--yellow)" },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue