Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions dashboard/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export { ApiError, api, apiPost } from "./client";
export type {
CircuitBreaker,
DagData,
DagEdge,
DagNode,
DeadLetter,
InterceptionStats,
Job,
JobError,
JobStatus,
MetricsResponse,
ProxyStats,
QueueStats,
QueueStatsMap,
ReplayEntry,
ResourceStatus,
TaskLog,
TaskMetrics,
TimeseriesBucket,
Worker,
} from "./types";
4 changes: 2 additions & 2 deletions dashboard/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Router from "preact-router";
import { Shell } from "./components/layout/shell";
import { ToastContainer } from "./components/ui/toast";
import { Shell } from "./components/layout";
import { ToastContainer } from "./components/ui";
import { CircuitBreakers } from "./pages/circuit-breakers";
import { DeadLetters } from "./pages/dead-letters";
import { JobDetail } from "./pages/job-detail";
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/charts/dag-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { route } from "preact-router";
import type { DagData, DagNode, JobStatus } from "../api/types";
import type { DagData, DagNode, JobStatus } from "../api";

interface DagViewerProps {
dag: DagData;
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/charts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { DagViewer } from "./dag-viewer";
export { ThroughputChart } from "./throughput-chart";
export { TimeseriesChart } from "./timeseries-chart";
20 changes: 14 additions & 6 deletions dashboard/src/charts/throughput-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TrendingUp } from "lucide-preact";
import { useEffect, useRef } from "preact/hooks";
import { theme } from "../hooks";

interface ThroughputChartProps {
data: number[];
Expand All @@ -15,6 +16,13 @@ export function ThroughputChart({ data }: ThroughputChartProps) {
const ctx = canvas.getContext("2d");
if (!ctx) return;

const isDark = theme.value === "dark";
const gridColor = isDark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.06)";
const textColor = isDark ? "rgba(139,149,165,0.5)" : "rgba(100,116,139,0.7)";
const placeholderColor = isDark ? "rgba(139,149,165,0.4)" : "rgba(100,116,139,0.5)";
const areaTop = isDark ? "rgba(34,197,94,0.2)" : "rgba(34,197,94,0.12)";
const areaBottom = isDark ? "rgba(34,197,94,0.01)" : "rgba(34,197,94,0.02)";

const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
Expand All @@ -26,7 +34,7 @@ export function ThroughputChart({ data }: ThroughputChartProps) {
ctx.clearRect(0, 0, w, h);

if (data.length < 2) {
ctx.fillStyle = "rgba(139,149,165,0.4)";
ctx.fillStyle = placeholderColor;
ctx.font = "12px -apple-system, sans-serif";
ctx.textAlign = "center";
ctx.fillText("Collecting data\u2026", w / 2, h / 2);
Expand All @@ -41,22 +49,22 @@ export function ThroughputChart({ data }: ThroughputChartProps) {
// Grid lines
for (let i = 0; i <= 4; i++) {
const y = pad.top + ch * (1 - i / 4);
ctx.strokeStyle = "rgba(255,255,255,0.04)";
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad.left, y);
ctx.lineTo(w - pad.right, y);
ctx.stroke();
ctx.fillStyle = "rgba(139,149,165,0.5)";
ctx.fillStyle = textColor;
ctx.font = "10px -apple-system, sans-serif";
ctx.textAlign = "right";
ctx.fillText(((max * i) / 4).toFixed(1), pad.left - 6, y + 3);
}

// Gradient fill
const gradient = ctx.createLinearGradient(0, pad.top, 0, pad.top + ch);
gradient.addColorStop(0, "rgba(34,197,94,0.2)");
gradient.addColorStop(1, "rgba(34,197,94,0.01)");
gradient.addColorStop(0, areaTop);
gradient.addColorStop(1, areaBottom);

ctx.beginPath();
ctx.moveTo(pad.left, pad.top + ch);
Expand Down Expand Up @@ -96,7 +104,7 @@ export function ThroughputChart({ data }: ThroughputChartProps) {
ctx.lineWidth = 2;
ctx.stroke();
}
}, [data]);
}, [data, theme.value]);

const current = data.length > 0 ? data[data.length - 1] : 0;

Expand Down
18 changes: 12 additions & 6 deletions dashboard/src/charts/timeseries-chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef } from "preact/hooks";
import type { TimeseriesBucket } from "../api/types";
import type { TimeseriesBucket } from "../api";
import { theme } from "../hooks";

interface TimeseriesChartProps {
data: TimeseriesBucket[];
Expand All @@ -23,10 +24,15 @@ export function TimeseriesChart({ data }: TimeseriesChartProps) {
const w = rect.width;
const h = rect.height;

const isDark = theme.value === "dark";
const gridColor = isDark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.06)";
const textColor = isDark ? "rgba(139,149,165,0.5)" : "rgba(100,116,139,0.7)";
const placeholderColor = isDark ? "rgba(139,149,165,0.4)" : "rgba(100,116,139,0.5)";

ctx.clearRect(0, 0, w, h);

if (!data.length) {
ctx.fillStyle = "rgba(139,149,165,0.4)";
ctx.fillStyle = placeholderColor;
ctx.font = "12px -apple-system, sans-serif";
ctx.textAlign = "center";
ctx.fillText("No timeseries data", w / 2, h / 2);
Expand All @@ -44,13 +50,13 @@ export function TimeseriesChart({ data }: TimeseriesChartProps) {
// Y-axis grid
for (let i = 0; i <= 4; i++) {
const y = pad.top + ch * (1 - i / 4);
ctx.strokeStyle = "rgba(255,255,255,0.04)";
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad.left, y);
ctx.lineTo(w - pad.right, y);
ctx.stroke();
ctx.fillStyle = "rgba(139,149,165,0.5)";
ctx.fillStyle = textColor;
ctx.font = "10px -apple-system, sans-serif";
ctx.textAlign = "right";
ctx.fillText(Math.round((maxCount * i) / 4).toString(), pad.left - 6, y + 3);
Expand Down Expand Up @@ -79,7 +85,7 @@ export function TimeseriesChart({ data }: TimeseriesChartProps) {
});

// X-axis timestamps
ctx.fillStyle = "rgba(139,149,165,0.5)";
ctx.fillStyle = textColor;
ctx.font = "10px -apple-system, sans-serif";
ctx.textAlign = "center";
const labelCount = Math.min(6, data.length);
Expand All @@ -90,7 +96,7 @@ export function TimeseriesChart({ data }: TimeseriesChartProps) {
const x = pad.left + idx * (barW + gap) + barW / 2;
ctx.fillText(label, x, h - 10);
}
}, [data]);
}, [data, theme.value]);

return (
<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">
Expand Down
9 changes: 7 additions & 2 deletions dashboard/src/components/layout/header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
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";
import {
lastRefreshAt,
refreshInterval,
setRefreshInterval,
theme,
toggleTheme,
} from "../../hooks";

function RelativeTime() {
const [ago, setAgo] = useState("");
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/components/layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Header } from "./header";
export { Shell } from "./shell";
export { Sidebar } from "./sidebar";
2 changes: 1 addition & 1 deletion dashboard/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function Sidebar() {
}, []);

return (
<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)]">
<aside class="w-60 shrink-0 border-r dark:border-white/[0.06] border-slate-200 dark:bg-surface-2 bg-white overflow-y-auto h-[calc(100vh-56px)]">
<nav class="p-3 space-y-5 pt-4">
{NAV_GROUPS.map((group) => (
<div key={group.title}>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JobStatus } from "../../api/types";
import type { JobStatus } from "../../api";

const STATUS_STYLES: Record<string, string> = {
pending: "bg-warning/10 text-warning border-warning/20",
Expand Down
25 changes: 25 additions & 0 deletions dashboard/src/components/ui/error-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RefreshCw, WifiOff } from "lucide-preact";
import { Button } from "./button";

interface ErrorStateProps {
message: string;
onRetry?: () => void;
}

export function ErrorState({ message, onRetry }: ErrorStateProps) {
return (
<div class="flex flex-col items-center justify-center py-20 text-center">
<div class="w-14 h-14 rounded-2xl dark:bg-danger/10 bg-danger/5 flex items-center justify-center mb-5">
<WifiOff class="w-7 h-7 text-danger" strokeWidth={1.5} />
</div>
<p class="text-sm font-medium dark:text-gray-200 text-slate-700 mb-1">Unable to load data</p>
<p class="text-xs text-muted max-w-xs mb-5">{message}</p>
{onRetry && (
<Button variant="ghost" onClick={onRetry}>
<RefreshCw class="w-3.5 h-3.5" />
Retry
</Button>
)}
</div>
);
}
12 changes: 12 additions & 0 deletions dashboard/src/components/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export { Badge } from "./badge";
export { Button } from "./button";
export { ConfirmDialog } from "./confirm-dialog";
export { type Column, DataTable } from "./data-table";
export { EmptyState } from "./empty-state";
export { ErrorState } from "./error-state";
export { CardSkeleton, Loading, TableSkeleton } from "./loading";
export { Pagination } from "./pagination";
export { ProgressBar } from "./progress-bar";
export { StatCard } from "./stat-card";
export { StatsGrid } from "./stats-grid";
export { ToastContainer } from "./toast";
2 changes: 1 addition & 1 deletion dashboard/src/components/ui/stat-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LucideIcon } from "lucide-preact";
import { Ban, CheckCircle2, Clock, Play, Skull, XCircle } from "lucide-preact";
import { fmtNumber } from "../../lib/format";
import { fmtNumber } from "../../lib";

interface StatCardProps {
label: string;
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/ui/stats-grid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { QueueStats } from "../../api/types";
import type { QueueStats } from "../../api";
import { StatCard } from "./stat-card";

interface StatsGridProps {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/ui/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CheckCircle2, Info, X, XCircle } from "lucide-preact";
import { dismissToast, type Toast, toasts } from "../../hooks/use-toast";
import { dismissToast, type Toast, toasts } from "../../hooks";

const TYPE_CONFIG: Record<
Toast["type"],
Expand Down
9 changes: 9 additions & 0 deletions dashboard/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { useApi } from "./use-api";
export {
lastRefreshAt,
markRefreshed,
refreshInterval,
setRefreshInterval,
} from "./use-auto-refresh";
export { theme, toggleTheme } from "./use-theme";
export { addToast, dismissToast, type Toast, toasts } from "./use-toast";
2 changes: 1 addition & 1 deletion dashboard/src/hooks/use-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { api } from "../api/client";
import { api } from "../api";
import { markRefreshed, refreshInterval } from "./use-auto-refresh";

interface UseApiResult<T> {
Expand Down
8 changes: 7 additions & 1 deletion dashboard/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ html:not(.dark) body {
animation: fadeIn 0.2s ease-out;
}

.animate-shimmer {
.dark .animate-shimmer {
background: linear-gradient(
90deg,
var(--color-surface-3) 25%,
Expand All @@ -120,6 +120,12 @@ html:not(.dark) body {
animation: shimmer 1.5s infinite;
}

html:not(.dark) .animate-shimmer {
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}

/* Monospace */
.font-mono {
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "SF Mono", monospace;
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { fmtDuration, fmtNumber, fmtTime, truncateId } from "./format";
export { ROUTES, type RoutableProps } from "./routes";
15 changes: 6 additions & 9 deletions dashboard/src/pages/circuit-breakers.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { ShieldAlert } from "lucide-preact";
import type { CircuitBreaker as CBType } from "../api/types";
import { Badge } from "../components/ui/badge";
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 } from "../lib/format";
import type { RoutableProps } from "../lib/routes";
import type { CircuitBreaker as CBType } from "../api";
import { Badge, type Column, DataTable, EmptyState, ErrorState, Loading } from "../components/ui";
import { useApi } from "../hooks";
import { fmtTime, type RoutableProps } from "../lib";

const CB_COLUMNS: Column<CBType>[] = [
{ header: "Task", accessor: (b) => <span class="font-medium">{b.task_name}</span> },
Expand All @@ -29,8 +25,9 @@ const CB_COLUMNS: Column<CBType>[] = [
];

export function CircuitBreakers(_props: RoutableProps) {
const { data: breakers, loading } = useApi<CBType[]>("/api/circuit-breakers");
const { data: breakers, loading, error, refetch } = useApi<CBType[]>("/api/circuit-breakers");

if (error && !breakers) return <ErrorState message={error} onRetry={refetch} />;
if (loading && !breakers) return <Loading />;

return (
Expand Down
27 changes: 15 additions & 12 deletions dashboard/src/pages/dead-letters.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
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";
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";
import { Pagination } from "../components/ui/pagination";
import { useApi } from "../hooks/use-api";
import { addToast } from "../hooks/use-toast";
import { fmtTime, truncateId } from "../lib/format";
import type { RoutableProps } from "../lib/routes";
import { apiPost, type DeadLetter } from "../api";
import {
Button,
type Column,
ConfirmDialog,
DataTable,
EmptyState,
ErrorState,
Loading,
Pagination,
} from "../components/ui";
import { addToast, useApi } from "../hooks";
import { fmtTime, type RoutableProps, truncateId } from "../lib";

const PAGE_SIZE = 20;

Expand Down Expand Up @@ -42,6 +43,7 @@ export function DeadLetters(_props: RoutableProps) {
const {
data: items,
loading,
error,
refetch,
} = useApi<DeadLetter[]>(
`/api/dead-letters?limit=${grouped ? 200 : PAGE_SIZE}&offset=${grouped ? 0 : page * PAGE_SIZE}`,
Expand Down Expand Up @@ -139,6 +141,7 @@ export function DeadLetters(_props: RoutableProps) {
},
];

if (error && !items) return <ErrorState message={error} onRetry={refetch} />;
if (loading && !items) return <Loading />;

const groups = grouped && items ? groupByError(items) : [];
Expand Down
Loading
Loading