Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: a schedules page, one card per scheduled prompt

/schedules lists the controller's scheduled prompts as cards: name as the
title, the agents it fires for, the message, its cadence and next fire.
Edit turns a card into its own form; pause, resume and remove act in place,
and + schedule opens an empty form. The form sends only what changed, so a
rename never moves the fire times, and warns when the start or the repeat
does. Filters reuse the shared card filter bar.

Adds a TextAreaField to the ui kit, and an upcoming mode on RelativeTime so
a next-fire time reads "in 5m". Needs the controller's /api/schedules
routes.
This commit is contained in:
iris 2026-10-04 17:03:33 +02:00 • committed by mara
commit 63ee6d12be
14 changed files with 931 additions and 3 deletions

View file

@ -0,0 +1,90 @@
// <ScheduleCard> — one scheduled prompt as a card: its name as the title,
// "agents: foo, bar" beneath, then the message, the cadence and when it
// fires next. Edit, pause/resume and remove sit at the foot; edit swaps the
// card for `ScheduleForm` (the page owns that swap).
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
import { Button } from "../../ui/button/Button.js";
import { Card } from "../../ui/card/Card.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
import {
fmtInterval,
fmtLocal,
scheduleState,
type Schedule,
type ScheduleState,
} from "./ScheduleTypes.js";
import "./ScheduleCard.css";
const STATE_TONE: Record<ScheduleState, BadgeTone> = {
active: "positive",
paused: "warning",
done: "neutral",
};
export function ScheduleCard({
schedule,
pending,
onEdit,
onTogglePaused,
onRemove,
}: {
schedule: Schedule;
/** A pause/resume or remove for this schedule is in flight. */
pending: boolean;
onEdit: () => void;
onTogglePaused: () => void;
onRemove: () => void;
}) {
const state = scheduleState(schedule);
return (
<Card class="ui-schedule-card">
<div class="ui-schedule-card-head">
<h3 class="ui-schedule-card-name">{schedule.name}</h3>
<Badge tone={STATE_TONE[state]} value={state} />
<Badge
value={
schedule.interval_seconds === null
? "once"
: `every ${fmtInterval(schedule.interval_seconds)}`
}
/>
</div>
<div class="ui-schedule-card-agents">
agents: {schedule.agents.join(", ")}
</div>
<p class="ui-schedule-card-body">{schedule.body}</p>
<div class="ui-schedule-card-next">
{schedule.next_fire_unix !== null ? (
<>
next fire:{" "}
<span title={fmtLocal(schedule.next_fire_unix)}>
<RelativeTime epochMs={schedule.next_fire_unix * 1000} upcoming />
</span>
</>
) : state === "paused" ? (
"paused, will not fire"
) : (
<>
fired{" "}
<span title={fmtLocal(schedule.anchor_unix)}>
<RelativeTime epochMs={schedule.anchor_unix * 1000} />
</span>
</>
)}
</div>
<div class="ui-schedule-card-actions">
<Button onClick={onEdit} disabled={pending}>
edit
</Button>
{state !== "done" ? (
<Button onClick={onTogglePaused} disabled={pending}>
{schedule.paused ? "resume" : "pause"}
</Button>
) : null}
<Button onClick={onRemove} disabled={pending}>
remove
</Button>
</div>
</Card>
);
}