({
}`}
>
{columns.map((col, ci) => (
- |
+ |
{typeof col.accessor === "function"
? col.accessor(row)
: (row[col.accessor] as ComponentChildren)}
diff --git a/dashboard/src/components/ui/empty-state.tsx b/dashboard/src/components/ui/empty-state.tsx
index f18e9a7..52f5f47 100644
--- a/dashboard/src/components/ui/empty-state.tsx
+++ b/dashboard/src/components/ui/empty-state.tsx
@@ -12,9 +12,7 @@ export function EmptyState({ message, subtitle }: EmptyStateProps) {
{message}
- {subtitle && (
- {subtitle}
- )}
+ {subtitle && {subtitle} }
);
}
diff --git a/dashboard/src/components/ui/pagination.tsx b/dashboard/src/components/ui/pagination.tsx
index 0b5081d..0d4c29f 100644
--- a/dashboard/src/components/ui/pagination.tsx
+++ b/dashboard/src/components/ui/pagination.tsx
@@ -7,12 +7,7 @@ interface PaginationProps {
onPageChange: (page: number) => void;
}
-export function Pagination({
- page,
- pageSize,
- itemCount,
- onPageChange,
-}: PaginationProps) {
+export function Pagination({ page, pageSize, itemCount, onPageChange }: PaginationProps) {
return (
@@ -20,6 +15,7 @@ export function Pagination({
diff --git a/dashboard/src/components/ui/toast.tsx b/dashboard/src/components/ui/toast.tsx
index b0519b3..35f65cd 100644
--- a/dashboard/src/components/ui/toast.tsx
+++ b/dashboard/src/components/ui/toast.tsx
@@ -1,7 +1,10 @@
-import { CheckCircle2, XCircle, Info, X } from "lucide-preact";
-import { toasts, dismissToast, type Toast } from "../../hooks/use-toast";
+import { CheckCircle2, Info, X, XCircle } from "lucide-preact";
+import { dismissToast, type Toast, toasts } from "../../hooks/use-toast";
-const TYPE_CONFIG: Record = {
+const TYPE_CONFIG: Record<
+ Toast["type"],
+ { border: string; icon: typeof CheckCircle2; iconColor: string }
+> = {
success: { border: "border-l-success", icon: CheckCircle2, iconColor: "text-success" },
error: { border: "border-l-danger", icon: XCircle, iconColor: "text-danger" },
info: { border: "border-l-info", icon: Info, iconColor: "text-info" },
@@ -25,6 +28,7 @@ export function ToastContainer() {
{t.message}
{!breakers?.length ? (
-
+
) : (
)}
diff --git a/dashboard/src/pages/dead-letters.tsx b/dashboard/src/pages/dead-letters.tsx
index 7346818..eeb0897 100644
--- a/dashboard/src/pages/dead-letters.tsx
+++ b/dashboard/src/pages/dead-letters.tsx
@@ -1,16 +1,16 @@
+import { RotateCcw, Skull, Trash2 } from "lucide-preact";
import { useState } from "preact/hooks";
-import { Skull, RotateCcw, Trash2 } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
-import { Pagination } from "../components/ui/pagination";
+import { apiPost } from "../api/client";
+import type { DeadLetter } from "../api/types";
import { Button } from "../components/ui/button";
import { ConfirmDialog } from "../components/ui/confirm-dialog";
-import { Loading } from "../components/ui/loading";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
+import { Loading } from "../components/ui/loading";
+import { Pagination } from "../components/ui/pagination";
+import { useApi } from "../hooks/use-api";
import { addToast } from "../hooks/use-toast";
-import { apiPost } from "../api/client";
import { fmtTime, truncateId } from "../lib/format";
-import type { DeadLetter } from "../api/types";
import type { RoutableProps } from "../lib/routes";
const PAGE_SIZE = 20;
@@ -19,10 +19,13 @@ export function DeadLetters(_props: RoutableProps) {
const [page, setPage] = useState(0);
const [showPurge, setShowPurge] = useState(false);
- const { data: items, loading, refetch } = useApi (
- `/api/dead-letters?limit=${PAGE_SIZE}&offset=${page * PAGE_SIZE}`,
- [page],
- );
+ const {
+ data: items,
+ loading,
+ refetch,
+ } = useApi(`/api/dead-letters?limit=${PAGE_SIZE}&offset=${page * PAGE_SIZE}`, [
+ page,
+ ]);
const handleRetry = async (id: string) => {
try {
@@ -53,7 +56,10 @@ export function DeadLetters(_props: RoutableProps) {
{
header: "Original Job",
accessor: (d) => (
-
+
{truncateId(d.original_job_id)}
),
@@ -69,8 +75,14 @@ export function DeadLetters(_props: RoutableProps) {
),
className: "max-w-[250px]",
},
- { header: "Retries", accessor: (d) => {d.retry_count} },
- { header: "Failed At", accessor: (d) => {fmtTime(d.failed_at)} },
+ {
+ header: "Retries",
+ accessor: (d) => {d.retry_count},
+ },
+ {
+ header: "Failed At",
+ accessor: (d) => {fmtTime(d.failed_at)},
+ },
{
header: "Actions",
accessor: (d) => (
@@ -108,7 +120,12 @@ export function DeadLetters(_props: RoutableProps) {
) : (
-
+
)}
diff --git a/dashboard/src/pages/job-detail.tsx b/dashboard/src/pages/job-detail.tsx
index 163dd35..0651fb3 100644
--- a/dashboard/src/pages/job-detail.tsx
+++ b/dashboard/src/pages/job-detail.tsx
@@ -1,17 +1,17 @@
import { FileText, RotateCcw } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
+import { route } from "preact-router";
+import { apiPost } from "../api/client";
+import type { DagData, Job, JobError, ReplayEntry, TaskLog } from "../api/types";
+import { DagViewer } from "../charts/dag-viewer";
import { Badge } from "../components/ui/badge";
-import { ProgressBar } from "../components/ui/progress-bar";
import { Button } from "../components/ui/button";
-import { Loading } from "../components/ui/loading";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
-import { DataTable, type Column } from "../components/ui/data-table";
-import { DagViewer } from "../charts/dag-viewer";
+import { Loading } from "../components/ui/loading";
+import { ProgressBar } from "../components/ui/progress-bar";
+import { useApi } from "../hooks/use-api";
import { addToast } from "../hooks/use-toast";
-import { apiPost } from "../api/client";
import { fmtTime, truncateId } from "../lib/format";
-import { route } from "preact-router";
-import type { Job, JobError, TaskLog, ReplayEntry, DagData } from "../api/types";
import type { RoutableProps } from "../lib/routes";
interface JobDetailProps extends RoutableProps {
@@ -26,7 +26,14 @@ const ERROR_COLUMNS: Column[] = [
const LOG_COLUMNS: Column[] = [
{ header: "Time", accessor: (l) => {fmtTime(l.logged_at)} },
- { header: "Level", accessor: (l) => },
+ {
+ header: "Level",
+ accessor: (l) => (
+
+ ),
+ },
{ header: "Message", accessor: "message" },
{ header: "Extra", accessor: (l) => l.extra ?? "\u2014", className: "max-w-[200px] truncate" },
];
@@ -35,14 +42,28 @@ const REPLAY_COLUMNS: Column[] = [
{
header: "Replay Job",
accessor: (r) => (
-
+
{truncateId(r.replay_job_id)}
),
},
- { header: "Replayed At", accessor: (r) => {fmtTime(r.replayed_at)} },
- { header: "Original Error", accessor: (r) => r.original_error ?? "\u2014", className: "max-w-[200px] truncate" },
- { header: "Replay Error", accessor: (r) => r.replay_error ?? "\u2014", className: "max-w-[200px] truncate" },
+ {
+ header: "Replayed At",
+ accessor: (r) => {fmtTime(r.replayed_at)},
+ },
+ {
+ header: "Original Error",
+ accessor: (r) => r.original_error ?? "\u2014",
+ className: "max-w-[200px] truncate",
+ },
+ {
+ header: "Replay Error",
+ accessor: (r) => r.replay_error ?? "\u2014",
+ className: "max-w-[200px] truncate",
+ },
];
export function JobDetail({ id }: JobDetailProps) {
@@ -58,7 +79,10 @@ export function JobDetail({ id }: JobDetailProps) {
const handleCancel = async () => {
try {
const res = await apiPost<{ cancelled: boolean }>(`/api/jobs/${id}/cancel`);
- addToast(res.cancelled ? "Job cancelled" : "Failed to cancel job", res.cancelled ? "success" : "error");
+ addToast(
+ res.cancelled ? "Job cancelled" : "Failed to cancel job",
+ res.cancelled ? "success" : "error",
+ );
refetch();
} catch {
addToast("Failed to cancel job", "error");
@@ -99,12 +123,18 @@ export function JobDetail({ id }: JobDetailProps) {
-
+
ID
- {job.id}
+
+ {job.id}
+
Status
-
+
+
+
Task
{job.task_name}
Queue
@@ -112,9 +142,13 @@ export function JobDetail({ id }: JobDetailProps) {
Priority
{job.priority}
Progress
-
+
+
+
Retries
- 0 ? "text-warning" : ""}>{job.retry_count} / {job.max_retries}
+ 0 ? "text-warning" : ""}>
+ {job.retry_count} / {job.max_retries}
+
Created
{fmtTime(job.created_at)}
Scheduled
@@ -128,7 +162,9 @@ export function JobDetail({ id }: JobDetailProps) {
{job.error && (
<>
Error
- {job.error}
+
+ {job.error}
+
>
)}
{job.unique_key && (
@@ -146,7 +182,9 @@ export function JobDetail({ id }: JobDetailProps) {
{job.status === "pending" && (
-
+
)}
@@ -135,12 +173,13 @@ export function Jobs(_props: RoutableProps) {
) : !jobs?.length ? (
) : (
- route(`/jobs/${j.id}`)}
- >
-
+ route(`/jobs/${j.id}`)}>
+
)}
diff --git a/dashboard/src/pages/logs.tsx b/dashboard/src/pages/logs.tsx
index a0f39b5..51ef323 100644
--- a/dashboard/src/pages/logs.tsx
+++ b/dashboard/src/pages/logs.tsx
@@ -1,12 +1,12 @@
-import { useState } from "preact/hooks";
import { ScrollText } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
+import { useState } from "preact/hooks";
+import type { TaskLog } from "../api/types";
import { Badge } from "../components/ui/badge";
-import { Loading } from "../components/ui/loading";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import { fmtTime, truncateId } from "../lib/format";
-import type { TaskLog } from "../api/types";
import type { RoutableProps } from "../lib/routes";
const LOG_COLUMNS: Column[] = [
@@ -14,7 +14,9 @@ const LOG_COLUMNS: Column[] = [
{
header: "Level",
accessor: (l) => (
-
+
),
},
{ header: "Task", accessor: (l) => {l.task_name} },
@@ -38,7 +40,10 @@ export function Logs(_props: RoutableProps) {
if (taskFilter) params.set("task", taskFilter);
if (levelFilter) params.set("level", levelFilter);
- const { data: logs, loading } = useApi(`/api/logs?${params}`, [taskFilter, levelFilter]);
+ const { data: logs, loading } = useApi(`/api/logs?${params}`, [
+ taskFilter,
+ levelFilter,
+ ]);
const inputClass =
"dark:bg-surface-3 bg-white dark:text-gray-200 text-slate-700 border dark:border-white/[0.06] border-slate-200 rounded-lg px-3 py-2 text-[13px] placeholder:text-muted/50 focus:border-accent/50 transition-colors";
@@ -56,8 +61,17 @@ export function Logs(_props: RoutableProps) {
- setTaskFilter((e.target as HTMLInputElement).value)} />
-
- {loading && !logs ? : !logs?.length ? : }
+ {loading && !logs ? (
+
+ ) : !logs?.length ? (
+
+ ) : (
+
+ )}
);
}
diff --git a/dashboard/src/pages/metrics.tsx b/dashboard/src/pages/metrics.tsx
index a27e6b7..9b0b84a 100644
--- a/dashboard/src/pages/metrics.tsx
+++ b/dashboard/src/pages/metrics.tsx
@@ -1,11 +1,11 @@
-import { useState } from "preact/hooks";
import { BarChart3 } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
-import { Loading } from "../components/ui/loading";
-import { EmptyState } from "../components/ui/empty-state";
-import { TimeseriesChart } from "../charts/timeseries-chart";
+import { useState } from "preact/hooks";
import type { MetricsResponse, TaskMetrics, TimeseriesBucket } from "../api/types";
+import { TimeseriesChart } from "../charts/timeseries-chart";
+import { type Column, DataTable } from "../components/ui/data-table";
+import { EmptyState } from "../components/ui/empty-state";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import type { RoutableProps } from "../lib/routes";
interface MetricsRow extends TaskMetrics {
@@ -21,14 +21,52 @@ function latencyColor(ms: number, threshold: { good: number; warn: number }): st
const METRICS_COLUMNS: Column[] = [
{ header: "Task", accessor: (r) => {r.task_name} },
{ header: "Total", accessor: (r) => {r.count} },
- { header: "Success", accessor: (r) => {r.success_count} },
- { header: "Failures", accessor: (r) => 0 ? "text-danger tabular-nums" : "text-muted tabular-nums"}>{r.failure_count} },
- { header: "Avg", accessor: (r) => {r.avg_ms}ms },
+ {
+ header: "Success",
+ accessor: (r) => {r.success_count},
+ },
+ {
+ header: "Failures",
+ accessor: (r) => (
+ 0 ? "text-danger tabular-nums" : "text-muted tabular-nums"}>
+ {r.failure_count}
+
+ ),
+ },
+ {
+ header: "Avg",
+ accessor: (r) => (
+
+ {r.avg_ms}ms
+
+ ),
+ },
{ header: "P50", accessor: (r) => {r.p50_ms}ms },
- { header: "P95", accessor: (r) => {r.p95_ms}ms },
- { header: "P99", accessor: (r) => {r.p99_ms}ms },
+ {
+ header: "P95",
+ accessor: (r) => (
+
+ {r.p95_ms}ms
+
+ ),
+ },
+ {
+ header: "P99",
+ accessor: (r) => (
+
+ {r.p99_ms}ms
+
+ ),
+ },
{ header: "Min", accessor: (r) => {r.min_ms}ms },
- { header: "Max", accessor: (r) => {r.max_ms}ms },
+ {
+ header: "Max",
+ accessor: (r) => (
+
+ {r.max_ms}ms
+
+ ),
+ },
];
const TIME_RANGES = [
@@ -39,7 +77,9 @@ const TIME_RANGES = [
export function Metrics(_props: RoutableProps) {
const [since, setSince] = useState(3600);
- const { data: metrics, loading } = useApi(`/api/metrics?since=${since}`, [since]);
+ const { data: metrics, loading } = useApi(`/api/metrics?since=${since}`, [
+ since,
+ ]);
const { data: timeseries } = useApi(
`/api/metrics/timeseries?since=${since}&bucket=${since <= 3600 ? 60 : since <= 21600 ? 300 : 900}`,
[since],
@@ -64,6 +104,7 @@ export function Metrics(_props: RoutableProps) {
{TIME_RANGES.map((r) => (
- {timeseries && timeseries.length > 0 && (
-
- )}
+ {timeseries && timeseries.length > 0 && }
{loading && !metrics ? (
diff --git a/dashboard/src/pages/overview.tsx b/dashboard/src/pages/overview.tsx
index c5cfc63..8407996 100644
--- a/dashboard/src/pages/overview.tsx
+++ b/dashboard/src/pages/overview.tsx
@@ -1,17 +1,17 @@
-import { useRef } from "preact/hooks";
import { LayoutDashboard } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { StatsGrid } from "../components/ui/stats-grid";
-import { DataTable, type Column } from "../components/ui/data-table";
+import { useRef } from "preact/hooks";
+import { route } from "preact-router";
+import type { Job, QueueStats } from "../api/types";
+import { ThroughputChart } from "../charts/throughput-chart";
import { Badge } from "../components/ui/badge";
-import { ProgressBar } from "../components/ui/progress-bar";
+import { type Column, DataTable } from "../components/ui/data-table";
import { Loading } from "../components/ui/loading";
-import { ThroughputChart } from "../charts/throughput-chart";
+import { ProgressBar } from "../components/ui/progress-bar";
+import { StatsGrid } from "../components/ui/stats-grid";
+import { useApi } from "../hooks/use-api";
+import { refreshInterval } from "../hooks/use-auto-refresh";
import { fmtTime, truncateId } from "../lib/format";
-import { route } from "preact-router";
-import type { QueueStats, Job } from "../api/types";
import type { RoutableProps } from "../lib/routes";
-import { refreshInterval } from "../hooks/use-auto-refresh";
const JOB_COLUMNS: Column[] = [
{
@@ -65,11 +65,7 @@ export function Overview(_props: RoutableProps) {
(latest 10)
{jobs?.length ? (
- route(`/jobs/${j.id}`)}
- />
+ route(`/jobs/${j.id}`)} />
) : null}
);
diff --git a/dashboard/src/pages/queues.tsx b/dashboard/src/pages/queues.tsx
index 8656eba..7c84ce5 100644
--- a/dashboard/src/pages/queues.tsx
+++ b/dashboard/src/pages/queues.tsx
@@ -1,13 +1,13 @@
-import { Layers, Play, Pause } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
+import { Layers, Pause, Play } from "lucide-preact";
+import { apiPost } from "../api/client";
+import type { QueueStatsMap } from "../api/types";
import { Badge } from "../components/ui/badge";
import { Button } from "../components/ui/button";
-import { Loading } from "../components/ui/loading";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import { addToast } from "../hooks/use-toast";
-import { apiPost } from "../api/client";
-import type { QueueStatsMap } from "../api/types";
import type { RoutableProps } from "../lib/routes";
interface QueueRow {
@@ -56,8 +56,14 @@ export function Queues(_props: RoutableProps) {
const columns: Column[] = [
{ header: "Queue", accessor: (r) => {r.name} },
- { header: "Pending", accessor: (r) => {r.pending} },
- { header: "Running", accessor: (r) => {r.running} },
+ {
+ header: "Pending",
+ accessor: (r) => {r.pending},
+ },
+ {
+ header: "Running",
+ accessor: (r) => {r.running},
+ },
{ header: "Status", accessor: (r) => },
{
header: "Actions",
diff --git a/dashboard/src/pages/resources.tsx b/dashboard/src/pages/resources.tsx
index f4a9a57..6a0a6d2 100644
--- a/dashboard/src/pages/resources.tsx
+++ b/dashboard/src/pages/resources.tsx
@@ -1,33 +1,48 @@
import { Box } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
+import type { ResourceStatus } from "../api/types";
import { Badge } from "../components/ui/badge";
-import { Loading } from "../components/ui/loading";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
-import type { ResourceStatus } from "../api/types";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import type { RoutableProps } from "../lib/routes";
const RESOURCE_COLUMNS: Column[] = [
{ header: "Name", accessor: (r) => {r.name} },
{ header: "Scope", accessor: (r) => },
{ header: "Health", accessor: (r) => },
- { header: "Init (ms)", accessor: (r) => {r.init_duration_ms.toFixed(1)}ms },
- { header: "Recreations", accessor: (r) => 0 ? "text-warning" : "text-muted"}`}>{r.recreations} },
+ {
+ header: "Init (ms)",
+ accessor: (r) => {r.init_duration_ms.toFixed(1)}ms,
+ },
+ {
+ header: "Recreations",
+ accessor: (r) => (
+ 0 ? "text-warning" : "text-muted"}`}>
+ {r.recreations}
+
+ ),
+ },
{
header: "Dependencies",
accessor: (r) =>
- r.depends_on.length
- ? {r.depends_on.join(", ")}
- : {"\u2014"},
+ r.depends_on.length ? (
+ {r.depends_on.join(", ")}
+ ) : (
+ {"\u2014"}
+ ),
},
{
header: "Pool",
accessor: (r) =>
r.pool ? (
- {r.pool.active}/{r.pool.size} active, {r.pool.idle} idle
+ {r.pool.active}/{r.pool.size} active,{" "}
+ {r.pool.idle} idle
- ) : {"\u2014"},
+ ) : (
+ {"\u2014"}
+ ),
},
];
@@ -49,7 +64,10 @@ export function Resources(_props: RoutableProps) {
{!resources?.length ? (
-
+
) : (
)}
diff --git a/dashboard/src/pages/system.tsx b/dashboard/src/pages/system.tsx
index f0b3e68..1eb7c7b 100644
--- a/dashboard/src/pages/system.tsx
+++ b/dashboard/src/pages/system.tsx
@@ -1,9 +1,9 @@
import { Cog } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { DataTable, type Column } from "../components/ui/data-table";
-import { Loading } from "../components/ui/loading";
+import type { InterceptionStats, ProxyStats } from "../api/types";
+import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
-import type { ProxyStats, InterceptionStats } from "../api/types";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import type { RoutableProps } from "../lib/routes";
interface ProxyRow {
@@ -21,25 +21,40 @@ interface InterceptionRow {
const PROXY_COLUMNS: Column[] = [
{ header: "Handler", accessor: (r) => {r.handler} },
- { header: "Reconstructions", accessor: (r) => {r.reconstructions} },
- { header: "Avg (ms)", accessor: (r) => {r.avg_ms.toFixed(1)}ms },
+ {
+ header: "Reconstructions",
+ accessor: (r) => {r.reconstructions},
+ },
+ {
+ header: "Avg (ms)",
+ accessor: (r) => {r.avg_ms.toFixed(1)}ms,
+ },
{
header: "Errors",
accessor: (r) => (
- 0 ? "text-danger font-medium" : "text-muted"}`}>{r.errors}
+ 0 ? "text-danger font-medium" : "text-muted"}`}>
+ {r.errors}
+
),
},
];
const INTERCEPTION_COLUMNS: Column[] = [
- { header: "Strategy", accessor: (r) => {r.strategy} },
+ {
+ header: "Strategy",
+ accessor: (r) => {r.strategy},
+ },
{ header: "Count", accessor: (r) => {r.count} },
- { header: "Avg (ms)", accessor: (r) => {r.avg_ms.toFixed(1)}ms },
+ {
+ header: "Avg (ms)",
+ accessor: (r) => {r.avg_ms.toFixed(1)}ms,
+ },
];
export function System(_props: RoutableProps) {
const { data: proxyStats, loading: proxyLoading } = useApi("/api/proxy-stats");
- const { data: interceptionStats, loading: interceptLoading } = useApi("/api/interception-stats");
+ const { data: interceptionStats, loading: interceptLoading } =
+ useApi("/api/interception-stats");
const proxyRows: ProxyRow[] = proxyStats
? Object.entries(proxyStats).map(([handler, s]) => ({ handler, ...s }))
@@ -62,11 +77,16 @@ export function System(_props: RoutableProps) {
- Proxy Reconstruction
+
+ Proxy Reconstruction
+
{proxyLoading && !proxyStats ? (
) : !proxyRows.length ? (
-
+
) : (
)}
@@ -77,7 +97,10 @@ export function System(_props: RoutableProps) {
{interceptLoading && !interceptionStats ? (
) : !interceptRows.length ? (
-
+
) : (
)}
diff --git a/dashboard/src/pages/workers.tsx b/dashboard/src/pages/workers.tsx
index 7ed8fc8..29c749b 100644
--- a/dashboard/src/pages/workers.tsx
+++ b/dashboard/src/pages/workers.tsx
@@ -1,9 +1,9 @@
-import { Server, Clock, Tag } from "lucide-preact";
-import { useApi } from "../hooks/use-api";
-import { Loading } from "../components/ui/loading";
+import { Clock, Server, Tag } from "lucide-preact";
+import type { QueueStats, Worker as WorkerType } from "../api/types";
import { EmptyState } from "../components/ui/empty-state";
+import { Loading } from "../components/ui/loading";
+import { useApi } from "../hooks/use-api";
import { fmtTime } from "../lib/format";
-import type { QueueStats, Worker as WorkerType } from "../api/types";
import type { RoutableProps } from "../lib/routes";
export function Workers(_props: RoutableProps) {
@@ -37,9 +37,7 @@ export function Workers(_props: RoutableProps) {
>
-
- {w.worker_id}
-
+ {w.worker_id}
@@ -48,11 +46,13 @@ export function Workers(_props: RoutableProps) {
- Last heartbeat: {fmtTime(w.last_heartbeat)}
+ Last heartbeat:{" "}
+ {fmtTime(w.last_heartbeat)}
- Registered: {fmtTime(w.registered_at)}
+ Registered:{" "}
+ {fmtTime(w.registered_at)}
{w.tags && (
diff --git a/py_src/taskito/templates/dashboard.html b/py_src/taskito/templates/dashboard.html
index 519809b..cbcb617 100644
--- a/py_src/taskito/templates/dashboard.html
+++ b/py_src/taskito/templates/dashboard.html
@@ -4,12 +4,12 @@
taskito dashboard
-
-
+ */const Br=b("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]);var Jr=Symbol.for("preact-signals");function Ve(){if(z>1)z--;else{var e,t=!1;for((function(){var a=Se;for(Se=void 0;a!==void 0;)a.S.v===a.v&&(a.S.i=a.i),a=a.o})();ne!==void 0;){var n=ne;for(ne=void 0,Ce++;n!==void 0;){var s=n.u;if(n.u=void 0,n.f&=-3,!(8&n.f)&&Jt(n))try{n.c()}catch(a){t||(e=a,t=!0)}n=s}}if(Ce=0,z--,t)throw e}}var y=void 0;function zt(e){var t=y;y=void 0;try{return e()}finally{y=t}}var ne=void 0,z=0,Ce=0,pt=0,Se=void 0,Me=0;function Bt(e){if(y!==void 0){var t=e.n;if(t===void 0||t.t!==y)return t={i:0,S:e,p:y.s,n:void 0,t:y,e:void 0,x:void 0,r:t},y.s!==void 0&&(y.s.n=t),y.s=t,e.n=t,32&y.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=y.s,t.n=void 0,y.s.n=t,y.s=t),t}}function M(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.l=0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}M.prototype.brand=Jr;M.prototype.h=function(){return!0};M.prototype.S=function(e){var t=this,n=this.t;n!==e&&e.e===void 0&&(e.x=n,this.t=e,n!==void 0?n.e=e:zt(function(){var s;(s=t.W)==null||s.call(t)}))};M.prototype.U=function(e){var t=this;if(this.t!==void 0){var n=e.e,s=e.x;n!==void 0&&(n.x=s,e.e=void 0),s!==void 0&&(s.e=n,e.x=void 0),e===this.t&&(this.t=s,s===void 0&&zt(function(){var a;(a=t.Z)==null||a.call(t)}))}};M.prototype.subscribe=function(e){var t=this;return Le(function(){var n=t.value,s=y;y=void 0;try{e(n)}finally{y=s}},{name:"sub"})};M.prototype.valueOf=function(){return this.value};M.prototype.toString=function(){return this.value+""};M.prototype.toJSON=function(){return this.value};M.prototype.peek=function(){var e=y;y=void 0;try{return this.value}finally{y=e}};Object.defineProperty(M.prototype,"value",{get:function(){var e=Bt(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(Ce>100)throw new Error("Cycle detected");(function(n){z!==0&&Ce===0&&n.l!==pt&&(n.l=pt,Se={S:n,v:n.v,i:n.i,o:Se})})(this),this.v=e,this.i++,Me++,z++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{Ve()}}}});function pe(e,t){return new M(e,t)}function Jt(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Qt(e){for(var t=e.s;t!==void 0;t=t.n){var n=t.S.n;if(n!==void 0&&(t.r=n),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Vt(e){for(var t=e.s,n=void 0;t!==void 0;){var s=t.p;t.i===-1?(t.S.U(t),s!==void 0&&(s.n=t.n),t.n!==void 0&&(t.n.p=s)):n=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=s}e.s=n}function J(e,t){M.call(this,void 0),this.x=e,this.s=void 0,this.g=Me-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}J.prototype=new M;J.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Me))return!0;if(this.g=Me,this.f|=1,this.i>0&&!Jt(this))return this.f&=-2,!0;var e=y;try{Qt(this),y=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(n){this.v=n,this.f|=16,this.i++}return y=e,Vt(this),this.f&=-2,!0};J.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}M.prototype.S.call(this,e)};J.prototype.U=function(e){if(this.t!==void 0&&(M.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};J.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(J.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=Bt(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function Qr(e,t){return new J(e,t)}function Gt(e){var t=e.m;if(e.m=void 0,typeof t=="function"){z++;var n=y;y=void 0;try{t()}catch(s){throw e.f&=-2,e.f|=8,Ge(e),s}finally{y=n,Ve()}}}function Ge(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,Gt(e)}function Vr(e){if(y!==this)throw new Error("Out-of-order effect");Vt(this),y=e,this.f&=-2,8&this.f&&Ge(this),Ve()}function X(e,t){this.x=e,this.m=void 0,this.s=void 0,this.u=void 0,this.f=32,this.name=t==null?void 0:t.name}X.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.m=t)}finally{e()}};X.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,Gt(this),Qt(this),z++;var e=y;return y=this,Vr.bind(this,e)};X.prototype.N=function(){2&this.f||(this.f|=2,this.u=ne,ne=this)};X.prototype.d=function(){this.f|=8,1&this.f||Ge(this)};X.prototype.dispose=function(){this.d()};function Le(e,t){var n=new X(e,t);try{n.c()}catch(a){throw n.d(),a}var s=n.d.bind(n);return s[Symbol.dispose]=s,s}var ge;function ee(e,t){k[e]=t.bind(null,k[e]||function(){})}function Te(e){if(ge){var t=ge;ge=void 0,t()}ge=e&&e.S()}function Yt(e){var t=this,n=e.data,s=Yr(n);s.value=n;var a=Pe(function(){for(var i=t.__v;i=i.__;)if(i.__c){i.__c.__$f|=4;break}return t.__$u.c=function(){var c,l=t.__$u.S(),u=a.value;l(),_t(u)||((c=t.base)==null?void 0:c.nodeType)!==3?(t.__$f|=1,t.setState({})):t.base.data=u},Qr(function(){var c=s.value.value;return c===0?0:c===!0?"":c||""})},[]);return a.value}Yt.displayName="_st";Object.defineProperties(M.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:Yt},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});ee("__b",function(e,t){if(typeof t.type=="string"){var n,s=t.props;for(var a in s)if(a!=="children"){var i=s[a];i instanceof M&&(n||(t.__np=n={}),n[a]=i,s[a]=i.peek())}}e(t)});ee("__r",function(e,t){e(t),Te();var n,s=t.__c;s&&(s.__$f&=-2,(n=s.__$u)===void 0&&(s.__$u=n=(function(a){var i;return Le(function(){i=this}),i.c=function(){s.__$f|=1,s.setState({})},i})())),Te(n)});ee("__e",function(e,t,n,s){Te(),e(t,n,s)});ee("diffed",function(e,t){Te();var n;if(typeof t.type=="string"&&(n=t.__e)){var s=t.__np,a=t.props;if(s){var i=n.U;if(i)for(var c in i){var l=i[c];l!==void 0&&!(c in s)&&(l.d(),i[c]=void 0)}else n.U=i={};for(var u in s){var d=i[u],h=s[u];d===void 0?(d=Gr(n,u,h,a),i[u]=d):d.o(h,a)}}}e(t)});function Gr(e,t,n,s){var a=t in e&&e.ownerSVGElement===void 0,i=pe(n);return{o:function(c,l){i.value=c,s=l},d:Le(function(){var c=i.value.value;s[t]!==c&&(s[t]=c,a?e[t]=c:c?e.setAttribute(t,c):e.removeAttribute(t))})}}ee("unmount",function(e,t){if(typeof t.type=="string"){var n=t.__e;if(n){var s=n.U;if(s){n.U=void 0;for(var a in s){var i=s[a];i&&i.d()}}}}else{var c=t.__c;if(c){var l=c.__$u;l&&(c.__$u=void 0,l.d())}}e(t)});ee("__h",function(e,t,n,s){(s<3||s===9)&&(t.__$f|=2),e(t,n,s)});V.prototype.shouldComponentUpdate=function(e,t){if(this.__R)return!0;var n=this.__$u,s=n&&n.s!==void 0;for(var a in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){if(!(s||2&this.__$f||4&this.__$f)||1&this.__$f)return!0}else if(!(s||4&this.__$f)||3&this.__$f)return!0;for(var i in e)if(i!=="__source"&&e[i]!==this.props[i])return!0;for(var c in this.props)if(!(c in e))return!0;return!1};function Yr(e){return Pe(function(){return pe(e)},[])}const le=pe(5e3);function Zr(e){le.value=e}const Kr=localStorage.getItem("taskito-theme"),Z=pe(Kr??"dark");Le(()=>{const e=document.documentElement;Z.value==="dark"?(e.classList.add("dark"),e.classList.remove("light")):(e.classList.remove("dark"),e.classList.add("light")),localStorage.setItem("taskito-theme",Z.value)});function Xr(){Z.value=Z.value==="dark"?"light":"dark"}function en(){return r("header",{class:"h-14 flex items-center justify-between px-6 dark:bg-surface-2/80 bg-white/80 backdrop-blur-md border-b dark:border-white/[0.06] border-slate-200 sticky top-0 z-40",children:[r("a",{href:"/",class:"flex items-center gap-2 no-underline group",children:[r("div",{class:"w-7 h-7 rounded-lg bg-gradient-to-br from-accent to-accent-light flex items-center justify-center shadow-md shadow-accent/20",children:r(Br,{class:"w-4 h-4 text-white",strokeWidth:2.5})}),r("span",{class:"text-[15px] font-semibold dark:text-white text-slate-900 tracking-tight",children:"taskito"}),r("span",{class:"text-xs text-muted font-normal hidden sm:inline",children:"dashboard"})]}),r("div",{class:"flex items-center gap-3",children:[r("div",{class:"flex items-center gap-2 text-xs",children:[r(Ir,{class:"w-3.5 h-3.5 text-muted"}),r("select",{class:"dark:bg-surface-3 bg-slate-100 dark:text-gray-300 text-slate-600 border dark:border-white/[0.06] border-slate-200 rounded-md px-2 py-1 text-xs cursor-pointer hover:dark:border-white/10 hover:border-slate-300 transition-colors",value:le.value,onChange:e=>Zr(Number(e.target.value)),children:[r("option",{value:2e3,children:"2s"}),r("option",{value:5e3,children:"5s"}),r("option",{value:1e4,children:"10s"}),r("option",{value:0,children:"Off"})]})]}),r("div",{class:"w-px h-5 dark:bg-white/[0.06] bg-slate-200"}),r("button",{type:"button",onClick:Xr,class:"p-2 rounded-lg dark:text-gray-400 text-slate-500 hover:dark:text-white hover:text-slate-900 hover:dark:bg-surface-3 hover:bg-slate-100 transition-all duration-150 border-none cursor-pointer bg-transparent",title:`Switch to ${Z.value==="dark"?"light":"dark"} mode`,children:Z.value==="dark"?r(qr,{class:"w-4 h-4"}):r(Er,{class:"w-4 h-4"})})]})]})}const tn=[{title:"Monitoring",items:[{path:"/",label:"Overview",icon:It},{path:"/jobs",label:"Jobs",icon:qt},{path:"/metrics",label:"Metrics",icon:Rt},{path:"/logs",label:"Logs",icon:Dt}]},{title:"Infrastructure",items:[{path:"/workers",label:"Workers",icon:We},{path:"/queues",label:"Queues",icon:Ot},{path:"/resources",label:"Resources",icon:At},{path:"/circuit-breakers",label:"Circuit Breakers",icon:Ht}]},{title:"Advanced",items:[{path:"/dead-letters",label:"Dead Letters",icon:Qe},{path:"/system",label:"System",icon:Ut}]}];function rn(e,t){return t==="/"?e==="/":e===t||e.startsWith(t+"/")}function nn(){const[e,t]=R(ce());return oe(()=>{const n=()=>t(ce());return addEventListener("popstate",n),addEventListener("pushstate",n),()=>{removeEventListener("popstate",n),removeEventListener("pushstate",n)}},[]),r("aside",{class:"w-60 shrink-0 border-r dark:border-white/[0.06] border-slate-200 dark:bg-surface-2/50 bg-slate-50/50 overflow-y-auto h-[calc(100vh-56px)]",children:r("nav",{class:"p-3 space-y-5 pt-4",children:tn.map(n=>r("div",{children:[r("div",{class:"px-3 pb-2 text-[10px] font-bold uppercase tracking-[0.1em] text-muted/60",children:n.title}),r("div",{class:"space-y-0.5",children:n.items.map(s=>{const a=rn(e,s.path),i=s.icon;return r("a",{href:s.path,class:`flex items-center gap-2.5 px-3 py-2 text-[13px] rounded-lg transition-all duration-150 no-underline relative ${a?"dark:bg-accent/10 bg-accent/5 dark:text-white text-slate-900 font-medium":"dark:text-gray-400 text-slate-500 hover:dark:text-gray-200 hover:text-slate-700 hover:dark:bg-white/[0.03] hover:bg-slate-100"}`,children:[a&&r("div",{class:"absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-4 rounded-r-full bg-accent"}),r(i,{class:`w-4 h-4 shrink-0 ${a?"text-accent":""}`,strokeWidth:a?2.2:1.8}),s.label]},s.path)})})]},n.title))})})}function sn({children:e}){return r("div",{class:"min-h-screen",children:[r(en,{}),r("div",{class:"flex",children:[r(nn,{}),r("main",{class:"flex-1 p-8 overflow-auto h-[calc(100vh-56px)]",children:r("div",{class:"max-w-[1280px] mx-auto",children:e})})]})]})}const F=pe([]);let an=0;function j(e,t="info",n=3e3){const s=String(++an);F.value=[...F.value,{id:s,message:e,type:t}],setTimeout(()=>{F.value=F.value.filter(a=>a.id!==s)},n)}function on(e){F.value=F.value.filter(t=>t.id!==e)}const cn={success:{border:"border-l-success",icon:Nt,iconColor:"text-success"},error:{border:"border-l-danger",icon:Et,iconColor:"text-danger"},info:{border:"border-l-info",icon:Nr,iconColor:"text-info"}};function ln(){const e=F.value;return e.length?r("div",{class:"fixed bottom-5 right-5 z-50 flex flex-col gap-2.5 max-w-sm",children:e.map(t=>{const n=cn[t.type],s=n.icon;return r("div",{class:`flex items-start gap-3 border-l-[3px] ${n.border} rounded-lg px-4 py-3.5 text-[13px] dark:bg-surface-2 bg-white shadow-xl dark:shadow-black/40 dark:text-gray-200 text-slate-700 animate-slide-in border dark:border-white/[0.06] border-slate-200`,role:"alert",children:[r(s,{class:`w-4.5 h-4.5 ${n.iconColor} shrink-0 mt-0.5`,strokeWidth:2}),r("span",{class:"flex-1",children:t.message}),r("button",{type:"button",onClick:()=>on(t.id),class:"text-muted hover:dark:text-white hover:text-slate-900 transition-colors border-none bg-transparent cursor-pointer p-0.5",children:r(zr,{class:"w-3.5 h-3.5"})})]},t.id)})}):null}const dn={pending:"bg-warning/10 text-warning border-warning/20",running:"bg-info/10 text-info border-info/20",complete:"bg-success/10 text-success border-success/20",failed:"bg-danger/10 text-danger border-danger/20",dead:"bg-danger/15 text-danger border-danger/25",cancelled:"bg-muted/10 text-muted border-muted/20",closed:"bg-success/10 text-success border-success/20",open:"bg-danger/10 text-danger border-danger/20",half_open:"bg-warning/10 text-warning border-warning/20",healthy:"bg-success/10 text-success border-success/20",unhealthy:"bg-danger/10 text-danger border-danger/20",degraded:"bg-warning/10 text-warning border-warning/20",active:"bg-success/10 text-success border-success/20",paused:"bg-warning/10 text-warning border-warning/20"},un={pending:"bg-warning",running:"bg-info",complete:"bg-success",failed:"bg-danger",dead:"bg-danger",cancelled:"bg-muted",closed:"bg-success",open:"bg-danger",half_open:"bg-warning",healthy:"bg-success",unhealthy:"bg-danger",degraded:"bg-warning",active:"bg-success",paused:"bg-warning"};function O({status:e}){const t=dn[e]??"bg-muted/10 text-muted border-muted/20",n=un[e]??"bg-muted";return r("span",{class:`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-semibold uppercase tracking-wide border ${t}`,children:[r("span",{class:`w-1.5 h-1.5 rounded-full ${n}`}),e]})}function L({columns:e,data:t,onRowClick:n,children:s}){return r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 overflow-hidden border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"overflow-x-auto",children:r("table",{class:"w-full border-collapse text-[13px]",children:[r("thead",{children:r("tr",{children:e.map((a,i)=>r("th",{class:`text-left px-4 py-3 dark:bg-surface-3/50 bg-slate-50 text-muted font-semibold text-[11px] uppercase tracking-[0.05em] whitespace-nowrap border-b dark:border-white/[0.04] border-slate-100 ${a.className??""}`,children:a.header},i))})}),r("tbody",{children:t.map((a,i)=>r("tr",{onClick:n?()=>n(a):void 0,class:`border-b dark:border-white/[0.03] border-slate-50 last:border-0 transition-colors duration-100 ${i%2===1?"dark:bg-white/[0.01] bg-slate-50/30":""} ${n?"cursor-pointer hover:dark:bg-accent/[0.04] hover:bg-accent/[0.02]":""}`,children:e.map((c,l)=>r("td",{class:`px-4 py-3 whitespace-nowrap ${c.className??""}`,children:typeof c.accessor=="function"?c.accessor(a):a[c.accessor]},l))},i))})]})}),s]})}function N({message:e,subtitle:t}){return r("div",{class:"flex flex-col items-center justify-center py-16 text-center",children:[r("div",{class:"w-12 h-12 rounded-xl dark:bg-surface-3 bg-slate-100 flex items-center justify-center mb-4",children:r(Rr,{class:"w-6 h-6 text-muted",strokeWidth:1.5})}),r("p",{class:"text-sm font-medium dark:text-gray-300 text-slate-600",children:e}),t&&r("p",{class:"text-xs text-muted mt-1",children:t})]})}function A(){return r("div",{class:"flex items-center justify-center py-20",children:r("div",{class:"flex items-center gap-3 text-muted text-sm",children:[r("div",{class:"w-5 h-5 border-2 border-accent/30 border-t-accent rounded-full animate-spin"}),r("span",{children:"Loading\\u2026"})]})})}class Zt extends Error{constructor(t,n){super(n),this.status=t}}async function hn(e,t){const n=await fetch(e,{signal:t});if(!n.ok)throw new Zt(n.status,`${n.status} ${n.statusText}`);return n.json()}async function K(e,t){const n=await fetch(e,{method:"POST",signal:t});if(!n.ok)throw new Zt(n.status,`${n.status} ${n.statusText}`);return n.json()}function $(e,t=[]){const[n,s]=R(null),[a,i]=R(!0),[c,l]=R(null),u=Y(null),d=Y(!0),h=ur(()=>{var f;if(!e){s(null),i(!1);return}(f=u.current)==null||f.abort();const o=new AbortController;u.current=o,i(_=>n===null?!0:_),hn(e,o.signal).then(_=>{d.current&&!o.signal.aborted&&(s(_),l(null),i(!1))}).catch(_=>{d.current&&!o.signal.aborted&&(l(_.message??"Failed to fetch"),i(!1))})},[e,...t]);return oe(()=>(d.current=!0,h(),()=>{var o;d.current=!1,(o=u.current)==null||o.abort()}),[h]),oe(()=>{const o=le.value;if(o<=0||!e)return;const f=setInterval(h,o);return()=>clearInterval(f)},[h,e,le.value]),{data:n,loading:a,error:c,refetch:h}}function T(e){if(!e)return"—";const t=new Date(e);return t.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit"})+" "+t.toLocaleDateString(void 0,{month:"short",day:"numeric"})}function pn(e){return e.toLocaleString()}function B(e,t=8){return e.length>t?e.slice(0,t):e}const fn=[{header:"Task",accessor:e=>r("span",{class:"font-medium",children:e.task_name})},{header:"State",accessor:e=>r(O,{status:e.state})},{header:"Failures",accessor:e=>r("span",{class:e.failure_count>0?"text-danger tabular-nums":"tabular-nums",children:e.failure_count})},{header:"Threshold",accessor:e=>r("span",{class:"tabular-nums",children:e.threshold})},{header:"Window",accessor:e=>`${(e.window_ms/1e3).toFixed(0)}s`},{header:"Cooldown",accessor:e=>`${(e.cooldown_ms/1e3).toFixed(0)}s`},{header:"Last Failure",accessor:e=>r("span",{class:"text-muted",children:T(e.last_failure_at)})}];function _n(e){const{data:t,loading:n}=$("/api/circuit-breakers");return n&&!t?r(A,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ht,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Circuit Breakers"}),r("p",{class:"text-xs text-muted",children:"Automatic failure protection status"})]})]}),t!=null&&t.length?r(L,{columns:fn,data:t}):r(N,{message:"No circuit breakers configured",subtitle:"Circuit breakers activate when tasks fail repeatedly"})]})}const mn={primary:"bg-accent text-white shadow-sm shadow-accent/20 hover:bg-accent/90 hover:shadow-md hover:shadow-accent/25 active:scale-[0.98]",danger:"bg-danger text-white shadow-sm shadow-danger/20 hover:bg-danger/90 hover:shadow-md hover:shadow-danger/25 active:scale-[0.98]",ghost:"dark:text-gray-400 text-slate-500 hover:dark:bg-surface-3 hover:bg-slate-100 hover:dark:text-gray-200 hover:text-slate-700 active:scale-[0.98]"};function W({onClick:e,variant:t="primary",disabled:n,children:s,class:a=""}){return r("button",{type:"button",onClick:e,disabled:n,class:`inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-[13px] font-medium cursor-pointer transition-all duration-150 disabled:opacity-40 disabled:cursor-default disabled:shadow-none border-none ${mn[t]} ${a}`,children:s})}function gn({message:e,onConfirm:t,onCancel:n}){return r("div",{class:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in",onClick:n,children:r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-2xl dark:shadow-black/50 p-6 max-w-sm mx-4 border dark:border-white/[0.08] border-slate-200 animate-slide-in",onClick:s=>s.stopPropagation(),children:[r("div",{class:"flex items-start gap-3 mb-5",children:[r("div",{class:"p-2 rounded-lg bg-danger-dim shrink-0",children:r(Hr,{class:"w-5 h-5 text-danger",strokeWidth:2})}),r("p",{class:"text-sm dark:text-gray-200 text-slate-700 pt-1",children:e})]}),r("div",{class:"flex justify-end gap-2.5",children:[r(W,{variant:"ghost",onClick:n,children:"Cancel"}),r(W,{variant:"danger",onClick:t,children:"Confirm"})]})]})})}function Kt({page:e,pageSize:t,itemCount:n,onPageChange:s}){return r("div",{class:"flex items-center justify-between px-4 py-3 text-[13px] text-muted border-t dark:border-white/[0.04] border-slate-100",children:[r("span",{children:["Showing ",e*t+1,"\\u2013",e*t+n," items"]}),r("div",{class:"flex gap-1.5",children:[r("button",{type:"button",onClick:()=>s(e-1),disabled:e===0,class:"inline-flex items-center gap-1 px-3 py-1.5 rounded-lg dark:bg-surface-3 bg-slate-100 dark:text-gray-300 text-slate-600 border dark:border-white/[0.06] border-slate-200 disabled:opacity-30 cursor-pointer disabled:cursor-default hover:enabled:dark:bg-surface-4 hover:enabled:bg-slate-200 transition-all duration-150 text-[13px]",children:[r(jr,{class:"w-3.5 h-3.5"}),"Prev"]}),r("button",{type:"button",onClick:()=>s(e+1),disabled:n {try{await K(`/api/dead-letters/${o}/retry`),j("Dead letter retried","success"),l()}catch{j("Failed to retry dead letter","error")}},d=async()=>{a(!1);try{const o=await K("/api/dead-letters/purge");j(`Purged ${o.purged} dead letters`,"success"),l()}catch{j("Failed to purge dead letters","error")}},h=[{header:"ID",accessor:o=>r("span",{class:"font-mono text-xs text-accent-light",children:B(o.id)})},{header:"Original Job",accessor:o=>r("a",{href:`/jobs/${o.original_job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:B(o.original_job_id)})},{header:"Task",accessor:o=>r("span",{class:"font-medium",children:o.task_name})},{header:"Queue",accessor:"queue"},{header:"Error",accessor:o=>r("span",{class:"text-danger text-xs",title:o.error??"",children:o.error?o.error.length>50?o.error.slice(0,50)+"…":o.error:"—"}),className:"max-w-[250px]"},{header:"Retries",accessor:o=>r("span",{class:"text-warning tabular-nums",children:o.retry_count})},{header:"Failed At",accessor:o=>r("span",{class:"text-muted",children:T(o.failed_at)})},{header:"Actions",accessor:o=>r(W,{onClick:()=>u(o.id),children:[r(Ft,{class:"w-3.5 h-3.5"}),"Retry"]})}];return c&&!i?r(A,{}):r("div",{children:[r("div",{class:"flex items-center justify-between mb-6",children:[r("div",{class:"flex items-center gap-3",children:[r("div",{class:"p-2 rounded-lg bg-danger-dim",children:r(Qe,{class:"w-5 h-5 text-danger",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Dead Letters"}),r("p",{class:"text-xs text-muted",children:"Failed jobs that exhausted all retries"})]})]}),i&&i.length>0&&r(W,{variant:"danger",onClick:()=>a(!0),children:[r(Fr,{class:"w-3.5 h-3.5"}),"Purge All"]})]}),i!=null&&i.length?r(L,{columns:h,data:i,children:r(Kt,{page:t,pageSize:Re,itemCount:i.length,onPageChange:n})}):r(N,{message:"No dead letters",subtitle:"All jobs are processing normally"}),s&&r(gn,{message:"Purge all dead letters? This cannot be undone.",onConfirm:d,onCancel:()=>a(!1)})]})}const xn={pending:"#ffa726",running:"#42a5f5",complete:"#66bb6a",failed:"#ef5350",dead:"#ef5350",cancelled:"#a0a0b0"};function bn({dag:e}){if(!e.nodes||e.nodes.length<=1)return null;const t=160,n=36,s=40,a=20,i={},c={};e.nodes.forEach(p=>{i[p.id]=[],c[p.id]=0}),e.edges.forEach(p=>{i[p.from]||(i[p.from]=[]),i[p.from].push(p.to),c[p.to]=(c[p.to]||0)+1});const l=[],u=new Set;let d=e.nodes.filter(p=>(c[p.id]||0)===0).map(p=>p.id);for(;d.length;){l.push([...d]);for(const m of d)u.add(m);const p=[];d.forEach(m=>{(i[m]||[]).forEach(g=>{!u.has(g)&&!p.includes(g)&&p.push(g)})}),d=p}e.nodes.forEach(p=>{u.has(p.id)||(l.push([p.id]),u.add(p.id))});const h={};for(const p of e.nodes)h[p.id]=p;const o={};let f=0,_=0;return l.forEach((p,m)=>{p.forEach((g,v)=>{const x=20+m*(t+s),S=20+v*(n+a);o[g]={x,y:S},f=Math.max(f,x+t+20),_=Math.max(_,S+n+20)})}),r("div",{class:"mt-4",children:[r("h3",{class:"text-sm text-muted mb-2",children:"Dependency Graph"}),r("div",{class:"dark:bg-surface-2 bg-white rounded-lg shadow-sm dark:shadow-black/30 p-4 overflow-x-auto border border-transparent dark:border-white/5",children:r("svg",{width:f,height:_,role:"img","aria-label":"Job dependency graph",children:[r("title",{children:"Job dependency graph"}),r("defs",{children:r("marker",{id:"arrow",viewBox:"0 0 10 10",refX:"10",refY:"5",markerWidth:"8",markerHeight:"8",orient:"auto",children:r("path",{d:"M0,0 L10,5 L0,10 z",fill:"#a0a0b0"})})}),e.edges.map((p,m)=>{const g=o[p.from],v=o[p.to];return!g||!v?null:r("line",{x1:g.x+t,y1:g.y+n/2,x2:v.x,y2:v.y+n/2,stroke:"#a0a0b0","stroke-width":"1.5",fill:"none","marker-end":"url(#arrow)"},m)}),e.nodes.map(p=>{const m=o[p.id];if(!m)return null;const g=xn[p.status]||"#a0a0b0";return r("g",{class:"cursor-pointer",onClick:()=>he(`/jobs/${p.id}`),children:[r("rect",{x:m.x,y:m.y,width:t,height:n,fill:`${g}22`,stroke:g,"stroke-width":"1.5",rx:"6",ry:"6"}),r("text",{x:m.x+8,y:m.y+14,fill:g,"font-size":"10","font-weight":"600",children:p.status.toUpperCase()}),r("text",{x:m.x+8,y:m.y+28,"font-size":"10",fill:"#a0a0b0",children:p.task_name.length>18?p.task_name.slice(-18):p.task_name})]},p.id)})]})})]})}function Ye({progress:e}){return e==null?r("span",{class:"text-muted text-xs",children:"—"}):r("span",{class:"inline-flex items-center gap-2",children:[r("span",{class:"inline-block w-16 h-1.5 rounded-full dark:bg-white/[0.08] bg-slate-200 overflow-hidden",children:r("span",{class:"block h-full rounded-full bg-gradient-to-r from-accent to-accent-light transition-[width] duration-300",style:{width:`${e}%`}})}),r("span",{class:"text-xs tabular-nums text-muted",children:[e,"%"]})]})}const yn=[{header:"Attempt",accessor:"attempt"},{header:"Error",accessor:"error",className:"max-w-xs truncate"},{header:"Failed At",accessor:e=>r("span",{class:"text-muted",children:T(e.failed_at)})}],kn=[{header:"Time",accessor:e=>r("span",{class:"text-muted",children:T(e.logged_at)})},{header:"Level",accessor:e=>r(O,{status:e.level==="error"?"failed":e.level==="warning"?"pending":"complete"})},{header:"Message",accessor:"message"},{header:"Extra",accessor:e=>e.extra??"—",className:"max-w-[200px] truncate"}],wn=[{header:"Replay Job",accessor:e=>r("a",{href:`/jobs/${e.replay_job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:B(e.replay_job_id)})},{header:"Replayed At",accessor:e=>r("span",{class:"text-muted",children:T(e.replayed_at)})},{header:"Original Error",accessor:e=>e.original_error??"—",className:"max-w-[200px] truncate"},{header:"Replay Error",accessor:e=>e.replay_error??"—",className:"max-w-[200px] truncate"}];function $n({id:e}){const{data:t,loading:n,refetch:s}=$(`/api/jobs/${e}`),{data:a}=$(`/api/jobs/${e}/errors`),{data:i}=$(`/api/jobs/${e}/logs`),{data:c}=$(`/api/jobs/${e}/replay-history`),{data:l}=$(`/api/jobs/${e}/dag`);if(n&&!t)return r(A,{});if(!t)return r(N,{message:`Job not found: ${e}`});const u=async()=>{try{const o=await K(`/api/jobs/${e}/cancel`);j(o.cancelled?"Job cancelled":"Failed to cancel job",o.cancelled?"success":"error"),s()}catch{j("Failed to cancel job","error")}},d=async()=>{try{const o=await K(`/api/jobs/${e}/replay`);j("Job replayed","success"),he(`/jobs/${o.replay_job_id}`)}catch{j("Failed to replay job","error")}},h={pending:"border-t-warning",running:"border-t-info",complete:"border-t-success",failed:"border-t-danger",dead:"border-t-danger",cancelled:"border-t-muted"};return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ar,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:["Job ",r("span",{class:"font-mono text-accent-light",children:B(t.id)})]}),r("p",{class:"text-xs text-muted",children:t.task_name})]})]}),r("div",{class:`dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-6 border dark:border-white/[0.06] border-slate-200 border-t-[3px] ${h[t.status]??"border-t-muted"}`,children:[r("div",{class:"grid grid-cols-[140px_1fr] gap-x-6 gap-y-3 text-[13px]",children:[r("span",{class:"text-muted font-medium",children:"ID"}),r("span",{class:"font-mono text-xs break-all dark:text-gray-300 text-slate-600",children:t.id}),r("span",{class:"text-muted font-medium",children:"Status"}),r("span",{children:r(O,{status:t.status})}),r("span",{class:"text-muted font-medium",children:"Task"}),r("span",{class:"font-medium",children:t.task_name}),r("span",{class:"text-muted font-medium",children:"Queue"}),r("span",{children:t.queue}),r("span",{class:"text-muted font-medium",children:"Priority"}),r("span",{children:t.priority}),r("span",{class:"text-muted font-medium",children:"Progress"}),r("span",{children:r(Ye,{progress:t.progress})}),r("span",{class:"text-muted font-medium",children:"Retries"}),r("span",{class:t.retry_count>0?"text-warning":"",children:[t.retry_count," / ",t.max_retries]}),r("span",{class:"text-muted font-medium",children:"Created"}),r("span",{class:"text-muted",children:T(t.created_at)}),r("span",{class:"text-muted font-medium",children:"Scheduled"}),r("span",{class:"text-muted",children:T(t.scheduled_at)}),r("span",{class:"text-muted font-medium",children:"Started"}),r("span",{class:"text-muted",children:t.started_at?T(t.started_at):"—"}),r("span",{class:"text-muted font-medium",children:"Completed"}),r("span",{class:"text-muted",children:t.completed_at?T(t.completed_at):"—"}),r("span",{class:"text-muted font-medium",children:"Timeout"}),r("span",{children:[(t.timeout_ms/1e3).toFixed(0),"s"]}),t.error&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Error"}),r("span",{class:"text-danger text-xs font-mono bg-danger-dim rounded px-2 py-1",children:t.error})]}),t.unique_key&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Unique Key"}),r("span",{class:"font-mono text-xs",children:t.unique_key})]}),t.metadata&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Metadata"}),r("span",{class:"font-mono text-xs",children:t.metadata})]})]}),r("div",{class:"flex gap-2.5 mt-5 pt-5 border-t dark:border-white/[0.06] border-slate-100",children:[t.status==="pending"&&r(W,{variant:"danger",onClick:u,children:"Cancel Job"}),r(W,{onClick:d,children:[r(Ft,{class:"w-3.5 h-3.5"}),"Replay"]})]})]}),a&&a.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Error History ",r("span",{class:"text-muted font-normal",children:["(",a.length,")"]})]}),r(L,{columns:yn,data:a})]}),i&&i.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Task Logs ",r("span",{class:"text-muted font-normal",children:["(",i.length,")"]})]}),r(L,{columns:kn,data:i})]}),c&&c.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Replay History ",r("span",{class:"text-muted font-normal",children:["(",c.length,")"]})]}),r(L,{columns:wn,data:c})]}),l&&r(bn,{dag:l}),r("div",{class:"mt-6",children:r("a",{href:"/jobs",class:"text-accent-light text-[13px] hover:underline",children:["←"," Back to jobs"]})})]})}const Cn={pending:{color:"text-warning",bg:"bg-warning-dim",border:"border-l-warning",icon:$e},running:{color:"text-info",bg:"bg-info-dim",border:"border-l-info",icon:Wt},completed:{color:"text-success",bg:"bg-success-dim",border:"border-l-success",icon:Nt},failed:{color:"text-danger",bg:"bg-danger-dim",border:"border-l-danger",icon:Et},dead:{color:"text-danger",bg:"bg-danger-dim",border:"border-l-danger",icon:Qe},cancelled:{color:"text-muted",bg:"bg-muted/10",border:"border-l-muted/40",icon:Pr}};function Sn({label:e,value:t,color:n}){const s=Cn[e],a=n??(s==null?void 0:s.color)??"text-accent-light",i=(s==null?void 0:s.bg)??"bg-accent-dim",c=(s==null?void 0:s.border)??"border-l-accent",l=(s==null?void 0:s.icon)??$e;return r("div",{class:`dark:bg-surface-2 bg-white rounded-xl p-5 shadow-sm dark:shadow-black/20 border-l-[3px] ${c} dark:border-t dark:border-r dark:border-b dark:border-white/[0.04] border border-slate-100 transition-all duration-150 hover:shadow-md hover:dark:shadow-black/30`,children:r("div",{class:"flex items-start justify-between",children:[r("div",{children:[r("div",{class:`text-3xl font-bold tabular-nums tracking-tight ${a}`,children:pn(t)}),r("div",{class:"text-xs text-muted uppercase mt-1.5 tracking-wider font-medium",children:e})]}),r("div",{class:`p-2 rounded-lg ${i}`,children:r(l,{class:`w-5 h-5 ${a}`,strokeWidth:1.8})})]})})}const Mn=["pending","running","completed","failed","dead","cancelled"];function Xt({stats:e}){return r("div",{class:"grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-4 mb-8",children:Mn.map(t=>r(Sn,{label:t,value:e[t]??0},t))})}const Fe=20,Tn=[{header:"ID",accessor:e=>r("span",{class:"font-mono text-xs text-accent-light",children:B(e.id)})},{header:"Task",accessor:"task_name"},{header:"Queue",accessor:"queue"},{header:"Status",accessor:e=>r(O,{status:e.status})},{header:"Priority",accessor:"priority"},{header:"Progress",accessor:e=>r(Ye,{progress:e.progress})},{header:"Retries",accessor:e=>r("span",{class:e.retry_count>0?"text-warning":"text-muted",children:[e.retry_count,"/",e.max_retries]})},{header:"Created",accessor:e=>r("span",{class:"text-muted",children:T(e.created_at)})}];function Pn(e,t){const n=new URLSearchParams;return n.set("limit",String(Fe)),n.set("offset",String(t*Fe)),e.status&&n.set("status",e.status),e.queue&&n.set("queue",e.queue),e.task&&n.set("task",e.task),e.metadata&&n.set("metadata",e.metadata),e.error&&n.set("error",e.error),e.created_after&&n.set("created_after",String(new Date(e.created_after).getTime())),e.created_before&&n.set("created_before",String(new Date(e.created_before).getTime())),`/api/jobs?${n}`}function Ln(e){const[t,n]=R({status:"",queue:"",task:"",metadata:"",error:"",created_after:"",created_before:""}),[s,a]=R(0),{data:i}=$("/api/stats"),{data:c,loading:l}=$(Pn(t,s),[t.status,t.queue,t.task,t.metadata,t.error,t.created_after,t.created_before,s]),u=(h,o)=>{n(f=>({...f,[h]:o})),a(0)},d="dark:bg-surface-3 bg-white dark:text-gray-200 text-slate-700 border dark:border-white/[0.06] border-slate-200 rounded-lg px-3 py-2 text-[13px] placeholder:text-muted/50 focus:border-accent/50 transition-colors";return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(qt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Jobs"}),r("p",{class:"text-xs text-muted",children:"Browse and filter task queue jobs"})]})]}),i&&r(Xt,{stats:i}),r("div",{class:"dark:bg-surface-2 bg-white rounded-xl p-4 mb-4 border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"flex items-center gap-2 mb-3 text-xs text-muted font-medium uppercase tracking-wider",children:[r(Or,{class:"w-3.5 h-3.5"}),"Filters"]}),r("div",{class:"grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-2.5",children:[r("select",{class:d,value:t.status,onChange:h=>u("status",h.target.value),children:[r("option",{value:"",children:"All statuses"}),r("option",{value:"pending",children:"Pending"}),r("option",{value:"running",children:"Running"}),r("option",{value:"complete",children:"Complete"}),r("option",{value:"failed",children:"Failed"}),r("option",{value:"dead",children:"Dead"}),r("option",{value:"cancelled",children:"Cancelled"})]}),r("input",{class:d,placeholder:"Queue\\u2026",value:t.queue,onInput:h=>u("queue",h.target.value)}),r("input",{class:d,placeholder:"Task\\u2026",value:t.task,onInput:h=>u("task",h.target.value)}),r("input",{class:d,placeholder:"Metadata\\u2026",value:t.metadata,onInput:h=>u("metadata",h.target.value)}),r("input",{class:d,placeholder:"Error text\\u2026",value:t.error,onInput:h=>u("error",h.target.value)}),r("input",{class:d,type:"date",title:"Created after",value:t.created_after,onInput:h=>u("created_after",h.target.value)}),r("input",{class:d,type:"date",title:"Created before",value:t.created_before,onInput:h=>u("created_before",h.target.value)})]})]}),l&&!c?r(A,{}):c!=null&&c.length?r(L,{columns:Tn,data:c,onRowClick:h=>he(`/jobs/${h.id}`),children:r(Kt,{page:s,pageSize:Fe,itemCount:c.length,onPageChange:a})}):r(N,{message:"No jobs found",subtitle:"Try adjusting your filters"})]})}const jn=[{header:"Time",accessor:e=>r("span",{class:"text-muted",children:T(e.logged_at)})},{header:"Level",accessor:e=>r(O,{status:e.level==="error"?"failed":e.level==="warning"?"pending":"complete"})},{header:"Task",accessor:e=>r("span",{class:"font-medium",children:e.task_name})},{header:"Job",accessor:e=>r("a",{href:`/jobs/${e.job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:B(e.job_id)})},{header:"Message",accessor:"message"},{header:"Extra",accessor:e=>e.extra??"—",className:"max-w-[200px] truncate"}];function An(e){const[t,n]=R(""),[s,a]=R(""),i=new URLSearchParams({limit:"100"});t&&i.set("task",t),s&&i.set("level",s);const{data:c,loading:l}=$(`/api/logs?${i}`,[t,s]),u="dark:bg-surface-3 bg-white dark:text-gray-200 text-slate-700 border dark:border-white/[0.06] border-slate-200 rounded-lg px-3 py-2 text-[13px] placeholder:text-muted/50 focus:border-accent/50 transition-colors";return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Dt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Logs"}),r("p",{class:"text-xs text-muted",children:"Structured task execution logs"})]})]}),r("div",{class:"flex gap-2.5 mb-5",children:[r("input",{class:u+" w-44",placeholder:"Filter by task\\u2026",value:t,onInput:d=>n(d.target.value)}),r("select",{class:u,value:s,onChange:d=>a(d.target.value),children:[r("option",{value:"",children:"All levels"}),r("option",{value:"error",children:"Error"}),r("option",{value:"warning",children:"Warning"}),r("option",{value:"info",children:"Info"}),r("option",{value:"debug",children:"Debug"})]})]}),l&&!c?r(A,{}):c!=null&&c.length?r(L,{columns:jn,data:c}):r(N,{message:"No logs yet",subtitle:"Logs appear when tasks execute"})]})}function Rn({data:e}){const t=Y(null);return oe(()=>{const n=t.current;if(!n)return;const s=n.getContext("2d");if(!s)return;const a=window.devicePixelRatio||1,i=n.getBoundingClientRect();n.width=i.width*a,n.height=i.height*a,s.scale(a,a);const c=i.width,l=i.height;if(s.clearRect(0,0,c,l),!e.length){s.fillStyle="rgba(139,149,165,0.4)",s.font="12px -apple-system, sans-serif",s.textAlign="center",s.fillText("No timeseries data",c/2,l/2);return}const u={top:12,right:12,bottom:32,left:48},d=c-u.left-u.right,h=l-u.top-u.bottom,o=Math.max(...e.map(m=>m.success+m.failure),1),f=Math.max(3,d/e.length-2),_=Math.max(1,(d-f*e.length)/e.length);for(let m=0;m<=4;m++){const g=u.top+h*(1-m/4);s.strokeStyle="rgba(255,255,255,0.04)",s.lineWidth=1,s.beginPath(),s.moveTo(u.left,g),s.lineTo(c-u.right,g),s.stroke(),s.fillStyle="rgba(139,149,165,0.5)",s.font="10px -apple-system, sans-serif",s.textAlign="right",s.fillText(Math.round(o*m/4).toString(),u.left-6,g+3)}e.forEach((m,g)=>{const v=u.left+g*(f+_),x=m.success/o*h,S=m.failure/o*h;s.fillStyle="rgba(34,197,94,0.65)",s.beginPath();const I=u.top+h-x-S;s.roundRect(v,I,f,x,[2,2,0,0]),s.fill(),S>0&&(s.fillStyle="rgba(239,68,68,0.65)",s.beginPath(),s.roundRect(v,u.top+h-S,f,S,[0,0,2,2]),s.fill())}),s.fillStyle="rgba(139,149,165,0.5)",s.font="10px -apple-system, sans-serif",s.textAlign="center";const p=Math.min(6,e.length);for(let m=0;mr("span",{class:"font-medium",children:e.task_name})},{header:"Total",accessor:e=>r("span",{class:"tabular-nums",children:e.count})},{header:"Success",accessor:e=>r("span",{class:"text-success tabular-nums",children:e.success_count})},{header:"Failures",accessor:e=>r("span",{class:e.failure_count>0?"text-danger tabular-nums":"text-muted tabular-nums",children:e.failure_count})},{header:"Avg",accessor:e=>r("span",{class:`tabular-nums ${ve(e.avg_ms,{good:100,warn:500})}`,children:[e.avg_ms,"ms"]})},{header:"P50",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.p50_ms,"ms"]})},{header:"P95",accessor:e=>r("span",{class:`tabular-nums ${ve(e.p95_ms,{good:200,warn:1e3})}`,children:[e.p95_ms,"ms"]})},{header:"P99",accessor:e=>r("span",{class:`tabular-nums ${ve(e.p99_ms,{good:500,warn:2e3})}`,children:[e.p99_ms,"ms"]})},{header:"Min",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.min_ms,"ms"]})},{header:"Max",accessor:e=>r("span",{class:`tabular-nums ${ve(e.max_ms,{good:1e3,warn:5e3})}`,children:[e.max_ms,"ms"]})}],En=[{label:"1h",seconds:3600},{label:"6h",seconds:21600},{label:"24h",seconds:86400}];function Un(e){const[t,n]=R(3600),{data:s,loading:a}=$(`/api/metrics?since=${t}`,[t]),{data:i}=$(`/api/metrics/timeseries?since=${t}&bucket=${t<=3600?60:t<=21600?300:900}`,[t]),c=s?Object.entries(s).map(([l,u])=>({task_name:l,...u})):[];return r("div",{children:[r("div",{class:"flex items-center justify-between mb-6",children:[r("div",{class:"flex items-center gap-3",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Rt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Metrics"}),r("p",{class:"text-xs text-muted",children:"Task performance and throughput"})]})]}),r("div",{class:"flex gap-1 dark:bg-surface-3 bg-slate-100 rounded-lg p-1",children:En.map(l=>r("button",{type:"button",onClick:()=>n(l.seconds),class:`px-3 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-all duration-150 ${t===l.seconds?"bg-accent text-white shadow-sm shadow-accent/20":"bg-transparent dark:text-gray-400 text-slate-500 hover:dark:text-white hover:text-slate-900"}`,children:l.label},l.label))})]}),i&&i.length>0&&r(Rn,{data:i}),a&&!s?r(A,{}):c.length?r(L,{columns:Nn,data:c}):r(N,{message:"No metrics yet",subtitle:"Run some tasks to see performance data"})]})}function In({data:e}){const t=Y(null);oe(()=>{const s=t.current;if(!s)return;const a=s.getContext("2d");if(!a)return;const i=window.devicePixelRatio||1,c=s.getBoundingClientRect();s.width=c.width*i,s.height=c.height*i,a.scale(i,i);const l=c.width,u=c.height;if(a.clearRect(0,0,l,u),e.length<2){a.fillStyle="rgba(139,149,165,0.4)",a.font="12px -apple-system, sans-serif",a.textAlign="center",a.fillText("Collecting data…",l/2,u/2);return}const d=Math.max(...e,1),h={top:12,right:12,bottom:24,left:44},o=l-h.left-h.right,f=u-h.top-h.bottom;for(let p=0;p<=4;p++){const m=h.top+f*(1-p/4);a.strokeStyle="rgba(255,255,255,0.04)",a.lineWidth=1,a.beginPath(),a.moveTo(h.left,m),a.lineTo(l-h.right,m),a.stroke(),a.fillStyle="rgba(139,149,165,0.5)",a.font="10px -apple-system, sans-serif",a.textAlign="right",a.fillText((d*p/4).toFixed(1),h.left-6,m+3)}const _=a.createLinearGradient(0,h.top,0,h.top+f);if(_.addColorStop(0,"rgba(34,197,94,0.2)"),_.addColorStop(1,"rgba(34,197,94,0.01)"),a.beginPath(),a.moveTo(h.left,h.top+f),e.forEach((p,m)=>{const g=h.left+m/(e.length-1)*o,v=h.top+f*(1-p/d);a.lineTo(g,v)}),a.lineTo(h.left+o,h.top+f),a.closePath(),a.fillStyle=_,a.fill(),a.beginPath(),e.forEach((p,m)=>{const g=h.left+m/(e.length-1)*o,v=h.top+f*(1-p/d);m===0?a.moveTo(g,v):a.lineTo(g,v)}),a.strokeStyle="#22c55e",a.lineWidth=2,a.lineJoin="round",a.stroke(),e.length>0){const p=h.left+o,m=h.top+f*(1-e[e.length-1]/d);a.beginPath(),a.arc(p,m,3,0,Math.PI*2),a.fillStyle="#22c55e",a.fill(),a.beginPath(),a.arc(p,m,5,0,Math.PI*2),a.strokeStyle="rgba(34,197,94,0.3)",a.lineWidth=2,a.stroke()}},[e]);const n=e.length>0?e[e.length-1]:0;return r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-5 mb-6 border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"flex items-center justify-between mb-4",children:[r("div",{class:"flex items-center gap-2",children:[r(Dr,{class:"w-4 h-4 text-success",strokeWidth:2}),r("h3",{class:"text-sm font-medium dark:text-gray-300 text-slate-600",children:"Throughput"})]}),r("span",{class:"text-xl font-bold tabular-nums text-success",children:[n.toFixed(1)," ",r("span",{class:"text-xs font-normal text-muted",children:"jobs/s"})]})]}),r("canvas",{ref:t,class:"w-full",style:{height:"180px"}})]})}const On=[{header:"ID",accessor:e=>r("span",{class:"font-mono text-xs text-accent-light",children:B(e.id)})},{header:"Task",accessor:"task_name"},{header:"Queue",accessor:"queue"},{header:"Status",accessor:e=>r(O,{status:e.status})},{header:"Progress",accessor:e=>r(Ye,{progress:e.progress})},{header:"Created",accessor:e=>r("span",{class:"text-muted",children:T(e.created_at)})}];function qn(e){const{data:t,loading:n}=$("/api/stats"),{data:s}=$("/api/jobs?limit=10"),a=Y(0),i=Y([]);if(t){const c=t.completed||0,l=le.value||5e3;let u=0;a.current>0&&(u=parseFloat(((c-a.current)/(l/1e3)).toFixed(1))),a.current=c,i.current=[...i.current.slice(-59),u]}return n&&!t?r(A,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(It,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Overview"}),r("p",{class:"text-xs text-muted",children:"Real-time queue status"})]})]}),t&&r(Xt,{stats:t}),r(In,{data:i.current}),r("div",{class:"flex items-center gap-2 mb-4 mt-8",children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700",children:"Recent Jobs"}),r("span",{class:"text-xs text-muted",children:"(latest 10)"})]}),s!=null&&s.length?r(L,{columns:On,data:s,onRowClick:c=>he(`/jobs/${c.id}`)}):null]})}function Wn(e){const{data:t,loading:n,refetch:s}=$("/api/stats/queues"),{data:a,refetch:i}=$("/api/queues/paused"),c=new Set(a??[]),l=t?Object.entries(t).map(([o,f])=>({name:o,pending:f.pending??0,running:f.running??0,paused:c.has(o)})):[],u=async o=>{try{await K(`/api/queues/${encodeURIComponent(o)}/pause`),j(`Queue "${o}" paused`,"success"),s(),i()}catch{j(`Failed to pause queue "${o}"`,"error")}},d=async o=>{try{await K(`/api/queues/${encodeURIComponent(o)}/resume`),j(`Queue "${o}" resumed`,"success"),s(),i()}catch{j(`Failed to resume queue "${o}"`,"error")}},h=[{header:"Queue",accessor:o=>r("span",{class:"font-medium",children:o.name})},{header:"Pending",accessor:o=>r("span",{class:"text-warning tabular-nums font-medium",children:o.pending})},{header:"Running",accessor:o=>r("span",{class:"text-info tabular-nums font-medium",children:o.running})},{header:"Status",accessor:o=>r(O,{status:o.paused?"paused":"active"})},{header:"Actions",accessor:o=>o.paused?r(W,{onClick:()=>d(o.name),children:[r(Wt,{class:"w-3.5 h-3.5"}),"Resume"]}):r(W,{variant:"ghost",onClick:()=>u(o.name),children:[r(Ur,{class:"w-3.5 h-3.5"}),"Pause"]})}];return n&&!t?r(A,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ot,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Queue Management"}),r("p",{class:"text-xs text-muted",children:"Monitor and control individual queues"})]})]}),l.length?r(L,{columns:h,data:l}):r(N,{message:"No queues found",subtitle:"Queues appear when tasks are enqueued"})]})}const Fn=[{header:"Name",accessor:e=>r("span",{class:"font-medium",children:e.name})},{header:"Scope",accessor:e=>r(O,{status:e.scope})},{header:"Health",accessor:e=>r(O,{status:e.health})},{header:"Init (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.init_duration_ms.toFixed(1),"ms"]})},{header:"Recreations",accessor:e=>r("span",{class:`tabular-nums ${e.recreations>0?"text-warning":"text-muted"}`,children:e.recreations})},{header:"Dependencies",accessor:e=>e.depends_on.length?r("span",{class:"text-xs",children:e.depends_on.join(", ")}):r("span",{class:"text-muted",children:"—"})},{header:"Pool",accessor:e=>e.pool?r("span",{class:"text-xs tabular-nums",children:[r("span",{class:"text-info",children:e.pool.active}),"/",e.pool.size," active,"," ",r("span",{class:"text-muted",children:[e.pool.idle," idle"]})]}):r("span",{class:"text-muted",children:"—"})}];function Dn(e){const{data:t,loading:n}=$("/api/resources");return n&&!t?r(A,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(At,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Resources"}),r("p",{class:"text-xs text-muted",children:"Worker dependency injection runtime"})]})]}),t!=null&&t.length?r(L,{columns:Fn,data:t}):r(N,{message:"No resources registered",subtitle:"Resources appear when workers start with DI configuration"})]})}const Hn=[{header:"Handler",accessor:e=>r("span",{class:"font-medium",children:e.handler})},{header:"Reconstructions",accessor:e=>r("span",{class:"tabular-nums",children:e.reconstructions})},{header:"Avg (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.avg_ms.toFixed(1),"ms"]})},{header:"Errors",accessor:e=>r("span",{class:`tabular-nums ${e.errors>0?"text-danger font-medium":"text-muted"}`,children:e.errors})}],zn=[{header:"Strategy",accessor:e=>r("span",{class:"font-medium uppercase text-xs tracking-wide",children:e.strategy})},{header:"Count",accessor:e=>r("span",{class:"tabular-nums",children:e.count})},{header:"Avg (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.avg_ms.toFixed(1),"ms"]})}];function Bn(e){const{data:t,loading:n}=$("/api/proxy-stats"),{data:s,loading:a}=$("/api/interception-stats"),i=t?Object.entries(t).map(([l,u])=>({handler:l,...u})):[],c=s?Object.entries(s).map(([l,u])=>({strategy:l,...u})):[];return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-8",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ut,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"System Internals"}),r("p",{class:"text-xs text-muted",children:"Proxy reconstruction and interception metrics"})]})]}),r("div",{class:"mb-8",children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:"Proxy Reconstruction"}),n&&!t?r(A,{}):i.length?r(L,{columns:Hn,data:i}):r(N,{message:"No proxy stats available",subtitle:"Stats appear when proxy handlers are used"})]}),r("div",{children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:"Interception"}),a&&!s?r(A,{}):c.length?r(L,{columns:zn,data:c}):r(N,{message:"No interception stats available",subtitle:"Stats appear when argument interception is enabled"})]})]})}function Jn(e){const{data:t,loading:n}=$("/api/workers"),{data:s}=$("/api/stats");return n&&!t?r(A,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(We,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Workers"}),r("p",{class:"text-xs text-muted",children:[(t==null?void 0:t.length)??0," active ","·"," ",(s==null?void 0:s.running)??0," running jobs"]})]})]}),t!=null&&t.length?r("div",{class:"grid grid-cols-[repeat(auto-fit,minmax(320px,1fr))] gap-4",children:t.map(a=>r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-5 border dark:border-white/[0.06] border-slate-200 transition-all duration-150 hover:shadow-md hover:dark:shadow-black/30",children:[r("div",{class:"flex items-center gap-2 mb-3",children:[r("span",{class:"w-2 h-2 rounded-full bg-success shadow-sm shadow-success/40"}),r("span",{class:"font-mono text-xs text-accent-light font-medium",children:a.worker_id})]}),r("div",{class:"space-y-2 text-[13px]",children:[r("div",{class:"flex items-center gap-2 text-muted",children:[r(We,{class:"w-3.5 h-3.5"}),"Queues: ",r("span",{class:"dark:text-gray-200 text-slate-700",children:a.queues})]}),r("div",{class:"flex items-center gap-2 text-muted",children:[r($e,{class:"w-3.5 h-3.5"}),"Last heartbeat:"," ",r("span",{class:"dark:text-gray-200 text-slate-700",children:T(a.last_heartbeat)})]}),r("div",{class:"flex items-center gap-2 text-muted",children:[r($e,{class:"w-3.5 h-3.5"}),"Registered:"," ",r("span",{class:"dark:text-gray-200 text-slate-700",children:T(a.registered_at)})]}),a.tags&&r("div",{class:"flex items-center gap-2 text-muted",children:[r(Wr,{class:"w-3.5 h-3.5"}),"Tags: ",r("span",{class:"dark:text-gray-200 text-slate-700",children:a.tags})]})]})]},a.worker_id))}):r(N,{message:"No active workers",subtitle:"Workers will appear when they connect"})]})}function Qn(){return r(sn,{children:[r(jt,{children:[r(qn,{path:"/"}),r(Ln,{path:"/jobs"}),r($n,{path:"/jobs/:id"}),r(Un,{path:"/metrics"}),r(An,{path:"/logs"}),r(Jn,{path:"/workers"}),r(_n,{path:"/circuit-breakers"}),r(vn,{path:"/dead-letters"}),r(Dn,{path:"/resources"}),r(Wn,{path:"/queues"}),r(Bn,{path:"/system"})]}),r(ln,{})]})}ir(r(Qn,{}),document.getElementById("app"));
+
From 762628664bd7a99ce4c1a88ce3648252927a4e5c Mon Sep 17 00:00:00 2001
From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:38:20 +0530
Subject: [PATCH 08/11] fix: resolve biome lint warnings in dashboard
Replace non-null assertion with type cast in main.tsx. Use template
literals instead of string concatenation in sidebar, logs, and
dead-letters pages. Zero warnings in CI annotations.
---
dashboard/src/components/layout/sidebar.tsx | 2 +-
dashboard/src/main.tsx | 2 +-
dashboard/src/pages/logs.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/dashboard/src/components/layout/sidebar.tsx b/dashboard/src/components/layout/sidebar.tsx
index 5c11c01..0064754 100644
--- a/dashboard/src/components/layout/sidebar.tsx
+++ b/dashboard/src/components/layout/sidebar.tsx
@@ -55,7 +55,7 @@ const NAV_GROUPS: NavGroup[] = [
function isActive(current: string, path: string): boolean {
if (path === "/") return current === "/";
- return current === path || current.startsWith(path + "/");
+ return current === path || current.startsWith(`${path}/`);
}
export function Sidebar() {
diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx
index 4aa1673..b2c3c36 100644
--- a/dashboard/src/main.tsx
+++ b/dashboard/src/main.tsx
@@ -2,4 +2,4 @@ import { render } from "preact";
import { App } from "./app";
import "./index.css";
-render(, document.getElementById("app")!);
+render(, document.getElementById("app") as HTMLElement);
diff --git a/dashboard/src/pages/logs.tsx b/dashboard/src/pages/logs.tsx
index 51ef323..a93b1a7 100644
--- a/dashboard/src/pages/logs.tsx
+++ b/dashboard/src/pages/logs.tsx
@@ -62,7 +62,7 @@ export function Logs(_props: RoutableProps) {
setTaskFilter((e.target as HTMLInputElement).value)}
From be9892c68efb238ffffb6e0eeeb7b1e8fe7ac39a Mon Sep 17 00:00:00 2001
From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:38:29 +0530
Subject: [PATCH 09/11] feat: add job search bar and auto-refresh indicator to
header
Search input in header navigates directly to job detail page by ID.
Auto-refresh indicator shows relative time since last data fetch
("just now", "3s ago") and highlights the refresh icon when active.
---
dashboard/src/components/layout/header.tsx | 56 ++++++++++++++++++++--
dashboard/src/hooks/use-api.ts | 3 +-
dashboard/src/hooks/use-auto-refresh.ts | 5 ++
3 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/dashboard/src/components/layout/header.tsx b/dashboard/src/components/layout/header.tsx
index bbbd569..62b5d8c 100644
--- a/dashboard/src/components/layout/header.tsx
+++ b/dashboard/src/components/layout/header.tsx
@@ -1,8 +1,37 @@
-import { Moon, RefreshCw, Sun, Zap } from "lucide-preact";
-import { refreshInterval, setRefreshInterval } from "../../hooks/use-auto-refresh";
+import { Moon, RefreshCw, Search, Sun, Zap } from "lucide-preact";
+import { useEffect, useState } from "preact/hooks";
+import { route } from "preact-router";
+import { lastRefreshAt, refreshInterval, setRefreshInterval } from "../../hooks/use-auto-refresh";
import { theme, toggleTheme } from "../../hooks/use-theme";
+function RelativeTime() {
+ const [ago, setAgo] = useState("");
+
+ useEffect(() => {
+ const update = () => {
+ const seconds = Math.round((Date.now() - lastRefreshAt.value) / 1000);
+ setAgo(seconds < 2 ? "just now" : `${seconds}s ago`);
+ };
+ update();
+ const timer = setInterval(update, 1000);
+ return () => clearInterval(timer);
+ }, [lastRefreshAt.value]);
+
+ return {ago};
+}
+
export function Header() {
+ const [searchValue, setSearchValue] = useState("");
+
+ const handleSearch = (e: Event) => {
+ e.preventDefault();
+ const id = searchValue.trim();
+ if (id) {
+ route(`/jobs/${id}`);
+ setSearchValue("");
+ }
+ };
+
return (
@@ -15,10 +44,28 @@ export function Header() {
dashboard
+ {/* Job ID search */}
+
+
- {/* Refresh interval */}
+ {/* Refresh interval + indicator */}
-
+ 0 ? "text-accent" : "text-muted"}`}
+ strokeWidth={refreshInterval.value > 0 ? 2 : 1.5}
+ />
10s
+
diff --git a/dashboard/src/hooks/use-api.ts b/dashboard/src/hooks/use-api.ts
index a6e3128..bae158e 100644
--- a/dashboard/src/hooks/use-api.ts
+++ b/dashboard/src/hooks/use-api.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { api } from "../api/client";
-import { refreshInterval } from "./use-auto-refresh";
+import { markRefreshed, refreshInterval } from "./use-auto-refresh";
interface UseApiResult {
data: T | null;
@@ -35,6 +35,7 @@ export function useApi(url: string | null, deps: unknown[] = []): UseApiResul
setData(result);
setError(null);
setLoading(false);
+ markRefreshed();
}
})
.catch((err) => {
diff --git a/dashboard/src/hooks/use-auto-refresh.ts b/dashboard/src/hooks/use-auto-refresh.ts
index 536730d..96739f2 100644
--- a/dashboard/src/hooks/use-auto-refresh.ts
+++ b/dashboard/src/hooks/use-auto-refresh.ts
@@ -1,7 +1,12 @@
import { signal } from "@preact/signals";
export const refreshInterval = signal(5000);
+export const lastRefreshAt = signal(Date.now());
export function setRefreshInterval(ms: number): void {
refreshInterval.value = ms;
}
+
+export function markRefreshed(): void {
+ lastRefreshAt.value = Date.now();
+}
From 56e5ad759f30672ab0073aec7f2d883f2c5b9a8c Mon Sep 17 00:00:00 2001
From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:38:38 +0530
Subject: [PATCH 10/11] feat: add bulk select and actions to jobs page
DataTable supports optional checkbox selection with select-all
header toggle. Jobs page shows bulk action bar when items are
selected: Cancel Selected (with confirm dialog), Replay Selected,
and Clear Selection. Actions process jobs sequentially and show
toast with success/failure counts.
---
dashboard/src/components/ui/data-table.tsx | 101 ++++++++++++++++-----
dashboard/src/pages/jobs.tsx | 95 ++++++++++++++++++-
2 files changed, 169 insertions(+), 27 deletions(-)
diff --git a/dashboard/src/components/ui/data-table.tsx b/dashboard/src/components/ui/data-table.tsx
index 613546f..fba5878 100644
--- a/dashboard/src/components/ui/data-table.tsx
+++ b/dashboard/src/components/ui/data-table.tsx
@@ -11,19 +11,59 @@ interface DataTableProps {
data: T[];
onRowClick?: (row: T) => void;
children?: ComponentChildren;
+ selectable?: boolean;
+ selectedKeys?: Set;
+ rowKey?: (row: T) => string;
+ onSelectionChange?: (keys: Set) => void;
}
-export function DataTable({ columns, data, onRowClick, children }: DataTableProps) {
+export function DataTable({
+ columns,
+ data,
+ onRowClick,
+ children,
+ selectable,
+ selectedKeys,
+ rowKey,
+ onSelectionChange,
+}: DataTableProps) {
+ const allKeys = selectable && rowKey ? data.map(rowKey) : [];
+ const allSelected =
+ selectable && allKeys.length > 0 && allKeys.every((k) => selectedKeys?.has(k));
+
+ const toggleAll = () => {
+ if (!onSelectionChange) return;
+ onSelectionChange(allSelected ? new Set() : new Set(allKeys));
+ };
+
+ const toggleRow = (key: string) => {
+ if (!onSelectionChange || !selectedKeys) return;
+ const next = new Set(selectedKeys);
+ if (next.has(key)) next.delete(key);
+ else next.add(key);
+ onSelectionChange(next);
+ };
+
return (
+ {selectable && (
+ |
+
+ |
+ )}
{columns.map((col, i) => (
{col.header}
|
@@ -31,27 +71,42 @@ export function DataTable({ columns, data, onRowClick, children }: DataTableP
- {data.map((row, ri) => (
- onRowClick(row) : undefined}
- class={`border-b dark:border-white/[0.03] border-slate-50 last:border-0 transition-colors duration-100 ${
- ri % 2 === 1 ? "dark:bg-white/[0.01] bg-slate-50/30" : ""
- } ${
- onRowClick
- ? "cursor-pointer hover:dark:bg-accent/[0.04] hover:bg-accent/[0.02]"
- : ""
- }`}
- >
- {columns.map((col, ci) => (
- |
- {typeof col.accessor === "function"
- ? col.accessor(row)
- : (row[col.accessor] as ComponentChildren)}
- |
- ))}
-
- ))}
+ {data.map((row, ri) => {
+ const key = selectable && rowKey ? rowKey(row) : String(ri);
+ const isSelected = selectedKeys?.has(key);
+ return (
+ onRowClick(row) : undefined}
+ class={`border-b dark:border-white/[0.03] border-slate-50 last:border-0 transition-colors duration-100 ${
+ ri % 2 === 1 ? "dark:bg-white/[0.01] bg-slate-50/30" : ""
+ } ${isSelected ? "dark:bg-accent/[0.08] bg-accent/[0.04]" : ""} ${
+ onRowClick
+ ? "cursor-pointer hover:dark:bg-accent/[0.04] hover:bg-accent/[0.02]"
+ : ""
+ }`}
+ >
+ {selectable && (
+ |
+ toggleRow(key)}
+ onClick={(e) => e.stopPropagation()}
+ class="accent-accent cursor-pointer"
+ />
+ |
+ )}
+ {columns.map((col, ci) => (
+
+ {typeof col.accessor === "function"
+ ? col.accessor(row)
+ : (row[col.accessor] as ComponentChildren)}
+ |
+ ))}
+
+ );
+ })}
diff --git a/dashboard/src/pages/jobs.tsx b/dashboard/src/pages/jobs.tsx
index 86acbd8..e9c6f76 100644
--- a/dashboard/src/pages/jobs.tsx
+++ b/dashboard/src/pages/jobs.tsx
@@ -1,8 +1,11 @@
-import { ListTodo, Search } from "lucide-preact";
+import { Ban, ListTodo, RotateCcw, Search, X } from "lucide-preact";
import { useState } from "preact/hooks";
import { route } from "preact-router";
+import { apiPost } from "../api/client";
import type { Job, QueueStats } from "../api/types";
import { Badge } from "../components/ui/badge";
+import { Button } from "../components/ui/button";
+import { ConfirmDialog } from "../components/ui/confirm-dialog";
import { type Column, DataTable } from "../components/ui/data-table";
import { EmptyState } from "../components/ui/empty-state";
import { Loading } from "../components/ui/loading";
@@ -10,6 +13,7 @@ import { Pagination } from "../components/ui/pagination";
import { ProgressBar } from "../components/ui/progress-bar";
import { StatsGrid } from "../components/ui/stats-grid";
import { useApi } from "../hooks/use-api";
+import { addToast } from "../hooks/use-toast";
import { fmtTime, truncateId } from "../lib/format";
import type { RoutableProps } from "../lib/routes";
@@ -43,7 +47,10 @@ const JOB_COLUMNS: Column [] = [
),
},
- { header: "Created", accessor: (j) => {fmtTime(j.created_at)} },
+ {
+ header: "Created",
+ accessor: (j) => {fmtTime(j.created_at)},
+ },
];
function buildUrl(filters: Filters, page: number): string {
@@ -73,9 +80,15 @@ export function Jobs(_props: RoutableProps) {
created_before: "",
});
const [page, setPage] = useState(0);
+ const [selected, setSelected] = useState>(new Set());
+ const [showBulkCancel, setShowBulkCancel] = useState(false);
const { data: stats } = useApi("/api/stats");
- const { data: jobs, loading } = useApi(buildUrl(filters, page), [
+ const {
+ data: jobs,
+ loading,
+ refetch,
+ } = useApi(buildUrl(filters, page), [
filters.status,
filters.queue,
filters.task,
@@ -89,6 +102,41 @@ export function Jobs(_props: RoutableProps) {
const updateFilter = (key: keyof Filters, value: string) => {
setFilters((f) => ({ ...f, [key]: value }));
setPage(0);
+ setSelected(new Set());
+ };
+
+ const handleBulkCancel = async () => {
+ setShowBulkCancel(false);
+ let cancelled = 0;
+ for (const id of selected) {
+ try {
+ const res = await apiPost<{ cancelled: boolean }>(`/api/jobs/${id}/cancel`);
+ if (res.cancelled) cancelled++;
+ } catch {
+ /* skip failed */
+ }
+ }
+ addToast(
+ `Cancelled ${cancelled} of ${selected.size} jobs`,
+ cancelled > 0 ? "success" : "error",
+ );
+ setSelected(new Set());
+ refetch();
+ };
+
+ const handleBulkReplay = async () => {
+ let replayed = 0;
+ for (const id of selected) {
+ try {
+ await apiPost<{ replay_job_id: string }>(`/api/jobs/${id}/replay`);
+ replayed++;
+ } catch {
+ /* skip failed */
+ }
+ }
+ addToast(`Replayed ${replayed} of ${selected.size} jobs`, replayed > 0 ? "success" : "error");
+ setSelected(new Set());
+ refetch();
};
const inputClass =
@@ -168,12 +216,43 @@ export function Jobs(_props: RoutableProps) {
+ {/* Bulk action bar */}
+ {selected.size > 0 && (
+
+
+ {selected.size} job{selected.size > 1 ? "s" : ""} selected
+
+
+
+
+
+
+
+ )}
+
{loading && !jobs ? (
) : !jobs?.length ? (
) : (
- route(`/jobs/${j.id}`)}>
+ route(`/jobs/${j.id}`)}
+ selectable
+ selectedKeys={selected}
+ rowKey={(j) => j.id}
+ onSelectionChange={setSelected}
+ >
)}
+
+ {showBulkCancel && (
+ 1 ? "s" : ""}? Only pending jobs can be cancelled.`}
+ onConfirm={handleBulkCancel}
+ onCancel={() => setShowBulkCancel(false)}
+ />
+ )}
);
}
From 3d707acae77e46f30a38baa4b4e853523185702e Mon Sep 17 00:00:00 2001
From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:38:47 +0530
Subject: [PATCH 11/11] feat: add error grouping toggle to dead letters page
Toggle between list view (flat, paginated) and grouped view that
collapses dead letters by error message. Groups show occurrence
count, expandable item list, and Retry All button. Groups sorted
by count descending so the most common errors appear first.
---
dashboard/src/pages/dead-letters.tsx | 146 ++++++++++++++++++++++--
py_src/taskito/templates/dashboard.html | 99 +++++++++-------
2 files changed, 192 insertions(+), 53 deletions(-)
diff --git a/dashboard/src/pages/dead-letters.tsx b/dashboard/src/pages/dead-letters.tsx
index eeb0897..1da06ea 100644
--- a/dashboard/src/pages/dead-letters.tsx
+++ b/dashboard/src/pages/dead-letters.tsx
@@ -1,4 +1,4 @@
-import { RotateCcw, Skull, Trash2 } from "lucide-preact";
+import { ChevronDown, ChevronRight, Group, List, RotateCcw, Skull, Trash2 } from "lucide-preact";
import { useState } from "preact/hooks";
import { apiPost } from "../api/client";
import type { DeadLetter } from "../api/types";
@@ -15,17 +15,38 @@ import type { RoutableProps } from "../lib/routes";
const PAGE_SIZE = 20;
+interface ErrorGroup {
+ error: string;
+ items: DeadLetter[];
+}
+
+function groupByError(items: DeadLetter[]): ErrorGroup[] {
+ const map = new Map();
+ for (const item of items) {
+ const key = item.error ?? "(no error message)";
+ const list = map.get(key);
+ if (list) list.push(item);
+ else map.set(key, [item]);
+ }
+ return Array.from(map.entries())
+ .map(([error, items]) => ({ error, items }))
+ .sort((a, b) => b.items.length - a.items.length);
+}
+
export function DeadLetters(_props: RoutableProps) {
const [page, setPage] = useState(0);
const [showPurge, setShowPurge] = useState(false);
+ const [grouped, setGrouped] = useState(false);
+ const [expandedGroups, setExpandedGroups] = useState>(new Set());
const {
data: items,
loading,
refetch,
- } = useApi(`/api/dead-letters?limit=${PAGE_SIZE}&offset=${page * PAGE_SIZE}`, [
- page,
- ]);
+ } = useApi(
+ `/api/dead-letters?limit=${grouped ? 200 : PAGE_SIZE}&offset=${grouped ? 0 : page * PAGE_SIZE}`,
+ [page, grouped],
+ );
const handleRetry = async (id: string) => {
try {
@@ -37,6 +58,23 @@ export function DeadLetters(_props: RoutableProps) {
}
};
+ const handleRetryGroup = async (group: ErrorGroup) => {
+ let retried = 0;
+ for (const item of group.items) {
+ try {
+ await apiPost<{ new_job_id: string }>(`/api/dead-letters/${item.id}/retry`);
+ retried++;
+ } catch {
+ /* skip failed */
+ }
+ }
+ addToast(
+ `Retried ${retried} of ${group.items.length} dead letters`,
+ retried > 0 ? "success" : "error",
+ );
+ refetch();
+ };
+
const handlePurge = async () => {
setShowPurge(false);
try {
@@ -48,6 +86,13 @@ export function DeadLetters(_props: RoutableProps) {
}
};
+ const toggleGroup = (error: string) => {
+ const next = new Set(expandedGroups);
+ if (next.has(error)) next.delete(error);
+ else next.add(error);
+ setExpandedGroups(next);
+ };
+
const columns: Column[] = [
{
header: "ID",
@@ -70,7 +115,7 @@ export function DeadLetters(_props: RoutableProps) {
header: "Error",
accessor: (d) => (
- {d.error ? (d.error.length > 50 ? d.error.slice(0, 50) + "\u2026" : d.error) : "\u2014"}
+ {d.error ? (d.error.length > 50 ? `${d.error.slice(0, 50)}\u2026` : d.error) : "\u2014"}
),
className: "max-w-[250px]",
@@ -96,6 +141,8 @@ export function DeadLetters(_props: RoutableProps) {
if (loading && !items) return ;
+ const groups = grouped && items ? groupByError(items) : [];
+
return (
@@ -108,17 +155,94 @@ export function DeadLetters(_props: RoutableProps) {
Failed jobs that exhausted all retries
- {items && items.length > 0 && (
-
- )}
+
+ {items && items.length > 0 && (
+ <>
+
+
+
+
+
+ >
+ )}
+
{!items?.length ? (
+ ) : grouped ? (
+ /* Grouped view */
+
+ {groups.map((group) => {
+ const isExpanded = expandedGroups.has(group.error);
+ return (
+
+ toggleGroup(group.error)}
+ >
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ {group.error}
+
+
+ {group.items.length}
+
+
+
+ {isExpanded && (
+
+
+
+ )}
+
+ );
+ })}
+
) : (
+ /* List view */
taskito dashboard
-
-
+ */const Vr=v("trending-up",[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]]);/**
+ * @license lucide-preact v0.577.0 - ISC
+ *
+ * This source code is licensed under the ISC license.
+ * See the LICENSE file in the root directory of this source tree.
+ */const Qr=v("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);/**
+ * @license lucide-preact v0.577.0 - ISC
+ *
+ * This source code is licensed under the ISC license.
+ * See the LICENSE file in the root directory of this source tree.
+ */const Vt=v("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);/**
+ * @license lucide-preact v0.577.0 - ISC
+ *
+ * This source code is licensed under the ISC license.
+ * See the LICENSE file in the root directory of this source tree.
+ */const Yr=v("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]);var Zr=Symbol.for("preact-signals");function Ye(){if(B>1)B--;else{var e,t=!1;for((function(){var a=Me;for(Me=void 0;a!==void 0;)a.S.v===a.v&&(a.S.i=a.i),a=a.o})();ie!==void 0;){var n=ie;for(ie=void 0,Ce++;n!==void 0;){var s=n.u;if(n.u=void 0,n.f&=-3,!(8&n.f)&&Zt(n))try{n.c()}catch(a){t||(e=a,t=!0)}n=s}}if(Ce=0,B--,t)throw e}}var k=void 0;function Qt(e){var t=k;k=void 0;try{return e()}finally{k=t}}var ie=void 0,B=0,Ce=0,mt=0,Me=void 0,Te=0;function Yt(e){if(k!==void 0){var t=e.n;if(t===void 0||t.t!==k)return t={i:0,S:e,p:k.s,n:void 0,t:k,e:void 0,x:void 0,r:t},k.s!==void 0&&(k.s.n=t),k.s=t,e.n=t,32&k.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=k.s,t.n=void 0,k.s.n=t,k.s=t),t}}function T(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.l=0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}T.prototype.brand=Zr;T.prototype.h=function(){return!0};T.prototype.S=function(e){var t=this,n=this.t;n!==e&&e.e===void 0&&(e.x=n,this.t=e,n!==void 0?n.e=e:Qt(function(){var s;(s=t.W)==null||s.call(t)}))};T.prototype.U=function(e){var t=this;if(this.t!==void 0){var n=e.e,s=e.x;n!==void 0&&(n.x=s,e.e=void 0),s!==void 0&&(s.e=n,e.x=void 0),e===this.t&&(this.t=s,s===void 0&&Qt(function(){var a;(a=t.Z)==null||a.call(t)}))}};T.prototype.subscribe=function(e){var t=this;return Re(function(){var n=t.value,s=k;k=void 0;try{e(n)}finally{k=s}},{name:"sub"})};T.prototype.valueOf=function(){return this.value};T.prototype.toString=function(){return this.value+""};T.prototype.toJSON=function(){return this.value};T.prototype.peek=function(){var e=k;k=void 0;try{return this.value}finally{k=e}};Object.defineProperty(T.prototype,"value",{get:function(){var e=Yt(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(Ce>100)throw new Error("Cycle detected");(function(n){B!==0&&Ce===0&&n.l!==mt&&(n.l=mt,Me={S:n,v:n.v,i:n.i,o:Me})})(this),this.v=e,this.i++,Te++,B++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{Ye()}}}});function re(e,t){return new T(e,t)}function Zt(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Xt(e){for(var t=e.s;t!==void 0;t=t.n){var n=t.S.n;if(n!==void 0&&(t.r=n),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Kt(e){for(var t=e.s,n=void 0;t!==void 0;){var s=t.p;t.i===-1?(t.S.U(t),s!==void 0&&(s.n=t.n),t.n!==void 0&&(t.n.p=s)):n=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=s}e.s=n}function V(e,t){T.call(this,void 0),this.x=e,this.s=void 0,this.g=Te-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}V.prototype=new T;V.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Te))return!0;if(this.g=Te,this.f|=1,this.i>0&&!Zt(this))return this.f&=-2,!0;var e=k;try{Xt(this),k=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(n){this.v=n,this.f|=16,this.i++}return k=e,Kt(this),this.f&=-2,!0};V.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}T.prototype.S.call(this,e)};V.prototype.U=function(e){if(this.t!==void 0&&(T.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};V.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(V.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=Yt(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function Xr(e,t){return new V(e,t)}function er(e){var t=e.m;if(e.m=void 0,typeof t=="function"){B++;var n=k;k=void 0;try{t()}catch(s){throw e.f&=-2,e.f|=8,Ze(e),s}finally{k=n,Ye()}}}function Ze(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,er(e)}function Kr(e){if(k!==this)throw new Error("Out-of-order effect");Kt(this),k=e,this.f&=-2,8&this.f&&Ze(this),Ye()}function ne(e,t){this.x=e,this.m=void 0,this.s=void 0,this.u=void 0,this.f=32,this.name=t==null?void 0:t.name}ne.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.m=t)}finally{e()}};ne.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,er(this),Xt(this),B++;var e=k;return k=this,Kr.bind(this,e)};ne.prototype.N=function(){2&this.f||(this.f|=2,this.u=ie,ie=this)};ne.prototype.d=function(){this.f|=8,1&this.f||Ze(this)};ne.prototype.dispose=function(){this.d()};function Re(e,t){var n=new ne(e,t);try{n.c()}catch(a){throw n.d(),a}var s=n.d.bind(n);return s[Symbol.dispose]=s,s}var ge;function se(e,t){w[e]=t.bind(null,w[e]||function(){})}function Pe(e){if(ge){var t=ge;ge=void 0,t()}ge=e&&e.S()}function tr(e){var t=this,n=e.data,s=tn(n);s.value=n;var a=je(function(){for(var o=t.__v;o=o.__;)if(o.__c){o.__c.__$f|=4;break}return t.__$u.c=function(){var i,l=t.__$u.S(),u=a.value;l(),gt(u)||((i=t.base)==null?void 0:i.nodeType)!==3?(t.__$f|=1,t.setState({})):t.base.data=u},Xr(function(){var i=s.value.value;return i===0?0:i===!0?"":i||""})},[]);return a.value}tr.displayName="_st";Object.defineProperties(T.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:tr},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});se("__b",function(e,t){if(typeof t.type=="string"){var n,s=t.props;for(var a in s)if(a!=="children"){var o=s[a];o instanceof T&&(n||(t.__np=n={}),n[a]=o,s[a]=o.peek())}}e(t)});se("__r",function(e,t){e(t),Pe();var n,s=t.__c;s&&(s.__$f&=-2,(n=s.__$u)===void 0&&(s.__$u=n=(function(a){var o;return Re(function(){o=this}),o.c=function(){s.__$f|=1,s.setState({})},o})())),Pe(n)});se("__e",function(e,t,n,s){Pe(),e(t,n,s)});se("diffed",function(e,t){Pe();var n;if(typeof t.type=="string"&&(n=t.__e)){var s=t.__np,a=t.props;if(s){var o=n.U;if(o)for(var i in o){var l=o[i];l!==void 0&&!(i in s)&&(l.d(),o[i]=void 0)}else n.U=o={};for(var u in s){var d=o[u],h=s[u];d===void 0?(d=en(n,u,h,a),o[u]=d):d.o(h,a)}}}e(t)});function en(e,t,n,s){var a=t in e&&e.ownerSVGElement===void 0,o=re(n);return{o:function(i,l){o.value=i,s=l},d:Re(function(){var i=o.value.value;s[t]!==i&&(s[t]=i,a?e[t]=i:i?e.setAttribute(t,i):e.removeAttribute(t))})}}se("unmount",function(e,t){if(typeof t.type=="string"){var n=t.__e;if(n){var s=n.U;if(s){n.U=void 0;for(var a in s){var o=s[a];o&&o.d()}}}}else{var i=t.__c;if(i){var l=i.__$u;l&&(i.__$u=void 0,l.d())}}e(t)});se("__h",function(e,t,n,s){(s<3||s===9)&&(t.__$f|=2),e(t,n,s)});Y.prototype.shouldComponentUpdate=function(e,t){if(this.__R)return!0;var n=this.__$u,s=n&&n.s!==void 0;for(var a in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){if(!(s||2&this.__$f||4&this.__$f)||1&this.__$f)return!0}else if(!(s||4&this.__$f)||3&this.__$f)return!0;for(var o in e)if(o!=="__source"&&e[o]!==this.props[o])return!0;for(var i in this.props)if(!(i in e))return!0;return!1};function tn(e){return je(function(){return re(e)},[])}const J=re(5e3),De=re(Date.now());function rn(e){J.value=e}function nn(){De.value=Date.now()}const sn=localStorage.getItem("taskito-theme"),ee=re(sn??"dark");Re(()=>{const e=document.documentElement;ee.value==="dark"?(e.classList.add("dark"),e.classList.remove("light")):(e.classList.remove("dark"),e.classList.add("light")),localStorage.setItem("taskito-theme",ee.value)});function an(){ee.value=ee.value==="dark"?"light":"dark"}function on(){const[e,t]=M("");return X(()=>{const n=()=>{const a=Math.round((Date.now()-De.value)/1e3);t(a<2?"just now":`${a}s ago`)};n();const s=setInterval(n,1e3);return()=>clearInterval(s)},[De.value]),r("span",{class:"text-[11px] text-muted tabular-nums",children:e})}function cn(){const[e,t]=M("");return r("header",{class:"h-14 flex items-center justify-between px-6 dark:bg-surface-2/80 bg-white/80 backdrop-blur-md border-b dark:border-white/[0.06] border-slate-200 sticky top-0 z-40",children:[r("a",{href:"/",class:"flex items-center gap-2 no-underline group",children:[r("div",{class:"w-7 h-7 rounded-lg bg-gradient-to-br from-accent to-accent-light flex items-center justify-center shadow-md shadow-accent/20",children:r(Yr,{class:"w-4 h-4 text-white",strokeWidth:2.5})}),r("span",{class:"text-[15px] font-semibold dark:text-white text-slate-900 tracking-tight",children:"taskito"}),r("span",{class:"text-xs text-muted font-normal hidden sm:inline",children:"dashboard"})]}),r("form",{onSubmit:s=>{s.preventDefault();const a=e.trim();a&&(te(`/jobs/${a}`),t(""))},class:"flex items-center gap-2 dark:bg-surface-3 bg-slate-100 rounded-lg px-3 py-1.5 border dark:border-white/[0.06] border-slate-200 w-64",children:[r(Jt,{class:"w-3.5 h-3.5 text-muted shrink-0"}),r("input",{type:"text",placeholder:"Jump to job ID\\u2026",value:e,onInput:s=>t(s.target.value),class:"bg-transparent border-none outline-none text-[13px] dark:text-gray-200 text-slate-700 placeholder:text-muted/50 w-full"})]}),r("div",{class:"flex items-center gap-3",children:[r("div",{class:"flex items-center gap-2 text-xs",children:[r(Hr,{class:`w-3.5 h-3.5 ${J.value>0?"text-accent":"text-muted"}`,strokeWidth:J.value>0?2:1.5}),r("select",{class:"dark:bg-surface-3 bg-slate-100 dark:text-gray-300 text-slate-600 border dark:border-white/[0.06] border-slate-200 rounded-md px-2 py-1 text-xs cursor-pointer hover:dark:border-white/10 hover:border-slate-300 transition-colors",value:J.value,onChange:s=>rn(Number(s.target.value)),children:[r("option",{value:2e3,children:"2s"}),r("option",{value:5e3,children:"5s"}),r("option",{value:1e4,children:"10s"}),r("option",{value:0,children:"Off"})]}),r(on,{})]}),r("div",{class:"w-px h-5 dark:bg-white/[0.06] bg-slate-200"}),r("button",{type:"button",onClick:an,class:"p-2 rounded-lg dark:text-gray-400 text-slate-500 hover:dark:text-white hover:text-slate-900 hover:dark:bg-surface-3 hover:bg-slate-100 transition-all duration-150 border-none cursor-pointer bg-transparent",title:`Switch to ${ee.value==="dark"?"light":"dark"} mode`,children:ee.value==="dark"?r(Br,{class:"w-4 h-4"}):r(Dr,{class:"w-4 h-4"})})]})]})}const ln=[{title:"Monitoring",items:[{path:"/",label:"Overview",icon:Ft},{path:"/jobs",label:"Jobs",icon:zt},{path:"/metrics",label:"Metrics",icon:Ut},{path:"/logs",label:"Logs",icon:Bt}]},{title:"Infrastructure",items:[{path:"/workers",label:"Workers",icon:Fe},{path:"/queues",label:"Queues",icon:Dt},{path:"/resources",label:"Resources",icon:Et},{path:"/circuit-breakers",label:"Circuit Breakers",icon:Gt}]},{title:"Advanced",items:[{path:"/dead-letters",label:"Dead Letters",icon:Qe},{path:"/system",label:"System",icon:Wt}]}];function dn(e,t){return t==="/"?e==="/":e===t||e.startsWith(`${t}/`)}function un(){const[e,t]=M(ue());return X(()=>{const n=()=>t(ue());return addEventListener("popstate",n),addEventListener("pushstate",n),()=>{removeEventListener("popstate",n),removeEventListener("pushstate",n)}},[]),r("aside",{class:"w-60 shrink-0 border-r dark:border-white/[0.06] border-slate-200 dark:bg-surface-2/50 bg-slate-50/50 overflow-y-auto h-[calc(100vh-56px)]",children:r("nav",{class:"p-3 space-y-5 pt-4",children:ln.map(n=>r("div",{children:[r("div",{class:"px-3 pb-2 text-[10px] font-bold uppercase tracking-[0.1em] text-muted/60",children:n.title}),r("div",{class:"space-y-0.5",children:n.items.map(s=>{const a=dn(e,s.path),o=s.icon;return r("a",{href:s.path,class:`flex items-center gap-2.5 px-3 py-2 text-[13px] rounded-lg transition-all duration-150 no-underline relative ${a?"dark:bg-accent/10 bg-accent/5 dark:text-white text-slate-900 font-medium":"dark:text-gray-400 text-slate-500 hover:dark:text-gray-200 hover:text-slate-700 hover:dark:bg-white/[0.03] hover:bg-slate-100"}`,children:[a&&r("div",{class:"absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-4 rounded-r-full bg-accent"}),r(o,{class:`w-4 h-4 shrink-0 ${a?"text-accent":""}`,strokeWidth:a?2.2:1.8}),s.label]},s.path)})})]},n.title))})})}function hn({children:e}){return r("div",{class:"min-h-screen",children:[r(cn,{}),r("div",{class:"flex",children:[r(un,{}),r("main",{class:"flex-1 p-8 overflow-auto h-[calc(100vh-56px)]",children:r("div",{class:"max-w-[1280px] mx-auto",children:e})})]})]})}const z=re([]);let pn=0;function P(e,t="info",n=3e3){const s=String(++pn);z.value=[...z.value,{id:s,message:e,type:t}],setTimeout(()=>{z.value=z.value.filter(a=>a.id!==s)},n)}function fn(e){z.value=z.value.filter(t=>t.id!==e)}const mn={success:{border:"border-l-success",icon:Ot,iconColor:"text-success"},error:{border:"border-l-danger",icon:qt,iconColor:"text-danger"},info:{border:"border-l-info",icon:Wr,iconColor:"text-info"}};function _n(){const e=z.value;return e.length?r("div",{class:"fixed bottom-5 right-5 z-50 flex flex-col gap-2.5 max-w-sm",children:e.map(t=>{const n=mn[t.type],s=n.icon;return r("div",{class:`flex items-start gap-3 border-l-[3px] ${n.border} rounded-lg px-4 py-3.5 text-[13px] dark:bg-surface-2 bg-white shadow-xl dark:shadow-black/40 dark:text-gray-200 text-slate-700 animate-slide-in border dark:border-white/[0.06] border-slate-200`,role:"alert",children:[r(s,{class:`w-4.5 h-4.5 ${n.iconColor} shrink-0 mt-0.5`,strokeWidth:2}),r("span",{class:"flex-1",children:t.message}),r("button",{type:"button",onClick:()=>fn(t.id),class:"text-muted hover:dark:text-white hover:text-slate-900 transition-colors border-none bg-transparent cursor-pointer p-0.5",children:r(Vt,{class:"w-3.5 h-3.5"})})]},t.id)})}):null}const gn={pending:"bg-warning/10 text-warning border-warning/20",running:"bg-info/10 text-info border-info/20",complete:"bg-success/10 text-success border-success/20",failed:"bg-danger/10 text-danger border-danger/20",dead:"bg-danger/15 text-danger border-danger/25",cancelled:"bg-muted/10 text-muted border-muted/20",closed:"bg-success/10 text-success border-success/20",open:"bg-danger/10 text-danger border-danger/20",half_open:"bg-warning/10 text-warning border-warning/20",healthy:"bg-success/10 text-success border-success/20",unhealthy:"bg-danger/10 text-danger border-danger/20",degraded:"bg-warning/10 text-warning border-warning/20",active:"bg-success/10 text-success border-success/20",paused:"bg-warning/10 text-warning border-warning/20"},xn={pending:"bg-warning",running:"bg-info",complete:"bg-success",failed:"bg-danger",dead:"bg-danger",cancelled:"bg-muted",closed:"bg-success",open:"bg-danger",half_open:"bg-warning",healthy:"bg-success",unhealthy:"bg-danger",degraded:"bg-warning",active:"bg-success",paused:"bg-warning"};function W({status:e}){const t=gn[e]??"bg-muted/10 text-muted border-muted/20",n=xn[e]??"bg-muted";return r("span",{class:`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-semibold uppercase tracking-wide border ${t}`,children:[r("span",{class:`w-1.5 h-1.5 rounded-full ${n}`}),e]})}function R({columns:e,data:t,onRowClick:n,children:s,selectable:a,selectedKeys:o,rowKey:i,onSelectionChange:l}){const u=a&&i?t.map(i):[],d=a&&u.length>0&&u.every(f=>o==null?void 0:o.has(f)),h=()=>{l&&l(d?new Set:new Set(u))},c=f=>{if(!l||!o)return;const p=new Set(o);p.has(f)?p.delete(f):p.add(f),l(p)};return r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 overflow-hidden border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"overflow-x-auto",children:r("table",{class:"w-full border-collapse text-[13px]",children:[r("thead",{children:r("tr",{children:[a&&r("th",{class:"w-10 text-center px-3 py-2.5 dark:bg-surface-3/50 bg-slate-50 border-b dark:border-white/[0.04] border-slate-100",children:r("input",{type:"checkbox",checked:d,onChange:h,class:"accent-accent cursor-pointer"})}),e.map((f,p)=>r("th",{class:`text-left px-4 py-2.5 dark:bg-surface-3/50 bg-slate-50 text-muted font-semibold text-[11px] uppercase tracking-[0.05em] whitespace-nowrap border-b dark:border-white/[0.04] border-slate-100 ${f.className??""}`,children:f.header},p))]})}),r("tbody",{children:t.map((f,p)=>{const m=a&&i?i(f):String(p),b=o==null?void 0:o.has(m);return r("tr",{onClick:n?()=>n(f):void 0,class:`border-b dark:border-white/[0.03] border-slate-50 last:border-0 transition-colors duration-100 ${p%2===1?"dark:bg-white/[0.01] bg-slate-50/30":""} ${b?"dark:bg-accent/[0.08] bg-accent/[0.04]":""} ${n?"cursor-pointer hover:dark:bg-accent/[0.04] hover:bg-accent/[0.02]":""}`,children:[a&&r("td",{class:"w-10 text-center px-3 py-3",children:r("input",{type:"checkbox",checked:b,onChange:()=>c(m),onClick:x=>x.stopPropagation(),class:"accent-accent cursor-pointer"})}),e.map((x,g)=>r("td",{class:`px-4 py-3 whitespace-nowrap ${x.className??""}`,children:typeof x.accessor=="function"?x.accessor(f):f[x.accessor]},g))]},m)})})]})}),s]})}function U({message:e,subtitle:t}){return r("div",{class:"flex flex-col items-center justify-center py-16 text-center",children:[r("div",{class:"w-12 h-12 rounded-xl dark:bg-surface-3 bg-slate-100 flex items-center justify-center mb-4",children:r(qr,{class:"w-6 h-6 text-muted",strokeWidth:1.5})}),r("p",{class:"text-sm font-medium dark:text-gray-300 text-slate-600",children:e}),t&&r("p",{class:"text-xs text-muted mt-1",children:t})]})}function N(){return r("div",{class:"flex items-center justify-center py-20",children:r("div",{class:"flex items-center gap-3 text-muted text-sm",children:[r("div",{class:"w-5 h-5 border-2 border-accent/30 border-t-accent rounded-full animate-spin"}),r("span",{children:"Loading\\u2026"})]})})}class rr extends Error{constructor(t,n){super(n),this.status=t}}async function bn(e,t){const n=await fetch(e,{signal:t});if(!n.ok)throw new rr(n.status,`${n.status} ${n.statusText}`);return n.json()}async function q(e,t){const n=await fetch(e,{method:"POST",signal:t});if(!n.ok)throw new rr(n.status,`${n.status} ${n.statusText}`);return n.json()}function S(e,t=[]){const[n,s]=M(null),[a,o]=M(!0),[i,l]=M(null),u=K(null),d=K(!0),h=gr(()=>{var f;if(!e){s(null),o(!1);return}(f=u.current)==null||f.abort();const c=new AbortController;u.current=c,o(p=>n===null?!0:p),bn(e,c.signal).then(p=>{d.current&&!c.signal.aborted&&(s(p),l(null),o(!1),nn())}).catch(p=>{d.current&&!c.signal.aborted&&(l(p.message??"Failed to fetch"),o(!1))})},[e,...t]);return X(()=>(d.current=!0,h(),()=>{var c;d.current=!1,(c=u.current)==null||c.abort()}),[h]),X(()=>{const c=J.value;if(c<=0||!e)return;const f=setInterval(h,c);return()=>clearInterval(f)},[h,e,J.value]),{data:n,loading:a,error:i,refetch:h}}function j(e){if(!e)return"—";const t=new Date(e);return t.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit"})+" "+t.toLocaleDateString(void 0,{month:"short",day:"numeric"})}function vn(e){return e.toLocaleString()}function G(e,t=8){return e.length>t?e.slice(0,t):e}const yn=[{header:"Task",accessor:e=>r("span",{class:"font-medium",children:e.task_name})},{header:"State",accessor:e=>r(W,{status:e.state})},{header:"Failures",accessor:e=>r("span",{class:e.failure_count>0?"text-danger tabular-nums":"tabular-nums",children:e.failure_count})},{header:"Threshold",accessor:e=>r("span",{class:"tabular-nums",children:e.threshold})},{header:"Window",accessor:e=>`${(e.window_ms/1e3).toFixed(0)}s`},{header:"Cooldown",accessor:e=>`${(e.cooldown_ms/1e3).toFixed(0)}s`},{header:"Last Failure",accessor:e=>r("span",{class:"text-muted",children:j(e.last_failure_at)})}];function kn(e){const{data:t,loading:n}=S("/api/circuit-breakers");return n&&!t?r(N,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Gt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Circuit Breakers"}),r("p",{class:"text-xs text-muted",children:"Automatic failure protection status"})]})]}),t!=null&&t.length?r(R,{columns:yn,data:t}):r(U,{message:"No circuit breakers configured",subtitle:"Circuit breakers activate when tasks fail repeatedly"})]})}const wn={primary:"bg-accent text-white shadow-sm shadow-accent/20 hover:bg-accent/90 hover:shadow-md hover:shadow-accent/25 active:scale-[0.98]",danger:"bg-danger text-white shadow-sm shadow-danger/20 hover:bg-danger/90 hover:shadow-md hover:shadow-danger/25 active:scale-[0.98]",ghost:"dark:text-gray-400 text-slate-500 hover:dark:bg-surface-3 hover:bg-slate-100 hover:dark:text-gray-200 hover:text-slate-700 active:scale-[0.98]"};function L({onClick:e,variant:t="primary",disabled:n,children:s,class:a=""}){return r("button",{type:"button",onClick:e,disabled:n,class:`inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-[13px] font-medium cursor-pointer transition-all duration-150 disabled:opacity-40 disabled:cursor-default disabled:shadow-none border-none ${wn[t]} ${a}`,children:s})}function nr({message:e,onConfirm:t,onCancel:n}){return r("div",{class:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in",onClick:n,children:r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-2xl dark:shadow-black/50 p-6 max-w-sm mx-4 border dark:border-white/[0.08] border-slate-200 animate-slide-in",onClick:s=>s.stopPropagation(),children:[r("div",{class:"flex items-start gap-3 mb-5",children:[r("div",{class:"p-2 rounded-lg bg-danger-dim shrink-0",children:r(Qr,{class:"w-5 h-5 text-danger",strokeWidth:2})}),r("p",{class:"text-sm dark:text-gray-200 text-slate-700 pt-1",children:e})]}),r("div",{class:"flex justify-end gap-2.5",children:[r(L,{variant:"ghost",onClick:n,children:"Cancel"}),r(L,{variant:"danger",onClick:t,children:"Confirm"})]})]})})}function sr({page:e,pageSize:t,itemCount:n,onPageChange:s}){return r("div",{class:"flex items-center justify-between px-4 py-3 text-[13px] text-muted border-t dark:border-white/[0.04] border-slate-100",children:[r("span",{children:["Showing ",e*t+1,"\\u2013",e*t+n," items"]}),r("div",{class:"flex gap-1.5",children:[r("button",{type:"button",onClick:()=>s(e-1),disabled:e===0,class:"inline-flex items-center gap-1 px-3 py-1.5 rounded-lg dark:bg-surface-3 bg-slate-100 dark:text-gray-300 text-slate-600 border dark:border-white/[0.06] border-slate-200 disabled:opacity-30 cursor-pointer disabled:cursor-default hover:enabled:dark:bg-surface-4 hover:enabled:bg-slate-200 transition-all duration-150 text-[13px]",children:[r(Ur,{class:"w-3.5 h-3.5"}),"Prev"]}),r("button",{type:"button",onClick:()=>s(e+1),disabled:n({error:n,items:s})).sort((n,s)=>s.items.length-n.items.length)}function Sn(e){const[t,n]=M(0),[s,a]=M(!1),[o,i]=M(!1),[l,u]=M(new Set),{data:d,loading:h,refetch:c}=S(`/api/dead-letters?limit=${o?200:Ne}&offset=${o?0:t*Ne}`,[t,o]),f=async _=>{try{await q(`/api/dead-letters/${_}/retry`),P("Dead letter retried","success"),c()}catch{P("Failed to retry dead letter","error")}},p=async _=>{let y=0;for(const E of _.items)try{await q(`/api/dead-letters/${E.id}/retry`),y++}catch{}P(`Retried ${y} of ${_.items.length} dead letters`,y>0?"success":"error"),c()},m=async()=>{a(!1);try{const _=await q("/api/dead-letters/purge");P(`Purged ${_.purged} dead letters`,"success"),c()}catch{P("Failed to purge dead letters","error")}},b=_=>{const y=new Set(l);y.has(_)?y.delete(_):y.add(_),u(y)},x=[{header:"ID",accessor:_=>r("span",{class:"font-mono text-xs text-accent-light",children:G(_.id)})},{header:"Original Job",accessor:_=>r("a",{href:`/jobs/${_.original_job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:G(_.original_job_id)})},{header:"Task",accessor:_=>r("span",{class:"font-medium",children:_.task_name})},{header:"Queue",accessor:"queue"},{header:"Error",accessor:_=>r("span",{class:"text-danger text-xs",title:_.error??"",children:_.error?_.error.length>50?`${_.error.slice(0,50)}…`:_.error:"—"}),className:"max-w-[250px]"},{header:"Retries",accessor:_=>r("span",{class:"text-warning tabular-nums",children:_.retry_count})},{header:"Failed At",accessor:_=>r("span",{class:"text-muted",children:j(_.failed_at)})},{header:"Actions",accessor:_=>r(L,{onClick:()=>f(_.id),children:[r(Se,{class:"w-3.5 h-3.5"}),"Retry"]})}];if(h&&!d)return r(N,{});const g=o&&d?$n(d):[];return r("div",{children:[r("div",{class:"flex items-center justify-between mb-6",children:[r("div",{class:"flex items-center gap-3",children:[r("div",{class:"p-2 rounded-lg bg-danger-dim",children:r(Qe,{class:"w-5 h-5 text-danger",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Dead Letters"}),r("p",{class:"text-xs text-muted",children:"Failed jobs that exhausted all retries"})]})]}),r("div",{class:"flex items-center gap-2",children:d&&d.length>0&&r(D,{children:[r("div",{class:"flex dark:bg-surface-3 bg-slate-100 rounded-lg p-1",children:[r("button",{type:"button",onClick:()=>{i(!1),n(0)},class:`inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md border-none cursor-pointer transition-all duration-150 ${o?"bg-transparent dark:text-gray-400 text-slate-500 hover:dark:text-white":"bg-accent text-white shadow-sm shadow-accent/20"}`,children:[r(Fr,{class:"w-3.5 h-3.5"}),"List"]}),r("button",{type:"button",onClick:()=>i(!0),class:`inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md border-none cursor-pointer transition-all duration-150 ${o?"bg-accent text-white shadow-sm shadow-accent/20":"bg-transparent dark:text-gray-400 text-slate-500 hover:dark:text-white"}`,children:[r(Or,{class:"w-3.5 h-3.5"}),"Group"]})]}),r(L,{variant:"danger",onClick:()=>a(!0),children:[r(Gr,{class:"w-3.5 h-3.5"}),"Purge All"]})]})})]}),d!=null&&d.length?o?r("div",{class:"space-y-3",children:g.map(_=>{const y=l.has(_.error);return r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 border dark:border-white/[0.06] border-slate-200 overflow-hidden",children:[r("div",{class:"flex items-center gap-3 px-5 py-4 cursor-pointer hover:dark:bg-white/[0.02] hover:bg-slate-50 transition-colors",onClick:()=>b(_.error),children:[y?r(Er,{class:"w-4 h-4 text-muted shrink-0"}):r(It,{class:"w-4 h-4 text-muted shrink-0"}),r("div",{class:"flex-1 min-w-0",children:r("span",{class:"text-danger text-sm font-mono truncate block",children:_.error})}),r("span",{class:"shrink-0 px-2.5 py-0.5 rounded-full text-xs font-semibold tabular-nums bg-danger/10 text-danger border border-danger/20",children:_.items.length}),r(L,{onClick:()=>{p(_)},children:[r(Se,{class:"w-3.5 h-3.5"}),"Retry All"]})]}),y&&r("div",{class:"border-t dark:border-white/[0.04] border-slate-100",children:r(R,{columns:x,data:_.items})})]},_.error)})}):r(R,{columns:x,data:d,children:r(sr,{page:t,pageSize:Ne,itemCount:d.length,onPageChange:n})}):r(U,{message:"No dead letters",subtitle:"All jobs are processing normally"}),s&&r(nr,{message:"Purge all dead letters? This cannot be undone.",onConfirm:m,onCancel:()=>a(!1)})]})}const Cn={pending:"#ffa726",running:"#42a5f5",complete:"#66bb6a",failed:"#ef5350",dead:"#ef5350",cancelled:"#a0a0b0"};function Mn({dag:e}){if(!e.nodes||e.nodes.length<=1)return null;const t=160,n=36,s=40,a=20,o={},i={};e.nodes.forEach(m=>{o[m.id]=[],i[m.id]=0}),e.edges.forEach(m=>{o[m.from]||(o[m.from]=[]),o[m.from].push(m.to),i[m.to]=(i[m.to]||0)+1});const l=[],u=new Set;let d=e.nodes.filter(m=>(i[m.id]||0)===0).map(m=>m.id);for(;d.length;){l.push([...d]);for(const b of d)u.add(b);const m=[];d.forEach(b=>{(o[b]||[]).forEach(x=>{!u.has(x)&&!m.includes(x)&&m.push(x)})}),d=m}e.nodes.forEach(m=>{u.has(m.id)||(l.push([m.id]),u.add(m.id))});const h={};for(const m of e.nodes)h[m.id]=m;const c={};let f=0,p=0;return l.forEach((m,b)=>{m.forEach((x,g)=>{const _=20+b*(t+s),y=20+g*(n+a);c[x]={x:_,y},f=Math.max(f,_+t+20),p=Math.max(p,y+n+20)})}),r("div",{class:"mt-4",children:[r("h3",{class:"text-sm text-muted mb-2",children:"Dependency Graph"}),r("div",{class:"dark:bg-surface-2 bg-white rounded-lg shadow-sm dark:shadow-black/30 p-4 overflow-x-auto border border-transparent dark:border-white/5",children:r("svg",{width:f,height:p,role:"img","aria-label":"Job dependency graph",children:[r("title",{children:"Job dependency graph"}),r("defs",{children:r("marker",{id:"arrow",viewBox:"0 0 10 10",refX:"10",refY:"5",markerWidth:"8",markerHeight:"8",orient:"auto",children:r("path",{d:"M0,0 L10,5 L0,10 z",fill:"#a0a0b0"})})}),e.edges.map((m,b)=>{const x=c[m.from],g=c[m.to];return!x||!g?null:r("line",{x1:x.x+t,y1:x.y+n/2,x2:g.x,y2:g.y+n/2,stroke:"#a0a0b0","stroke-width":"1.5",fill:"none","marker-end":"url(#arrow)"},b)}),e.nodes.map(m=>{const b=c[m.id];if(!b)return null;const x=Cn[m.status]||"#a0a0b0";return r("g",{class:"cursor-pointer",onClick:()=>te(`/jobs/${m.id}`),children:[r("rect",{x:b.x,y:b.y,width:t,height:n,fill:`${x}22`,stroke:x,"stroke-width":"1.5",rx:"6",ry:"6"}),r("text",{x:b.x+8,y:b.y+14,fill:x,"font-size":"10","font-weight":"600",children:m.status.toUpperCase()}),r("text",{x:b.x+8,y:b.y+28,"font-size":"10",fill:"#a0a0b0",children:m.task_name.length>18?m.task_name.slice(-18):m.task_name})]},m.id)})]})})]})}function Xe({progress:e}){return e==null?r("span",{class:"text-muted text-xs",children:"—"}):r("span",{class:"inline-flex items-center gap-2",children:[r("span",{class:"inline-block w-16 h-1.5 rounded-full dark:bg-white/[0.08] bg-slate-200 overflow-hidden",children:r("span",{class:"block h-full rounded-full bg-gradient-to-r from-accent to-accent-light transition-[width] duration-300",style:{width:`${e}%`}})}),r("span",{class:"text-xs tabular-nums text-muted",children:[e,"%"]})]})}const Tn=[{header:"Attempt",accessor:"attempt"},{header:"Error",accessor:"error",className:"max-w-xs truncate"},{header:"Failed At",accessor:e=>r("span",{class:"text-muted",children:j(e.failed_at)})}],Pn=[{header:"Time",accessor:e=>r("span",{class:"text-muted",children:j(e.logged_at)})},{header:"Level",accessor:e=>r(W,{status:e.level==="error"?"failed":e.level==="warning"?"pending":"complete"})},{header:"Message",accessor:"message"},{header:"Extra",accessor:e=>e.extra??"—",className:"max-w-[200px] truncate"}],jn=[{header:"Replay Job",accessor:e=>r("a",{href:`/jobs/${e.replay_job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:G(e.replay_job_id)})},{header:"Replayed At",accessor:e=>r("span",{class:"text-muted",children:j(e.replayed_at)})},{header:"Original Error",accessor:e=>e.original_error??"—",className:"max-w-[200px] truncate"},{header:"Replay Error",accessor:e=>e.replay_error??"—",className:"max-w-[200px] truncate"}];function Rn({id:e}){const{data:t,loading:n,refetch:s}=S(`/api/jobs/${e}`),{data:a}=S(`/api/jobs/${e}/errors`),{data:o}=S(`/api/jobs/${e}/logs`),{data:i}=S(`/api/jobs/${e}/replay-history`),{data:l}=S(`/api/jobs/${e}/dag`);if(n&&!t)return r(N,{});if(!t)return r(U,{message:`Job not found: ${e}`});const u=async()=>{try{const c=await q(`/api/jobs/${e}/cancel`);P(c.cancelled?"Job cancelled":"Failed to cancel job",c.cancelled?"success":"error"),s()}catch{P("Failed to cancel job","error")}},d=async()=>{try{const c=await q(`/api/jobs/${e}/replay`);P("Job replayed","success"),te(`/jobs/${c.replay_job_id}`)}catch{P("Failed to replay job","error")}},h={pending:"border-t-warning",running:"border-t-info",complete:"border-t-success",failed:"border-t-danger",dead:"border-t-danger",cancelled:"border-t-muted"};return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ir,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:["Job ",r("span",{class:"font-mono text-accent-light",children:G(t.id)})]}),r("p",{class:"text-xs text-muted",children:t.task_name})]})]}),r("div",{class:`dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-6 border dark:border-white/[0.06] border-slate-200 border-t-[3px] ${h[t.status]??"border-t-muted"}`,children:[r("div",{class:"grid grid-cols-[140px_1fr] gap-x-6 gap-y-3 text-[13px]",children:[r("span",{class:"text-muted font-medium",children:"ID"}),r("span",{class:"font-mono text-xs break-all dark:text-gray-300 text-slate-600",children:t.id}),r("span",{class:"text-muted font-medium",children:"Status"}),r("span",{children:r(W,{status:t.status})}),r("span",{class:"text-muted font-medium",children:"Task"}),r("span",{class:"font-medium",children:t.task_name}),r("span",{class:"text-muted font-medium",children:"Queue"}),r("span",{children:t.queue}),r("span",{class:"text-muted font-medium",children:"Priority"}),r("span",{children:t.priority}),r("span",{class:"text-muted font-medium",children:"Progress"}),r("span",{children:r(Xe,{progress:t.progress})}),r("span",{class:"text-muted font-medium",children:"Retries"}),r("span",{class:t.retry_count>0?"text-warning":"",children:[t.retry_count," / ",t.max_retries]}),r("span",{class:"text-muted font-medium",children:"Created"}),r("span",{class:"text-muted",children:j(t.created_at)}),r("span",{class:"text-muted font-medium",children:"Scheduled"}),r("span",{class:"text-muted",children:j(t.scheduled_at)}),r("span",{class:"text-muted font-medium",children:"Started"}),r("span",{class:"text-muted",children:t.started_at?j(t.started_at):"—"}),r("span",{class:"text-muted font-medium",children:"Completed"}),r("span",{class:"text-muted",children:t.completed_at?j(t.completed_at):"—"}),r("span",{class:"text-muted font-medium",children:"Timeout"}),r("span",{children:[(t.timeout_ms/1e3).toFixed(0),"s"]}),t.error&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Error"}),r("span",{class:"text-danger text-xs font-mono bg-danger-dim rounded px-2 py-1",children:t.error})]}),t.unique_key&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Unique Key"}),r("span",{class:"font-mono text-xs",children:t.unique_key})]}),t.metadata&&r(D,{children:[r("span",{class:"text-muted font-medium",children:"Metadata"}),r("span",{class:"font-mono text-xs",children:t.metadata})]})]}),r("div",{class:"flex gap-2.5 mt-5 pt-5 border-t dark:border-white/[0.06] border-slate-100",children:[t.status==="pending"&&r(L,{variant:"danger",onClick:u,children:"Cancel Job"}),r(L,{onClick:d,children:[r(Se,{class:"w-3.5 h-3.5"}),"Replay"]})]})]}),a&&a.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Error History ",r("span",{class:"text-muted font-normal",children:["(",a.length,")"]})]}),r(R,{columns:Tn,data:a})]}),o&&o.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Task Logs ",r("span",{class:"text-muted font-normal",children:["(",o.length,")"]})]}),r(R,{columns:Pn,data:o})]}),i&&i.length>0&&r("div",{class:"mt-6",children:[r("h3",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:["Replay History ",r("span",{class:"text-muted font-normal",children:["(",i.length,")"]})]}),r(R,{columns:jn,data:i})]}),l&&r(Mn,{dag:l}),r("div",{class:"mt-6",children:r("a",{href:"/jobs",class:"text-accent-light text-[13px] hover:underline",children:["←"," Back to jobs"]})})]})}const An={pending:{color:"text-warning",bg:"bg-warning-dim",border:"border-l-warning",icon:$e},running:{color:"text-info",bg:"bg-info-dim",border:"border-l-info",icon:Ht},completed:{color:"text-success",bg:"bg-success-dim",border:"border-l-success",icon:Ot},failed:{color:"text-danger",bg:"bg-danger-dim",border:"border-l-danger",icon:qt},dead:{color:"text-danger",bg:"bg-danger-dim",border:"border-l-danger",icon:Qe},cancelled:{color:"text-muted",bg:"bg-muted/10",border:"border-l-muted/40",icon:Nt}};function Ln({label:e,value:t,color:n}){const s=An[e],a=n??(s==null?void 0:s.color)??"text-accent-light",o=(s==null?void 0:s.bg)??"bg-accent-dim",i=(s==null?void 0:s.border)??"border-l-accent",l=(s==null?void 0:s.icon)??$e;return r("div",{class:`dark:bg-surface-2 bg-white rounded-xl p-5 shadow-sm dark:shadow-black/20 border-l-[3px] ${i} dark:border-t dark:border-r dark:border-b dark:border-white/[0.04] border border-slate-100 transition-all duration-150 hover:shadow-md hover:dark:shadow-black/30`,children:r("div",{class:"flex items-start justify-between",children:[r("div",{children:[r("div",{class:`text-3xl font-bold tabular-nums tracking-tight ${a}`,children:vn(t)}),r("div",{class:"text-xs text-muted uppercase mt-1.5 tracking-wider font-medium",children:e})]}),r("div",{class:`p-2 rounded-lg ${o}`,children:r(l,{class:`w-5 h-5 ${a}`,strokeWidth:1.8})})]})})}const Nn=["pending","running","completed","failed","dead","cancelled"];function ar({stats:e}){return r("div",{class:"grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-4 mb-8",children:Nn.map(t=>r(Ln,{label:t,value:e[t]??0},t))})}const ze=20,En=[{header:"ID",accessor:e=>r("span",{class:"font-mono text-xs text-accent-light",children:G(e.id)})},{header:"Task",accessor:"task_name"},{header:"Queue",accessor:"queue"},{header:"Status",accessor:e=>r(W,{status:e.status})},{header:"Priority",accessor:"priority"},{header:"Progress",accessor:e=>r(Xe,{progress:e.progress})},{header:"Retries",accessor:e=>r("span",{class:e.retry_count>0?"text-warning":"text-muted",children:[e.retry_count,"/",e.max_retries]})},{header:"Created",accessor:e=>r("span",{class:"text-muted",children:j(e.created_at)})}];function Un(e,t){const n=new URLSearchParams;return n.set("limit",String(ze)),n.set("offset",String(t*ze)),e.status&&n.set("status",e.status),e.queue&&n.set("queue",e.queue),e.task&&n.set("task",e.task),e.metadata&&n.set("metadata",e.metadata),e.error&&n.set("error",e.error),e.created_after&&n.set("created_after",String(new Date(e.created_after).getTime())),e.created_before&&n.set("created_before",String(new Date(e.created_before).getTime())),`/api/jobs?${n}`}function In(e){const[t,n]=M({status:"",queue:"",task:"",metadata:"",error:"",created_after:"",created_before:""}),[s,a]=M(0),[o,i]=M(new Set),[l,u]=M(!1),{data:d}=S("/api/stats"),{data:h,loading:c,refetch:f}=S(Un(t,s),[t.status,t.queue,t.task,t.metadata,t.error,t.created_after,t.created_before,s]),p=(g,_)=>{n(y=>({...y,[g]:_})),a(0),i(new Set)},m=async()=>{u(!1);let g=0;for(const _ of o)try{(await q(`/api/jobs/${_}/cancel`)).cancelled&&g++}catch{}P(`Cancelled ${g} of ${o.size} jobs`,g>0?"success":"error"),i(new Set),f()},b=async()=>{let g=0;for(const _ of o)try{await q(`/api/jobs/${_}/replay`),g++}catch{}P(`Replayed ${g} of ${o.size} jobs`,g>0?"success":"error"),i(new Set),f()},x="dark:bg-surface-3 bg-white dark:text-gray-200 text-slate-700 border dark:border-white/[0.06] border-slate-200 rounded-lg px-3 py-2 text-[13px] placeholder:text-muted/50 focus:border-accent/50 transition-colors";return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(zt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Jobs"}),r("p",{class:"text-xs text-muted",children:"Browse and filter task queue jobs"})]})]}),d&&r(ar,{stats:d}),r("div",{class:"dark:bg-surface-2 bg-white rounded-xl p-4 mb-4 border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"flex items-center gap-2 mb-3 text-xs text-muted font-medium uppercase tracking-wider",children:[r(Jt,{class:"w-3.5 h-3.5"}),"Filters"]}),r("div",{class:"grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-2.5",children:[r("select",{class:x,value:t.status,onChange:g=>p("status",g.target.value),children:[r("option",{value:"",children:"All statuses"}),r("option",{value:"pending",children:"Pending"}),r("option",{value:"running",children:"Running"}),r("option",{value:"complete",children:"Complete"}),r("option",{value:"failed",children:"Failed"}),r("option",{value:"dead",children:"Dead"}),r("option",{value:"cancelled",children:"Cancelled"})]}),r("input",{class:x,placeholder:"Queue\\u2026",value:t.queue,onInput:g=>p("queue",g.target.value)}),r("input",{class:x,placeholder:"Task\\u2026",value:t.task,onInput:g=>p("task",g.target.value)}),r("input",{class:x,placeholder:"Metadata\\u2026",value:t.metadata,onInput:g=>p("metadata",g.target.value)}),r("input",{class:x,placeholder:"Error text\\u2026",value:t.error,onInput:g=>p("error",g.target.value)}),r("input",{class:x,type:"date",title:"Created after",value:t.created_after,onInput:g=>p("created_after",g.target.value)}),r("input",{class:x,type:"date",title:"Created before",value:t.created_before,onInput:g=>p("created_before",g.target.value)})]})]}),o.size>0&&r("div",{class:"flex items-center gap-3 mb-4 px-4 py-3 rounded-xl dark:bg-accent/[0.08] bg-accent/[0.04] border dark:border-accent/20 border-accent/10",children:[r("span",{class:"text-sm font-medium dark:text-gray-200 text-slate-700",children:[o.size," job",o.size>1?"s":""," selected"]}),r("div",{class:"flex gap-2 ml-auto",children:[r(L,{variant:"danger",onClick:()=>u(!0),children:[r(Nt,{class:"w-3.5 h-3.5"}),"Cancel Selected"]}),r(L,{onClick:b,children:[r(Se,{class:"w-3.5 h-3.5"}),"Replay Selected"]}),r(L,{variant:"ghost",onClick:()=>i(new Set),children:[r(Vt,{class:"w-3.5 h-3.5"}),"Clear"]})]})]}),c&&!h?r(N,{}):h!=null&&h.length?r(R,{columns:En,data:h,onRowClick:g=>te(`/jobs/${g.id}`),selectable:!0,selectedKeys:o,rowKey:g=>g.id,onSelectionChange:i,children:r(sr,{page:s,pageSize:ze,itemCount:h.length,onPageChange:a})}):r(U,{message:"No jobs found",subtitle:"Try adjusting your filters"}),l&&r(nr,{message:`Cancel ${o.size} selected job${o.size>1?"s":""}? Only pending jobs can be cancelled.`,onConfirm:m,onCancel:()=>u(!1)})]})}const On=[{header:"Time",accessor:e=>r("span",{class:"text-muted",children:j(e.logged_at)})},{header:"Level",accessor:e=>r(W,{status:e.level==="error"?"failed":e.level==="warning"?"pending":"complete"})},{header:"Task",accessor:e=>r("span",{class:"font-medium",children:e.task_name})},{header:"Job",accessor:e=>r("a",{href:`/jobs/${e.job_id}`,class:"font-mono text-xs text-accent-light hover:underline",children:G(e.job_id)})},{header:"Message",accessor:"message"},{header:"Extra",accessor:e=>e.extra??"—",className:"max-w-[200px] truncate"}];function qn(e){const[t,n]=M(""),[s,a]=M(""),o=new URLSearchParams({limit:"100"});t&&o.set("task",t),s&&o.set("level",s);const{data:i,loading:l}=S(`/api/logs?${o}`,[t,s]),u="dark:bg-surface-3 bg-white dark:text-gray-200 text-slate-700 border dark:border-white/[0.06] border-slate-200 rounded-lg px-3 py-2 text-[13px] placeholder:text-muted/50 focus:border-accent/50 transition-colors";return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Bt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Logs"}),r("p",{class:"text-xs text-muted",children:"Structured task execution logs"})]})]}),r("div",{class:"flex gap-2.5 mb-5",children:[r("input",{class:`${u} w-44`,placeholder:"Filter by task\\u2026",value:t,onInput:d=>n(d.target.value)}),r("select",{class:u,value:s,onChange:d=>a(d.target.value),children:[r("option",{value:"",children:"All levels"}),r("option",{value:"error",children:"Error"}),r("option",{value:"warning",children:"Warning"}),r("option",{value:"info",children:"Info"}),r("option",{value:"debug",children:"Debug"})]})]}),l&&!i?r(N,{}):i!=null&&i.length?r(R,{columns:On,data:i}):r(U,{message:"No logs yet",subtitle:"Logs appear when tasks execute"})]})}function Wn({data:e}){const t=K(null);return X(()=>{const n=t.current;if(!n)return;const s=n.getContext("2d");if(!s)return;const a=window.devicePixelRatio||1,o=n.getBoundingClientRect();n.width=o.width*a,n.height=o.height*a,s.scale(a,a);const i=o.width,l=o.height;if(s.clearRect(0,0,i,l),!e.length){s.fillStyle="rgba(139,149,165,0.4)",s.font="12px -apple-system, sans-serif",s.textAlign="center",s.fillText("No timeseries data",i/2,l/2);return}const u={top:12,right:12,bottom:32,left:48},d=i-u.left-u.right,h=l-u.top-u.bottom,c=Math.max(...e.map(b=>b.success+b.failure),1),f=Math.max(3,d/e.length-2),p=Math.max(1,(d-f*e.length)/e.length);for(let b=0;b<=4;b++){const x=u.top+h*(1-b/4);s.strokeStyle="rgba(255,255,255,0.04)",s.lineWidth=1,s.beginPath(),s.moveTo(u.left,x),s.lineTo(i-u.right,x),s.stroke(),s.fillStyle="rgba(139,149,165,0.5)",s.font="10px -apple-system, sans-serif",s.textAlign="right",s.fillText(Math.round(c*b/4).toString(),u.left-6,x+3)}e.forEach((b,x)=>{const g=u.left+x*(f+p),_=b.success/c*h,y=b.failure/c*h;s.fillStyle="rgba(34,197,94,0.65)",s.beginPath();const E=u.top+h-_-y;s.roundRect(g,E,f,_,[2,2,0,0]),s.fill(),y>0&&(s.fillStyle="rgba(239,68,68,0.65)",s.beginPath(),s.roundRect(g,u.top+h-y,f,y,[0,0,2,2]),s.fill())}),s.fillStyle="rgba(139,149,165,0.5)",s.font="10px -apple-system, sans-serif",s.textAlign="center";const m=Math.min(6,e.length);for(let b=0;br("span",{class:"font-medium",children:e.task_name})},{header:"Total",accessor:e=>r("span",{class:"tabular-nums",children:e.count})},{header:"Success",accessor:e=>r("span",{class:"text-success tabular-nums",children:e.success_count})},{header:"Failures",accessor:e=>r("span",{class:e.failure_count>0?"text-danger tabular-nums":"text-muted tabular-nums",children:e.failure_count})},{header:"Avg",accessor:e=>r("span",{class:`tabular-nums ${xe(e.avg_ms,{good:100,warn:500})}`,children:[e.avg_ms,"ms"]})},{header:"P50",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.p50_ms,"ms"]})},{header:"P95",accessor:e=>r("span",{class:`tabular-nums ${xe(e.p95_ms,{good:200,warn:1e3})}`,children:[e.p95_ms,"ms"]})},{header:"P99",accessor:e=>r("span",{class:`tabular-nums ${xe(e.p99_ms,{good:500,warn:2e3})}`,children:[e.p99_ms,"ms"]})},{header:"Min",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.min_ms,"ms"]})},{header:"Max",accessor:e=>r("span",{class:`tabular-nums ${xe(e.max_ms,{good:1e3,warn:5e3})}`,children:[e.max_ms,"ms"]})}],Dn=[{label:"1h",seconds:3600},{label:"6h",seconds:21600},{label:"24h",seconds:86400}];function zn(e){const[t,n]=M(3600),{data:s,loading:a}=S(`/api/metrics?since=${t}`,[t]),{data:o}=S(`/api/metrics/timeseries?since=${t}&bucket=${t<=3600?60:t<=21600?300:900}`,[t]),i=s?Object.entries(s).map(([l,u])=>({task_name:l,...u})):[];return r("div",{children:[r("div",{class:"flex items-center justify-between mb-6",children:[r("div",{class:"flex items-center gap-3",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ut,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Metrics"}),r("p",{class:"text-xs text-muted",children:"Task performance and throughput"})]})]}),r("div",{class:"flex gap-1 dark:bg-surface-3 bg-slate-100 rounded-lg p-1",children:Dn.map(l=>r("button",{type:"button",onClick:()=>n(l.seconds),class:`px-3 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-all duration-150 ${t===l.seconds?"bg-accent text-white shadow-sm shadow-accent/20":"bg-transparent dark:text-gray-400 text-slate-500 hover:dark:text-white hover:text-slate-900"}`,children:l.label},l.label))})]}),o&&o.length>0&&r(Wn,{data:o}),a&&!s?r(N,{}):i.length?r(R,{columns:Fn,data:i}):r(U,{message:"No metrics yet",subtitle:"Run some tasks to see performance data"})]})}function Hn({data:e}){const t=K(null);X(()=>{const s=t.current;if(!s)return;const a=s.getContext("2d");if(!a)return;const o=window.devicePixelRatio||1,i=s.getBoundingClientRect();s.width=i.width*o,s.height=i.height*o,a.scale(o,o);const l=i.width,u=i.height;if(a.clearRect(0,0,l,u),e.length<2){a.fillStyle="rgba(139,149,165,0.4)",a.font="12px -apple-system, sans-serif",a.textAlign="center",a.fillText("Collecting data…",l/2,u/2);return}const d=Math.max(...e,1),h={top:12,right:12,bottom:24,left:44},c=l-h.left-h.right,f=u-h.top-h.bottom;for(let m=0;m<=4;m++){const b=h.top+f*(1-m/4);a.strokeStyle="rgba(255,255,255,0.04)",a.lineWidth=1,a.beginPath(),a.moveTo(h.left,b),a.lineTo(l-h.right,b),a.stroke(),a.fillStyle="rgba(139,149,165,0.5)",a.font="10px -apple-system, sans-serif",a.textAlign="right",a.fillText((d*m/4).toFixed(1),h.left-6,b+3)}const p=a.createLinearGradient(0,h.top,0,h.top+f);if(p.addColorStop(0,"rgba(34,197,94,0.2)"),p.addColorStop(1,"rgba(34,197,94,0.01)"),a.beginPath(),a.moveTo(h.left,h.top+f),e.forEach((m,b)=>{const x=h.left+b/(e.length-1)*c,g=h.top+f*(1-m/d);a.lineTo(x,g)}),a.lineTo(h.left+c,h.top+f),a.closePath(),a.fillStyle=p,a.fill(),a.beginPath(),e.forEach((m,b)=>{const x=h.left+b/(e.length-1)*c,g=h.top+f*(1-m/d);b===0?a.moveTo(x,g):a.lineTo(x,g)}),a.strokeStyle="#22c55e",a.lineWidth=2,a.lineJoin="round",a.stroke(),e.length>0){const m=h.left+c,b=h.top+f*(1-e[e.length-1]/d);a.beginPath(),a.arc(m,b,3,0,Math.PI*2),a.fillStyle="#22c55e",a.fill(),a.beginPath(),a.arc(m,b,5,0,Math.PI*2),a.strokeStyle="rgba(34,197,94,0.3)",a.lineWidth=2,a.stroke()}},[e]);const n=e.length>0?e[e.length-1]:0;return r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-5 mb-6 border dark:border-white/[0.06] border-slate-200",children:[r("div",{class:"flex items-center justify-between mb-4",children:[r("div",{class:"flex items-center gap-2",children:[r(Vr,{class:"w-4 h-4 text-success",strokeWidth:2}),r("h3",{class:"text-sm font-medium dark:text-gray-300 text-slate-600",children:"Throughput"})]}),r("span",{class:"text-xl font-bold tabular-nums text-success",children:[n.toFixed(1)," ",r("span",{class:"text-xs font-normal text-muted",children:"jobs/s"})]})]}),r("canvas",{ref:t,class:"w-full",style:{height:"180px"}})]})}const Bn=[{header:"ID",accessor:e=>r("span",{class:"font-mono text-xs text-accent-light",children:G(e.id)})},{header:"Task",accessor:"task_name"},{header:"Queue",accessor:"queue"},{header:"Status",accessor:e=>r(W,{status:e.status})},{header:"Progress",accessor:e=>r(Xe,{progress:e.progress})},{header:"Created",accessor:e=>r("span",{class:"text-muted",children:j(e.created_at)})}];function Jn(e){const{data:t,loading:n}=S("/api/stats"),{data:s}=S("/api/jobs?limit=10"),a=K(0),o=K([]);if(t){const i=t.completed||0,l=J.value||5e3;let u=0;a.current>0&&(u=parseFloat(((i-a.current)/(l/1e3)).toFixed(1))),a.current=i,o.current=[...o.current.slice(-59),u]}return n&&!t?r(N,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Ft,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Overview"}),r("p",{class:"text-xs text-muted",children:"Real-time queue status"})]})]}),t&&r(ar,{stats:t}),r(Hn,{data:o.current}),r("div",{class:"flex items-center gap-2 mb-4 mt-8",children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700",children:"Recent Jobs"}),r("span",{class:"text-xs text-muted",children:"(latest 10)"})]}),s!=null&&s.length?r(R,{columns:Bn,data:s,onRowClick:i=>te(`/jobs/${i.id}`)}):null]})}function Gn(e){const{data:t,loading:n,refetch:s}=S("/api/stats/queues"),{data:a,refetch:o}=S("/api/queues/paused"),i=new Set(a??[]),l=t?Object.entries(t).map(([c,f])=>({name:c,pending:f.pending??0,running:f.running??0,paused:i.has(c)})):[],u=async c=>{try{await q(`/api/queues/${encodeURIComponent(c)}/pause`),P(`Queue "${c}" paused`,"success"),s(),o()}catch{P(`Failed to pause queue "${c}"`,"error")}},d=async c=>{try{await q(`/api/queues/${encodeURIComponent(c)}/resume`),P(`Queue "${c}" resumed`,"success"),s(),o()}catch{P(`Failed to resume queue "${c}"`,"error")}},h=[{header:"Queue",accessor:c=>r("span",{class:"font-medium",children:c.name})},{header:"Pending",accessor:c=>r("span",{class:"text-warning tabular-nums font-medium",children:c.pending})},{header:"Running",accessor:c=>r("span",{class:"text-info tabular-nums font-medium",children:c.running})},{header:"Status",accessor:c=>r(W,{status:c.paused?"paused":"active"})},{header:"Actions",accessor:c=>c.paused?r(L,{onClick:()=>d(c.name),children:[r(Ht,{class:"w-3.5 h-3.5"}),"Resume"]}):r(L,{variant:"ghost",onClick:()=>u(c.name),children:[r(zr,{class:"w-3.5 h-3.5"}),"Pause"]})}];return n&&!t?r(N,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Dt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Queue Management"}),r("p",{class:"text-xs text-muted",children:"Monitor and control individual queues"})]})]}),l.length?r(R,{columns:h,data:l}):r(U,{message:"No queues found",subtitle:"Queues appear when tasks are enqueued"})]})}const Vn=[{header:"Name",accessor:e=>r("span",{class:"font-medium",children:e.name})},{header:"Scope",accessor:e=>r(W,{status:e.scope})},{header:"Health",accessor:e=>r(W,{status:e.health})},{header:"Init (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.init_duration_ms.toFixed(1),"ms"]})},{header:"Recreations",accessor:e=>r("span",{class:`tabular-nums ${e.recreations>0?"text-warning":"text-muted"}`,children:e.recreations})},{header:"Dependencies",accessor:e=>e.depends_on.length?r("span",{class:"text-xs",children:e.depends_on.join(", ")}):r("span",{class:"text-muted",children:"—"})},{header:"Pool",accessor:e=>e.pool?r("span",{class:"text-xs tabular-nums",children:[r("span",{class:"text-info",children:e.pool.active}),"/",e.pool.size," active,"," ",r("span",{class:"text-muted",children:[e.pool.idle," idle"]})]}):r("span",{class:"text-muted",children:"—"})}];function Qn(e){const{data:t,loading:n}=S("/api/resources");return n&&!t?r(N,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Et,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Resources"}),r("p",{class:"text-xs text-muted",children:"Worker dependency injection runtime"})]})]}),t!=null&&t.length?r(R,{columns:Vn,data:t}):r(U,{message:"No resources registered",subtitle:"Resources appear when workers start with DI configuration"})]})}const Yn=[{header:"Handler",accessor:e=>r("span",{class:"font-medium",children:e.handler})},{header:"Reconstructions",accessor:e=>r("span",{class:"tabular-nums",children:e.reconstructions})},{header:"Avg (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.avg_ms.toFixed(1),"ms"]})},{header:"Errors",accessor:e=>r("span",{class:`tabular-nums ${e.errors>0?"text-danger font-medium":"text-muted"}`,children:e.errors})}],Zn=[{header:"Strategy",accessor:e=>r("span",{class:"font-medium uppercase text-xs tracking-wide",children:e.strategy})},{header:"Count",accessor:e=>r("span",{class:"tabular-nums",children:e.count})},{header:"Avg (ms)",accessor:e=>r("span",{class:"tabular-nums text-muted",children:[e.avg_ms.toFixed(1),"ms"]})}];function Xn(e){const{data:t,loading:n}=S("/api/proxy-stats"),{data:s,loading:a}=S("/api/interception-stats"),o=t?Object.entries(t).map(([l,u])=>({handler:l,...u})):[],i=s?Object.entries(s).map(([l,u])=>({strategy:l,...u})):[];return r("div",{children:[r("div",{class:"flex items-center gap-3 mb-8",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Wt,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"System Internals"}),r("p",{class:"text-xs text-muted",children:"Proxy reconstruction and interception metrics"})]})]}),r("div",{class:"mb-8",children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:"Proxy Reconstruction"}),n&&!t?r(N,{}):o.length?r(R,{columns:Yn,data:o}):r(U,{message:"No proxy stats available",subtitle:"Stats appear when proxy handlers are used"})]}),r("div",{children:[r("h2",{class:"text-sm font-semibold dark:text-gray-200 text-slate-700 mb-3",children:"Interception"}),a&&!s?r(N,{}):i.length?r(R,{columns:Zn,data:i}):r(U,{message:"No interception stats available",subtitle:"Stats appear when argument interception is enabled"})]})]})}function Kn(e){const{data:t,loading:n}=S("/api/workers"),{data:s}=S("/api/stats");return n&&!t?r(N,{}):r("div",{children:[r("div",{class:"flex items-center gap-3 mb-6",children:[r("div",{class:"p-2 rounded-lg dark:bg-surface-3 bg-slate-100",children:r(Fe,{class:"w-5 h-5 text-accent",strokeWidth:1.8})}),r("div",{children:[r("h1",{class:"text-lg font-semibold dark:text-white text-slate-900",children:"Workers"}),r("p",{class:"text-xs text-muted",children:[(t==null?void 0:t.length)??0," active ","·"," ",(s==null?void 0:s.running)??0," running jobs"]})]})]}),t!=null&&t.length?r("div",{class:"grid grid-cols-[repeat(auto-fit,minmax(320px,1fr))] gap-4",children:t.map(a=>r("div",{class:"dark:bg-surface-2 bg-white rounded-xl shadow-sm dark:shadow-black/20 p-5 border dark:border-white/[0.06] border-slate-200 transition-all duration-150 hover:shadow-md hover:dark:shadow-black/30",children:[r("div",{class:"flex items-center gap-2 mb-3",children:[r("span",{class:"w-2 h-2 rounded-full bg-success shadow-sm shadow-success/40"}),r("span",{class:"font-mono text-xs text-accent-light font-medium",children:a.worker_id})]}),r("div",{class:"space-y-2 text-[13px]",children:[r("div",{class:"flex items-center gap-2 text-muted",children:[r(Fe,{class:"w-3.5 h-3.5"}),"Queues: ",r("span",{class:"dark:text-gray-200 text-slate-700",children:a.queues})]}),r("div",{class:"flex items-center gap-2 text-muted",children:[r($e,{class:"w-3.5 h-3.5"}),"Last heartbeat:"," ",r("span",{class:"dark:text-gray-200 text-slate-700",children:j(a.last_heartbeat)})]}),r("div",{class:"flex items-center gap-2 text-muted",children:[r($e,{class:"w-3.5 h-3.5"}),"Registered:"," ",r("span",{class:"dark:text-gray-200 text-slate-700",children:j(a.registered_at)})]}),a.tags&&r("div",{class:"flex items-center gap-2 text-muted",children:[r(Jr,{class:"w-3.5 h-3.5"}),"Tags: ",r("span",{class:"dark:text-gray-200 text-slate-700",children:a.tags})]})]})]},a.worker_id))}):r(U,{message:"No active workers",subtitle:"Workers will appear when they connect"})]})}function es(){return r(hn,{children:[r(Lt,{children:[r(Jn,{path:"/"}),r(In,{path:"/jobs"}),r(Rn,{path:"/jobs/:id"}),r(zn,{path:"/metrics"}),r(qn,{path:"/logs"}),r(Kn,{path:"/workers"}),r(kn,{path:"/circuit-breakers"}),r(Sn,{path:"/dead-letters"}),r(Qn,{path:"/resources"}),r(Gn,{path:"/queues"}),r(Xn,{path:"/system"})]}),r(_n,{})]})}hr(r(es,{}),document.getElementById("app"));
+
|