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:
parent
7f597f5c43
commit
63ee6d12be
14 changed files with 931 additions and 3 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue