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
6 changes: 3 additions & 3 deletions src/app/(dashboard)/fleet/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
environmentsQuery.isLoading ||
nodesQuery.isLoading;

const rawNodes = nodesQuery.data ?? [];

Check warning on line 111 in src/app/(dashboard)/fleet/page.tsx

View workflow job for this annotation

GitHub Actions / Lint & Type Check

The 'rawNodes' logical expression could make the dependencies of useMemo Hook (at line 132) change on every render. To fix this, wrap the initialization of 'rawNodes' in its own useMemo() Hook

// Sort client-side
const nodes = useMemo(() => {
Expand Down Expand Up @@ -186,15 +186,15 @@
return (
<div className="space-y-6">
<div className="flex items-center gap-1">
<span className="rounded-full px-3 h-7 text-xs font-medium border transition-colors bg-accent text-accent-foreground border-transparent inline-flex items-center">
Nodes
</span>
<Link
href="/fleet/overview"
className="rounded-full px-3 h-7 text-xs font-medium border transition-colors bg-transparent text-muted-foreground border-border hover:bg-muted inline-flex items-center"
>
Overview
</Link>
<span className="rounded-full px-3 h-7 text-xs font-medium border transition-colors bg-accent text-accent-foreground border-transparent inline-flex items-center">
Nodes
</span>
</div>

{/* Toolbar — shown when not loading and nodes exist or filters active */}
Expand Down
35 changes: 19 additions & 16 deletions src/app/(dashboard)/pipelines/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { ComponentPalette } from "@/components/flow/component-palette";
import { FlowCanvas } from "@/components/flow/flow-canvas";
import { FlowToolbar } from "@/components/flow/flow-toolbar";
import { AiPipelineDialog } from "@/components/flow/ai-pipeline-dialog";
import { AiDebugPanel } from "@/components/flow/ai-debug-panel";
import { DetailPanel } from "@/components/flow/detail-panel";
import { DeployDialog } from "@/components/flow/deploy-dialog";
import { SaveTemplateDialog } from "@/components/flow/save-template-dialog";
Expand Down Expand Up @@ -128,7 +127,6 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) {
const [metricsOpen, setMetricsOpen] = useState(false);
const [logsOpen, setLogsOpen] = useState(false);
const [aiDialogOpen, setAiDialogOpen] = useState(false);
const [debugPanelOpen, setDebugPanelOpen] = useState(false);

const selectedTeamId = useTeamStore((s) => s.selectedTeamId);
const teamQuery = useQuery(
Expand Down Expand Up @@ -202,14 +200,27 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) {
),
);

// Lightweight check for recent errors (for toolbar badge) — 24h window
const [errorCheckSince] = useState(
() => new Date(Date.now() - 24 * 60 * 60 * 1000),
);
// Compute session start from minimum uptime across all running nodes.
// Use dataUpdatedAt (stable timestamp from React Query) instead of Date.now()
// to satisfy react-hooks/purity (no impure calls) and avoid useEffect+setState.
const sessionStart = useMemo(() => {
const statuses = pipelineQuery.data?.nodeStatuses;
if (!statuses || statuses.length === 0) return null;
const uptimes = statuses
.filter((s: { status: string; uptimeSeconds: number | null }) =>
s.status === "RUNNING" && s.uptimeSeconds != null
)
.map((s: { uptimeSeconds: number | null }) => s.uptimeSeconds!);
if (uptimes.length === 0) return null;
const minUptime = Math.min(...uptimes);
return new Date(pipelineQuery.dataUpdatedAt - minUptime * 1000);
}, [pipelineQuery.data?.nodeStatuses, pipelineQuery.dataUpdatedAt]);

// Lightweight check for recent errors (for toolbar badge) — scoped to current session
const recentErrorsQuery = useQuery(
trpc.pipeline.logs.queryOptions(
{ pipelineId, levels: ["ERROR"], limit: 1, since: errorCheckSince },
{ enabled: !!isDeployed && !logsOpen, refetchInterval: 10000 },
{ pipelineId, levels: ["ERROR"], limit: 1, since: sessionStart! },
{ enabled: !!isDeployed && !logsOpen && !!sessionStart, refetchInterval: 10000 },
),
);
const hasRecentErrors = (recentErrorsQuery.data?.items?.length ?? 0) > 0;
Expand Down Expand Up @@ -464,7 +475,6 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) {
onDiscardChanges={() => setDiscardOpen(true)}
aiEnabled={aiEnabled}
onAiOpen={() => setAiDialogOpen(true)}
onDebugOpen={() => setDebugPanelOpen(true)}
deployedVersionNumber={pipelineQuery.data?.deployedVersionNumber}
/>
</div>
Expand Down Expand Up @@ -582,13 +592,6 @@ function PipelineBuilderInner({ pipelineId }: { pipelineId: string }) {
onOpenChange={setAiDialogOpen}
pipelineId={pipelineId}
environmentName={pipelineQuery.data?.environment?.name}
/>
)}
{aiEnabled && (
<AiDebugPanel
open={debugPanelOpen}
onOpenChange={setDebugPanelOpen}
pipelineId={pipelineId}
currentYaml={currentYaml}
/>
)}
Expand Down
14 changes: 14 additions & 0 deletions src/app/(dashboard)/pipelines/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ function getReductionPercent(totals: {
return Math.max(0, (1 - evOut / evIn) * 100);
}

function formatUptime(seconds: number | null): string {
if (seconds == null) return "\u2014";
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
if (seconds < 86400)
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`;
}

/** Derive display status string for a pipeline row. */
function derivePipelineStatus(
pipeline: { isDraft: boolean; nodeStatuses: Array<{ status: string }> },
Expand Down Expand Up @@ -587,6 +596,7 @@ export default function PipelinesPage() {
currentDirection={sortDirection}
onSort={handleSort}
/>
<TableHead className="text-right">Uptime</TableHead>
<TableHead className="text-center">Health</TableHead>
<SortableHeader
label="Events/sec In"
Expand Down Expand Up @@ -758,6 +768,10 @@ export default function PipelinesPage() {
)}
</div>
</TableCell>
{/* Uptime */}
<TableCell className="text-right font-mono text-sm tabular-nums text-muted-foreground">
{formatUptime(pipeline.minUptimeSeconds)}
</TableCell>
{/* Health — batch data instead of per-row query */}
<TableCell className="text-center">
{pipeline.isDraft ? (
Expand Down
229 changes: 0 additions & 229 deletions src/components/flow/ai-debug-panel.tsx

This file was deleted.

Loading
Loading