-
+
+
+
+
+ {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}` : ""}
-
- )}
-
-
-
-
-
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}
+