@agent-native/dispatch 0.13.10 → 0.13.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/actions/navigate.js +1 -1
  2. package/dist/actions/navigate.js.map +1 -1
  3. package/dist/components/agents-panel.js +3 -3
  4. package/dist/components/agents-panel.js.map +1 -1
  5. package/dist/components/app-keys-popover.d.ts.map +1 -1
  6. package/dist/components/app-keys-popover.js +3 -3
  7. package/dist/components/app-keys-popover.js.map +1 -1
  8. package/dist/components/create-app-popover.d.ts.map +1 -1
  9. package/dist/components/create-app-popover.js +2 -2
  10. package/dist/components/create-app-popover.js.map +1 -1
  11. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  12. package/dist/components/dispatch-control-plane.js +19 -7
  13. package/dist/components/dispatch-control-plane.js.map +1 -1
  14. package/dist/components/dispatch-shell.d.ts.map +1 -1
  15. package/dist/components/dispatch-shell.js +2 -2
  16. package/dist/components/dispatch-shell.js.map +1 -1
  17. package/dist/components/layout/Header.js +2 -1
  18. package/dist/components/layout/Header.js.map +1 -1
  19. package/dist/components/layout/Layout.d.ts.map +1 -1
  20. package/dist/components/layout/Layout.js +12 -5
  21. package/dist/components/layout/Layout.js.map +1 -1
  22. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  23. package/dist/components/messaging-setup-panel.js +4 -4
  24. package/dist/components/messaging-setup-panel.js.map +1 -1
  25. package/dist/components/task-queue-health.d.ts +2 -0
  26. package/dist/components/task-queue-health.d.ts.map +1 -0
  27. package/dist/components/task-queue-health.js +49 -0
  28. package/dist/components/task-queue-health.js.map +1 -0
  29. package/dist/components/ui/accordion.js.map +1 -1
  30. package/dist/components/ui/alert-dialog.js +1 -1
  31. package/dist/components/ui/alert-dialog.js.map +1 -1
  32. package/dist/components/ui/alert.js.map +1 -1
  33. package/dist/components/ui/avatar.js.map +1 -1
  34. package/dist/components/ui/badge.js.map +1 -1
  35. package/dist/components/ui/breadcrumb.js.map +1 -1
  36. package/dist/components/ui/button.js.map +1 -1
  37. package/dist/components/ui/calendar.js +1 -1
  38. package/dist/components/ui/calendar.js.map +1 -1
  39. package/dist/components/ui/card.js.map +1 -1
  40. package/dist/components/ui/carousel.d.ts +2 -2
  41. package/dist/components/ui/carousel.d.ts.map +1 -1
  42. package/dist/components/ui/carousel.js +1 -1
  43. package/dist/components/ui/carousel.js.map +1 -1
  44. package/dist/components/ui/chart.js.map +1 -1
  45. package/dist/components/ui/checkbox.js.map +1 -1
  46. package/dist/components/ui/command.js +1 -1
  47. package/dist/components/ui/command.js.map +1 -1
  48. package/dist/components/ui/context-menu.js.map +1 -1
  49. package/dist/components/ui/dialog.js.map +1 -1
  50. package/dist/components/ui/drawer.js.map +1 -1
  51. package/dist/components/ui/dropdown-menu.js.map +1 -1
  52. package/dist/components/ui/form.js +1 -1
  53. package/dist/components/ui/form.js.map +1 -1
  54. package/dist/components/ui/hover-card.js.map +1 -1
  55. package/dist/components/ui/input-otp.js.map +1 -1
  56. package/dist/components/ui/input.js.map +1 -1
  57. package/dist/components/ui/label.js.map +1 -1
  58. package/dist/components/ui/menubar.js.map +1 -1
  59. package/dist/components/ui/navigation-menu.js.map +1 -1
  60. package/dist/components/ui/pagination.d.ts +1 -1
  61. package/dist/components/ui/pagination.d.ts.map +1 -1
  62. package/dist/components/ui/pagination.js +1 -1
  63. package/dist/components/ui/pagination.js.map +1 -1
  64. package/dist/components/ui/popover.js.map +1 -1
  65. package/dist/components/ui/progress.js.map +1 -1
  66. package/dist/components/ui/radio-group.js.map +1 -1
  67. package/dist/components/ui/resizable.js.map +1 -1
  68. package/dist/components/ui/scroll-area.js.map +1 -1
  69. package/dist/components/ui/select.js.map +1 -1
  70. package/dist/components/ui/separator.js.map +1 -1
  71. package/dist/components/ui/sheet.js.map +1 -1
  72. package/dist/components/ui/sidebar.d.ts +2 -2
  73. package/dist/components/ui/sidebar.d.ts.map +1 -1
  74. package/dist/components/ui/sidebar.js +6 -6
  75. package/dist/components/ui/sidebar.js.map +1 -1
  76. package/dist/components/ui/skeleton.js.map +1 -1
  77. package/dist/components/ui/slider.js.map +1 -1
  78. package/dist/components/ui/sonner.js.map +1 -1
  79. package/dist/components/ui/spinner.js.map +1 -1
  80. package/dist/components/ui/switch.js.map +1 -1
  81. package/dist/components/ui/table.js.map +1 -1
  82. package/dist/components/ui/tabs.js.map +1 -1
  83. package/dist/components/ui/textarea.js.map +1 -1
  84. package/dist/components/ui/toast.js.map +1 -1
  85. package/dist/components/ui/toaster.js +1 -1
  86. package/dist/components/ui/toaster.js.map +1 -1
  87. package/dist/components/ui/toggle-group.js +1 -1
  88. package/dist/components/ui/toggle-group.js.map +1 -1
  89. package/dist/components/ui/toggle.js.map +1 -1
  90. package/dist/components/ui/tooltip.js.map +1 -1
  91. package/dist/components/ui/use-toast.d.ts.map +1 -1
  92. package/dist/components/ui/use-toast.js.map +1 -1
  93. package/dist/components/workspace-app-card.d.ts.map +1 -1
  94. package/dist/components/workspace-app-card.js +10 -10
  95. package/dist/components/workspace-app-card.js.map +1 -1
  96. package/dist/components/workspace-resource-effective-stack.js +1 -1
  97. package/dist/components/workspace-resource-effective-stack.js.map +1 -1
  98. package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -1
  99. package/dist/components/workspace-resource-impact-preview.js +2 -2
  100. package/dist/components/workspace-resource-impact-preview.js.map +1 -1
  101. package/dist/hooks/use-navigation-state.js +5 -0
  102. package/dist/hooks/use-navigation-state.js.map +1 -1
  103. package/dist/hooks/use-toast.d.ts.map +1 -1
  104. package/dist/hooks/use-toast.js.map +1 -1
  105. package/dist/lib/automation-display.d.ts +13 -0
  106. package/dist/lib/automation-display.d.ts.map +1 -0
  107. package/dist/lib/automation-display.js +62 -0
  108. package/dist/lib/automation-display.js.map +1 -0
  109. package/dist/lib/task-queue.d.ts +19 -0
  110. package/dist/lib/task-queue.d.ts.map +1 -0
  111. package/dist/lib/task-queue.js +54 -0
  112. package/dist/lib/task-queue.js.map +1 -0
  113. package/dist/routes/index.d.ts.map +1 -1
  114. package/dist/routes/index.js +1 -0
  115. package/dist/routes/index.js.map +1 -1
  116. package/dist/routes/pages/_index.js.map +1 -1
  117. package/dist/routes/pages/agents.d.ts.map +1 -1
  118. package/dist/routes/pages/agents.js +1 -1
  119. package/dist/routes/pages/agents.js.map +1 -1
  120. package/dist/routes/pages/approval.js.map +1 -1
  121. package/dist/routes/pages/approvals.js.map +1 -1
  122. package/dist/routes/pages/apps.$appId.js.map +1 -1
  123. package/dist/routes/pages/apps.js.map +1 -1
  124. package/dist/routes/pages/audit.js.map +1 -1
  125. package/dist/routes/pages/automations.d.ts +5 -0
  126. package/dist/routes/pages/automations.d.ts.map +1 -0
  127. package/dist/routes/pages/automations.js +106 -0
  128. package/dist/routes/pages/automations.js.map +1 -0
  129. package/dist/routes/pages/chat.js.map +1 -1
  130. package/dist/routes/pages/destinations.d.ts.map +1 -1
  131. package/dist/routes/pages/destinations.js +31 -27
  132. package/dist/routes/pages/destinations.js.map +1 -1
  133. package/dist/routes/pages/dreams.d.ts.map +1 -1
  134. package/dist/routes/pages/dreams.js +1 -1
  135. package/dist/routes/pages/dreams.js.map +1 -1
  136. package/dist/routes/pages/identities.js.map +1 -1
  137. package/dist/routes/pages/integrations.js.map +1 -1
  138. package/dist/routes/pages/messaging.js.map +1 -1
  139. package/dist/routes/pages/metrics.js.map +1 -1
  140. package/dist/routes/pages/new-app.js.map +1 -1
  141. package/dist/routes/pages/overview.d.ts +2 -2
  142. package/dist/routes/pages/overview.js.map +1 -1
  143. package/dist/routes/pages/settings.d.ts.map +1 -1
  144. package/dist/routes/pages/settings.js +2 -2
  145. package/dist/routes/pages/settings.js.map +1 -1
  146. package/dist/routes/pages/thread-debug.d.ts.map +1 -1
  147. package/dist/routes/pages/thread-debug.js +1 -1
  148. package/dist/routes/pages/thread-debug.js.map +1 -1
  149. package/dist/routes/pages/vault.d.ts.map +1 -1
  150. package/dist/routes/pages/vault.js +1 -1
  151. package/dist/routes/pages/vault.js.map +1 -1
  152. package/dist/routes/pages/workspace.d.ts.map +1 -1
  153. package/dist/routes/pages/workspace.js +1 -1
  154. package/dist/routes/pages/workspace.js.map +1 -1
  155. package/dist/server/lib/pre-auth-routing.d.ts.map +1 -1
  156. package/dist/server/lib/pre-auth-routing.js +1 -0
  157. package/dist/server/lib/pre-auth-routing.js.map +1 -1
  158. package/package.json +7 -7
  159. package/src/actions/navigate.ts +1 -1
  160. package/src/components/agents-panel.tsx +3 -3
  161. package/src/components/app-keys-popover.tsx +3 -7
  162. package/src/components/create-app-popover.tsx +2 -6
  163. package/src/components/dispatch-control-plane.tsx +48 -7
  164. package/src/components/dispatch-shell.tsx +2 -6
  165. package/src/components/layout/Header.tsx +2 -2
  166. package/src/components/layout/Layout.tsx +13 -15
  167. package/src/components/messaging-setup-panel.tsx +4 -8
  168. package/src/components/task-queue-health.tsx +166 -0
  169. package/src/components/ui/accordion.tsx +1 -1
  170. package/src/components/ui/alert-dialog.tsx +2 -2
  171. package/src/components/ui/alert.tsx +1 -1
  172. package/src/components/ui/avatar.tsx +1 -1
  173. package/src/components/ui/badge.tsx +1 -1
  174. package/src/components/ui/breadcrumb.tsx +1 -1
  175. package/src/components/ui/button.tsx +1 -1
  176. package/src/components/ui/calendar.tsx +2 -2
  177. package/src/components/ui/card.tsx +1 -1
  178. package/src/components/ui/carousel.tsx +2 -2
  179. package/src/components/ui/chart.tsx +1 -1
  180. package/src/components/ui/checkbox.tsx +1 -1
  181. package/src/components/ui/command.tsx +2 -2
  182. package/src/components/ui/context-menu.tsx +1 -1
  183. package/src/components/ui/dialog.tsx +1 -1
  184. package/src/components/ui/drawer.tsx +1 -1
  185. package/src/components/ui/dropdown-menu.tsx +1 -1
  186. package/src/components/ui/form.tsx +2 -2
  187. package/src/components/ui/hover-card.tsx +1 -1
  188. package/src/components/ui/input-otp.tsx +1 -1
  189. package/src/components/ui/input.tsx +1 -1
  190. package/src/components/ui/label.tsx +1 -1
  191. package/src/components/ui/menubar.tsx +1 -1
  192. package/src/components/ui/navigation-menu.tsx +1 -1
  193. package/src/components/ui/pagination.tsx +2 -2
  194. package/src/components/ui/popover.tsx +1 -1
  195. package/src/components/ui/progress.tsx +1 -1
  196. package/src/components/ui/radio-group.tsx +1 -1
  197. package/src/components/ui/resizable.tsx +1 -1
  198. package/src/components/ui/scroll-area.tsx +1 -1
  199. package/src/components/ui/select.tsx +1 -1
  200. package/src/components/ui/separator.tsx +1 -1
  201. package/src/components/ui/sheet.tsx +1 -1
  202. package/src/components/ui/sidebar.tsx +8 -13
  203. package/src/components/ui/skeleton.tsx +1 -1
  204. package/src/components/ui/slider.tsx +1 -1
  205. package/src/components/ui/sonner.tsx +1 -1
  206. package/src/components/ui/spinner.tsx +1 -1
  207. package/src/components/ui/switch.tsx +1 -1
  208. package/src/components/ui/table.tsx +1 -1
  209. package/src/components/ui/tabs.tsx +1 -1
  210. package/src/components/ui/textarea.tsx +1 -1
  211. package/src/components/ui/toast.tsx +1 -1
  212. package/src/components/ui/toaster.tsx +2 -2
  213. package/src/components/ui/toggle-group.tsx +2 -2
  214. package/src/components/ui/toggle.tsx +1 -1
  215. package/src/components/ui/tooltip.tsx +1 -1
  216. package/src/components/ui/use-toast.ts +1 -1
  217. package/src/components/workspace-app-card.tsx +16 -20
  218. package/src/components/workspace-resource-effective-stack.tsx +2 -2
  219. package/src/components/workspace-resource-impact-preview.tsx +2 -3
  220. package/src/hooks/use-navigation-state.spec.ts +7 -0
  221. package/src/hooks/use-navigation-state.ts +4 -0
  222. package/src/hooks/use-toast.ts +1 -1
  223. package/src/lib/automation-display.spec.ts +95 -0
  224. package/src/lib/automation-display.ts +70 -0
  225. package/src/lib/task-queue.spec.ts +57 -0
  226. package/src/lib/task-queue.ts +72 -0
  227. package/src/routes/index.ts +1 -0
  228. package/src/routes/pages/_index.tsx +1 -1
  229. package/src/routes/pages/agents.tsx +8 -5
  230. package/src/routes/pages/approval.tsx +4 -4
  231. package/src/routes/pages/approvals.tsx +4 -4
  232. package/src/routes/pages/apps.$appId.tsx +5 -5
  233. package/src/routes/pages/apps.tsx +8 -8
  234. package/src/routes/pages/audit.tsx +1 -1
  235. package/src/routes/pages/automations.tsx +280 -0
  236. package/src/routes/pages/chat.tsx +1 -1
  237. package/src/routes/pages/destinations.tsx +163 -154
  238. package/src/routes/pages/dreams.tsx +22 -18
  239. package/src/routes/pages/identities.tsx +2 -2
  240. package/src/routes/pages/integrations.tsx +8 -8
  241. package/src/routes/pages/messaging.tsx +2 -2
  242. package/src/routes/pages/metrics.tsx +6 -6
  243. package/src/routes/pages/new-app.tsx +1 -1
  244. package/src/routes/pages/overview.tsx +1 -1
  245. package/src/routes/pages/settings.tsx +37 -6
  246. package/src/routes/pages/thread-debug.tsx +14 -9
  247. package/src/routes/pages/vault.tsx +17 -12
  248. package/src/routes/pages/workspace.tsx +17 -12
  249. package/src/server/lib/pre-auth-routing.ts +1 -0
@@ -2,12 +2,8 @@ import { useT } from "@agent-native/core/client";
2
2
  import { IconInfoCircle } from "@tabler/icons-react";
3
3
  import { type ReactNode } from "react";
4
4
 
5
- import { useSetPageTitle } from "@/components/layout/HeaderActions";
6
- import {
7
- Popover,
8
- PopoverContent,
9
- PopoverTrigger,
10
- } from "@/components/ui/popover";
5
+ import { useSetPageTitle } from "./layout/HeaderActions";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
11
7
 
12
8
  /**
13
9
  * DispatchShell renders the per-page title (with an optional click-to-open
@@ -3,8 +3,7 @@ import { RunsTray } from "@agent-native/core/client/progress";
3
3
  import { IconLayoutSidebar } from "@tabler/icons-react";
4
4
  import { useLocation, useNavigate } from "react-router";
5
5
 
6
- import { Button } from "@/components/ui/button";
7
-
6
+ import { Button } from "../ui/button";
8
7
  import { useHeaderTitle, useHeaderActions } from "./HeaderActions";
9
8
 
10
9
  const pageTitles: Record<string, string> = {
@@ -18,6 +17,7 @@ const pageTitles: Record<string, string> = {
18
17
  "/destinations": "Destinations",
19
18
  "/identities": "Identities",
20
19
  "/approvals": "Approvals",
20
+ "/automations": "Automations",
21
21
  "/audit": "Audit",
22
22
  "/settings": "Settings",
23
23
  };
@@ -37,6 +37,7 @@ import {
37
37
  IconLayoutSidebarLeftExpand,
38
38
  IconPuzzle,
39
39
  IconSettings,
40
+ IconSettingsAutomation,
40
41
  IconShieldCheck,
41
42
  } from "@tabler/icons-react";
42
43
  import {
@@ -50,26 +51,16 @@ import {
50
51
  } from "react";
51
52
  import { NavLink, useLocation, useNavigate } from "react-router";
52
53
 
54
+ import { cn } from "../../lib/utils";
53
55
  import {
54
56
  DropdownMenu,
55
57
  DropdownMenuContent,
56
58
  DropdownMenuItem,
57
59
  DropdownMenuTrigger,
58
- } from "@/components/ui/dropdown-menu";
59
- import { Input } from "@/components/ui/input";
60
- import {
61
- Sheet,
62
- SheetContent,
63
- SheetDescription,
64
- SheetTitle,
65
- } from "@/components/ui/sheet";
66
- import {
67
- Tooltip,
68
- TooltipContent,
69
- TooltipTrigger,
70
- } from "@/components/ui/tooltip";
71
- import { cn } from "@/lib/utils";
72
-
60
+ } from "../ui/dropdown-menu";
61
+ import { Input } from "../ui/input";
62
+ import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../ui/sheet";
63
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
73
64
  import { Header } from "./Header";
74
65
  import { HeaderActionsProvider } from "./HeaderActions";
75
66
 
@@ -188,6 +179,13 @@ const OPERATIONS_NAV_ITEMS = [
188
179
  icon: IconShieldCheck,
189
180
  section: "operations",
190
181
  },
182
+ {
183
+ id: "automations",
184
+ to: "/automations",
185
+ label: "Automations",
186
+ icon: IconSettingsAutomation,
187
+ section: "operations",
188
+ },
191
189
  {
192
190
  id: "audit",
193
191
  to: "/audit",
@@ -14,18 +14,14 @@ import {
14
14
  import { useEffect, useMemo, useState } from "react";
15
15
  import { toast } from "sonner";
16
16
 
17
- import { Button } from "@/components/ui/button";
17
+ import { Button } from "./ui/button";
18
18
  import {
19
19
  Collapsible,
20
20
  CollapsibleContent,
21
21
  CollapsibleTrigger,
22
- } from "@/components/ui/collapsible";
23
- import { Input } from "@/components/ui/input";
24
- import {
25
- Tooltip,
26
- TooltipContent,
27
- TooltipTrigger,
28
- } from "@/components/ui/tooltip";
22
+ } from "./ui/collapsible";
23
+ import { Input } from "./ui/input";
24
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
29
25
 
30
26
  interface EnvStatus {
31
27
  key: string;
@@ -0,0 +1,166 @@
1
+ import { useChangeVersions, useT } from "@agent-native/core/client";
2
+ import { IconAlertTriangle, IconPlayerPlay } from "@tabler/icons-react";
3
+ import { useQuery } from "@tanstack/react-query";
4
+
5
+ import {
6
+ formatQueueAgeSeconds,
7
+ getDispatchTaskQueueStats,
8
+ type TaskQueueStats,
9
+ ZERO_TASK_QUEUE_STATS,
10
+ } from "../lib/task-queue";
11
+ import { cn } from "../lib/utils";
12
+ import { Skeleton } from "./ui/skeleton";
13
+
14
+ const TASK_QUEUE_QUERY_KEY = ["dispatch-task-queue"] as const;
15
+
16
+ function formatNumber(value: number): string {
17
+ return new Intl.NumberFormat(undefined, {
18
+ notation: value >= 10_000 ? "compact" : "standard",
19
+ }).format(value);
20
+ }
21
+
22
+ function useTaskQueueStats() {
23
+ const version = useChangeVersions(["action", "screen-refresh"]);
24
+ return useQuery<TaskQueueStats>({
25
+ queryKey: [...TASK_QUEUE_QUERY_KEY, version],
26
+ queryFn: getDispatchTaskQueueStats,
27
+ placeholderData: (prev) => prev,
28
+ refetchInterval: 15_000,
29
+ staleTime: 5_000,
30
+ });
31
+ }
32
+
33
+ function QueueCell({
34
+ label,
35
+ value,
36
+ danger,
37
+ }: {
38
+ label: string;
39
+ value: number;
40
+ danger?: boolean;
41
+ }) {
42
+ return (
43
+ <div className="rounded-md border bg-background px-2 py-2">
44
+ <div
45
+ className={cn(
46
+ "text-lg font-semibold text-foreground",
47
+ danger && "text-destructive",
48
+ )}
49
+ >
50
+ {formatNumber(value)}
51
+ </div>
52
+ <div className="truncate text-[11px] text-muted-foreground">{label}</div>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ export function TaskQueueHealth() {
58
+ const t = useT();
59
+ const query = useTaskQueueStats();
60
+ const taskQueue = query.data ?? ZERO_TASK_QUEUE_STATS;
61
+ const hasFailure = taskQueue.failed_last_hour > 0;
62
+ const hasBacklog =
63
+ taskQueue.pending > 5 || taskQueue.oldest_pending_age_seconds > 300;
64
+ const oldestAge = formatQueueAgeSeconds(taskQueue.oldest_pending_age_seconds);
65
+ const oldestAgeLabel =
66
+ oldestAge === "none" ? t("dispatch.pages.queueAgeNone") : oldestAge;
67
+
68
+ return (
69
+ <section className="rounded-lg border bg-card p-4">
70
+ <div className="flex min-w-0 items-center justify-between gap-3">
71
+ <div className="flex min-w-0 items-center gap-2">
72
+ <IconPlayerPlay
73
+ size={16}
74
+ className="shrink-0 text-muted-foreground"
75
+ />
76
+ <div className="min-w-0">
77
+ <h2 className="truncate text-sm font-semibold text-foreground">
78
+ {t("dispatch.pages.deliveryQueue")}
79
+ </h2>
80
+ <p className="mt-0.5 truncate text-xs text-muted-foreground">
81
+ {hasFailure
82
+ ? t("dispatch.pages.failedLastHour", {
83
+ count: taskQueue.failed_last_hour,
84
+ })
85
+ : t("dispatch.pages.processingCount", {
86
+ count: taskQueue.processing,
87
+ })}
88
+ </p>
89
+ </div>
90
+ </div>
91
+ </div>
92
+
93
+ {query.isLoading && !query.data ? (
94
+ <div className="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4">
95
+ {Array.from({ length: 4 }).map((_, index) => (
96
+ <Skeleton key={index} className="h-14 rounded-md" />
97
+ ))}
98
+ </div>
99
+ ) : (
100
+ <>
101
+ <div className="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4">
102
+ <QueueCell
103
+ label={t("dispatch.pages.queued")}
104
+ value={taskQueue.pending}
105
+ />
106
+ <QueueCell
107
+ label={t("dispatch.pages.active")}
108
+ value={taskQueue.processing}
109
+ />
110
+ <QueueCell
111
+ label={t("dispatch.pages.done1h")}
112
+ value={taskQueue.completed_last_hour}
113
+ />
114
+ <QueueCell
115
+ label={t("dispatch.pages.failed1h")}
116
+ value={taskQueue.failed_last_hour}
117
+ danger={hasFailure}
118
+ />
119
+ </div>
120
+
121
+ <div
122
+ className={cn(
123
+ "mt-3 rounded-md border px-3 py-2 text-xs",
124
+ hasFailure
125
+ ? "border-destructive/30 bg-destructive/5 text-destructive"
126
+ : hasBacklog
127
+ ? "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
128
+ : "bg-muted/20 text-muted-foreground",
129
+ )}
130
+ >
131
+ {t("dispatch.pages.oldestQueued", { age: oldestAgeLabel })}
132
+ </div>
133
+
134
+ {hasFailure ? (
135
+ <div className="mt-3 flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive">
136
+ <IconAlertTriangle size={14} className="mt-0.5 shrink-0" />
137
+ <span>{t("dispatch.pages.queueFailureHint")}</span>
138
+ </div>
139
+ ) : null}
140
+
141
+ {taskQueue.recent_failures.length > 0 ? (
142
+ <div className="mt-3 divide-y rounded-md border">
143
+ {taskQueue.recent_failures.slice(0, 3).map((failure) => (
144
+ <div key={failure.id} className="px-3 py-2">
145
+ <div className="flex items-center justify-between gap-3 text-xs">
146
+ <span className="font-medium text-foreground">
147
+ {failure.platform || t("dispatch.pages.unknownPlatform")}
148
+ </span>
149
+ <span className="text-muted-foreground">
150
+ {t("dispatch.pages.attemptsCount", {
151
+ count: failure.attempts,
152
+ })}
153
+ </span>
154
+ </div>
155
+ <div className="mt-1 truncate text-xs text-muted-foreground">
156
+ {failure.error || t("dispatch.pages.noErrorMessage")}
157
+ </div>
158
+ </div>
159
+ ))}
160
+ </div>
161
+ ) : null}
162
+ </>
163
+ )}
164
+ </section>
165
+ );
166
+ }
@@ -2,7 +2,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion";
2
2
  import { IconChevronDown } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const Accordion = AccordionPrimitive.Root;
8
8
 
@@ -1,8 +1,8 @@
1
1
  import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
2
2
  import * as React from "react";
3
3
 
4
- import { buttonVariants } from "@/components/ui/button";
5
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
+ import { buttonVariants } from "./button";
6
6
 
7
7
  const AlertDialog = AlertDialogPrimitive.Root;
8
8
 
@@ -1,7 +1,7 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const alertVariants = cva(
7
7
  "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@@ -1,7 +1,7 @@
1
1
  import * as AvatarPrimitive from "@radix-ui/react-avatar";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const Avatar = React.forwardRef<
7
7
  React.ElementRef<typeof AvatarPrimitive.Root>,
@@ -1,7 +1,7 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const badgeVariants = cva(
7
7
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
@@ -2,7 +2,7 @@ import { Slot } from "@radix-ui/react-slot";
2
2
  import { IconChevronRight, IconDots } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const Breadcrumb = React.forwardRef<
8
8
  HTMLElement,
@@ -2,7 +2,7 @@ import { Slot } from "@radix-ui/react-slot";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const buttonVariants = cva(
8
8
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
@@ -2,8 +2,8 @@ import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
2
2
  import * as React from "react";
3
3
  import { DayPicker } from "react-day-picker";
4
4
 
5
- import { buttonVariants } from "@/components/ui/button";
6
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
+ import { buttonVariants } from "./button";
7
7
 
8
8
  export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9
9
 
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import { cn } from "@/lib/utils";
3
+ import { cn } from "../../lib/utils";
4
4
 
5
5
  const Card = React.forwardRef<
6
6
  HTMLDivElement,
@@ -4,8 +4,8 @@ import useEmblaCarousel, {
4
4
  } from "embla-carousel-react";
5
5
  import * as React from "react";
6
6
 
7
- import { Button } from "@/components/ui/button";
8
- import { cn } from "@/lib/utils";
7
+ import { cn } from "../../lib/utils";
8
+ import { Button } from "./button";
9
9
 
10
10
  type CarouselApi = UseEmblaCarouselType[1];
11
11
  type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as RechartsPrimitive from "recharts";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  // Format: { THEME_NAME: CSS_SELECTOR }
7
7
  const THEMES = { light: "", dark: ".dark" } as const;
@@ -2,7 +2,7 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2
2
  import { IconCheck } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const Checkbox = React.forwardRef<
8
8
  React.ElementRef<typeof CheckboxPrimitive.Root>,
@@ -3,8 +3,8 @@ import { IconSearch } from "@tabler/icons-react";
3
3
  import { Command as CommandPrimitive } from "cmdk";
4
4
  import * as React from "react";
5
5
 
6
- import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
7
- import { cn } from "@/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
+ import { Dialog, DialogContent, DialogTitle } from "./dialog";
8
8
 
9
9
  const Command = React.forwardRef<
10
10
  React.ElementRef<typeof CommandPrimitive>,
@@ -2,7 +2,7 @@ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
2
2
  import { IconCheck, IconChevronRight, IconCircle } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const ContextMenu = ContextMenuPrimitive.Root;
8
8
 
@@ -2,7 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
2
  import { IconX } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const Dialog = DialogPrimitive.Root;
8
8
 
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { Drawer as DrawerPrimitive } from "vaul";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const Drawer = ({
7
7
  shouldScaleBackground = true,
@@ -2,7 +2,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
2
  import { IconCheck, IconChevronRight, IconCircle } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const DropdownMenu = DropdownMenuPrimitive.Root;
8
8
 
@@ -10,8 +10,8 @@ import {
10
10
  useFormContext,
11
11
  } from "react-hook-form";
12
12
 
13
- import { Label } from "@/components/ui/label";
14
- import { cn } from "@/lib/utils";
13
+ import { cn } from "../../lib/utils";
14
+ import { Label } from "./label";
15
15
 
16
16
  const Form = FormProvider;
17
17
 
@@ -1,7 +1,7 @@
1
1
  import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const HoverCard = HoverCardPrimitive.Root;
7
7
 
@@ -2,7 +2,7 @@ import { IconPoint } from "@tabler/icons-react";
2
2
  import { OTPInput, OTPInputContext } from "input-otp";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const InputOTP = React.forwardRef<
8
8
  React.ElementRef<typeof OTPInput>,
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import { cn } from "@/lib/utils";
3
+ import { cn } from "../../lib/utils";
4
4
 
5
5
  const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
6
  ({ className, type, ...props }, ref) => {
@@ -2,7 +2,7 @@ import * as LabelPrimitive from "@radix-ui/react-label";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const labelVariants = cva(
8
8
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
@@ -2,7 +2,7 @@ import * as MenubarPrimitive from "@radix-ui/react-menubar";
2
2
  import { IconCheck, IconChevronRight, IconCircle } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  // Explicit type annotations needed because TS can't generate portable .d.ts
8
8
  // declarations for these without referencing internal Radix types from a
@@ -3,7 +3,7 @@ import { IconChevronDown } from "@tabler/icons-react";
3
3
  import { cva } from "class-variance-authority";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const NavigationMenu = React.forwardRef<
9
9
  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
@@ -5,8 +5,8 @@ import {
5
5
  } from "@tabler/icons-react";
6
6
  import * as React from "react";
7
7
 
8
- import { ButtonProps, buttonVariants } from "@/components/ui/button";
9
- import { cn } from "@/lib/utils";
8
+ import { cn } from "../../lib/utils";
9
+ import { ButtonProps, buttonVariants } from "./button";
10
10
 
11
11
  const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
12
12
  <nav
@@ -1,7 +1,7 @@
1
1
  import * as PopoverPrimitive from "@radix-ui/react-popover";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const Popover = PopoverPrimitive.Root;
7
7
 
@@ -1,7 +1,7 @@
1
1
  import * as ProgressPrimitive from "@radix-ui/react-progress";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const Progress = React.forwardRef<
7
7
  React.ElementRef<typeof ProgressPrimitive.Root>,
@@ -2,7 +2,7 @@ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
2
2
  import { IconCircle } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const RadioGroup = React.forwardRef<
8
8
  React.ElementRef<typeof RadioGroupPrimitive.Root>,
@@ -1,7 +1,7 @@
1
1
  import { IconGripVertical } from "@tabler/icons-react";
2
2
  import * as ResizablePrimitive from "react-resizable-panels";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const ResizablePanelGroup = ({
7
7
  className,
@@ -1,7 +1,7 @@
1
1
  import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const ScrollArea = React.forwardRef<
7
7
  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@@ -2,7 +2,7 @@ import * as SelectPrimitive from "@radix-ui/react-select";
2
2
  import { IconCheck, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
3
3
  import * as React from "react";
4
4
 
5
- import { cn } from "@/lib/utils";
5
+ import { cn } from "../../lib/utils";
6
6
 
7
7
  const Select = SelectPrimitive.Root;
8
8
 
@@ -1,7 +1,7 @@
1
1
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
2
2
  import * as React from "react";
3
3
 
4
- import { cn } from "@/lib/utils";
4
+ import { cn } from "../../lib/utils";
5
5
 
6
6
  const Separator = React.forwardRef<
7
7
  React.ElementRef<typeof SeparatorPrimitive.Root>,
@@ -3,7 +3,7 @@ import { IconX } from "@tabler/icons-react";
3
3
  import { cva, type VariantProps } from "class-variance-authority";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Sheet = SheetPrimitive.Root;
9
9