diff --git a/ui/src/components/TriggerListCard.tsx b/ui/src/components/TriggerListCard.tsx index 606faebdef..6ca4b0ed5c 100644 --- a/ui/src/components/TriggerListCard.tsx +++ b/ui/src/components/TriggerListCard.tsx @@ -49,90 +49,90 @@ export function TriggerListCard({ return (
-
-
- +
+
+
+ + {trigger.label || (isSchedule ? "Schedule" : isWebhook ? "Webhook" : "Trigger")} + + +
-
-
- - {trigger.label || (isSchedule ? "Schedule" : isWebhook ? "Webhook" : "Trigger")} - - - {trigger.kind} - - {!trigger.enabled && ( - - paused - +
+ + {trigger.kind} + + {!trigger.enabled && ( + + paused + + )} +
+ +
{summary}
+ {isSchedule && trigger.cronExpression && ( +
+ {trigger.cronExpression} + {trigger.timezone ? ` · ${trigger.timezone}` : ""} +
+ )} + +
+
+
Next run
+
{nextRun}
+
+
+
Last fired
+
{lastFired}
+
+
+
Last result
+
+ {trigger.lastResult ? ( + + {trigger.lastResult} + + ) : ( + )} -
-
{summary}
- {isSchedule && trigger.cronExpression && ( -
- {trigger.cronExpression} - {trigger.timezone ? ` · ${trigger.timezone}` : ""} -
- )} - -
-
-
Next run
-
{nextRun}
-
-
-
Last fired
-
{lastFired}
-
-
-
Last result
-
- {trigger.lastResult ? ( - - {trigger.lastResult} - - ) : ( - - )} -
-
-
+
+ -
- -
- {isWebhook && onRotateSecret && ( - - )} - - -
-
+
+ {isWebhook && onRotateSecret && ( + + )} + +
); diff --git a/ui/src/pages/RoutineDetail.tsx b/ui/src/pages/RoutineDetail.tsx index f9501b8c01..fdb72e485e 100644 --- a/ui/src/pages/RoutineDetail.tsx +++ b/ui/src/pages/RoutineDetail.tsx @@ -11,6 +11,7 @@ import { Plus, Repeat, Save, + SlidersHorizontal, } from "lucide-react"; import { routinesApi, type RoutineTriggerResponse, type RotateRoutineTriggerResponse } from "../api/routines"; import { TriggerListCard } from "../components/TriggerListCard"; @@ -22,7 +23,9 @@ import { agentsApi } from "../api/agents"; import { projectsApi } from "../api/projects"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; +import { usePanel } from "../context/PanelContext"; import { useToast } from "../context/ToastContext"; +import { cn } from "../lib/utils"; import { queryKeys } from "../lib/queryKeys"; import { timeAgo } from "../lib/timeAgo"; import { ToggleSwitch } from "@/components/ui/toggle-switch"; @@ -136,6 +139,7 @@ export function RoutineDetail() { const navigate = useNavigate(); const location = useLocation(); const { pushToast } = useToast(); + const { openPanel, closePanel, panelVisible, setPanelVisible } = usePanel(); const hydratedRoutineIdRef = useRef(null); const titleInputRef = useRef(null); const descriptionEditorRef = useRef(null); @@ -519,6 +523,167 @@ export function RoutineDetail() { const currentAssignee = editDraft.assigneeAgentId ? agentById.get(editDraft.assigneeAgentId) ?? null : null; const currentProject = editDraft.projectId ? projectById.get(editDraft.projectId) ?? null : null; + const renderActivityTabs = () => { + if (!routine) return null; + return ( + + + + + Triggers + + + + Runs + {hasLiveRun && } + + + + Activity + + + + + + + {routine.triggers.length === 0 ? ( +
+

No triggers yet

+

+ Triggers fire this routine on a schedule or via webhook. +

+ +
+ ) : ( +
+ {routine.triggers.map((trigger) => ( + { + setEditingTrigger(trigger); + setTriggerDialogOpen(true); + }} + onDelete={() => setTriggerPendingDelete(trigger)} + onToggleEnabled={(enabled) => { + setTogglingTriggerId(trigger.id); + updateTrigger.mutate({ id: trigger.id, patch: { enabled } }); + }} + onRotateSecret={ + trigger.kind === "webhook" + ? () => rotateTrigger.mutate(trigger.id) + : undefined + } + togglePending={togglingTriggerId === trigger.id} + /> + ))} +
+ )} +
+ + + {hasLiveRun && activeIssueId && routine && ( + + )} + {(routineRuns ?? []).length === 0 ? ( +

No runs yet.

+ ) : ( +
+ {(routineRuns ?? []).map((run) => ( +
+
+ {run.source} + + {run.status.replaceAll("_", " ")} + +
+ {(run.trigger || run.linkedIssue) && ( +
+ {run.trigger && ( + {run.trigger.label ?? run.trigger.kind} + )} + {run.linkedIssue && ( + + {run.linkedIssue.identifier ?? run.linkedIssue.id.slice(0, 8)} + + )} +
+ )} + {timeAgo(run.triggeredAt)} +
+ ))} +
+ )} +
+ + + {(activity ?? []).length === 0 ? ( +

No activity yet.

+ ) : ( +
+ {(activity ?? []).map((event) => ( +
+ {event.action.replaceAll(".", " ")} + {event.details && Object.keys(event.details).length > 0 && ( +
+ {Object.entries(event.details).slice(0, 3).map(([key, value], i) => ( + + {i > 0 && ·} + {key.replaceAll("_", " ")}:{" "} + {formatActivityDetailValue(value)} + + ))} +
+ )} + {timeAgo(event.createdAt)} +
+ ))} +
+ )} +
+
+ ); + }; + + useEffect(() => { + if (!routine) { + closePanel(); + return; + } + openPanel(renderActivityTabs()); + return () => closePanel(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + routine, + routineRuns, + activity, + activeTab, + hasLiveRun, + activeIssueId, + togglingTriggerId, + openPanel, + closePanel, + ]); + if (!selectedCompanyId) { return ; } @@ -612,6 +777,18 @@ export function RoutineDetail() { {automationLabel} +
@@ -820,149 +997,12 @@ export function RoutineDetail() {
- + - {/* Tabs */} - - - - - Triggers - - - - Runs - {hasLiveRun && } - - - - Activity - - - - -
-
-

Triggers

-

- Schedules and webhooks that fire this routine. -

-
- -
- - {routine.triggers.length === 0 ? ( -
-

No triggers yet

-

- Triggers fire this routine on a schedule or via webhook. -

- -
- ) : ( -
- {routine.triggers.map((trigger) => ( - { - setEditingTrigger(trigger); - setTriggerDialogOpen(true); - }} - onDelete={() => setTriggerPendingDelete(trigger)} - onToggleEnabled={(enabled) => { - setTogglingTriggerId(trigger.id); - updateTrigger.mutate({ id: trigger.id, patch: { enabled } }); - }} - onRotateSecret={ - trigger.kind === "webhook" - ? () => rotateTrigger.mutate(trigger.id) - : undefined - } - togglePending={togglingTriggerId === trigger.id} - /> - ))} -
- )} -
- - - {hasLiveRun && activeIssueId && routine && ( - - )} - {(routineRuns ?? []).length === 0 ? ( -

No runs yet.

- ) : ( -
- {(routineRuns ?? []).map((run) => ( -
-
- {run.source} - - {run.status.replaceAll("_", " ")} - - {run.trigger && ( - {run.trigger.label ?? run.trigger.kind} - )} - {run.linkedIssue && ( - - {run.linkedIssue.identifier ?? run.linkedIssue.id.slice(0, 8)} - - )} -
- {timeAgo(run.triggeredAt)} -
- ))} -
- )} -
- - - {(activity ?? []).length === 0 ? ( -

No activity yet.

- ) : ( -
- {(activity ?? []).map((event) => ( -
-
- {event.action.replaceAll(".", " ")} - {event.details && Object.keys(event.details).length > 0 && ( - - {Object.entries(event.details).slice(0, 3).map(([key, value], i) => ( - - {i > 0 && ·} - {key.replaceAll("_", " ")}:{" "} - {formatActivityDetailValue(value)} - - ))} - - )} -
- {timeAgo(event.createdAt)} -
- ))} -
- )} -
-
+ {/* Tabs (mobile only — desktop renders in the right properties panel) */} +
+ {renderActivityTabs()} +