"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { ArrowDown, ArrowUp, Layers, Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { api, type Environment, type FlowSummary } from "@/lib/api"; export default function EnvironmentsPage() { const [envs, setEnvs] = useState(null); const [pipelines, setPipelines] = useState([]); const [error, setError] = useState(null); async function refresh() { setError(null); try { const [e, p] = await Promise.all([api.listEnvironments(), api.listFlows()]); setEnvs(e); setPipelines(p); } catch (err) { setError(err instanceof Error ? err.message : "load failed"); } } useEffect(() => { refresh(); }, []); async function handleDelete(name: string) { if (!confirm(`Delete environment "${name}"? Agents following it will stop dispatching its pipelines.`)) return; try { await api.deleteEnvironment(name); await refresh(); } catch (e) { setError(e instanceof Error ? e.message : "delete failed"); } } const pipelineName = (id: string) => pipelines.find((p) => p.id === id)?.name ?? id; return (
Environments

Environments

A deploy stage is a named, ordered list of pipelines — the promotion sequence. Agents follow environments; triggering an agent runs its followed envs’ pipelines (each still gated by its Trigger node’s branch).

{error && (

{error}

)} {envs === null &&

Loading…

} {envs?.length === 0 && (

No environments yet. Create dev,{" "} staging, and{" "} prod to get started.

)}
{envs?.map((env) => (
{env.name} {env.description && {env.description}}
))}
); } // ─── pipelines-in-env sub-component ───────────────────────────────────────── function PipelinesInEnv({ env, allPipelines, pipelineName, onChanged, onError, }: { env: Environment; allPipelines: FlowSummary[]; pipelineName: (id: string) => string; onChanged: () => void | Promise; onError: (m: string | null) => void; }) { const [picking, setPicking] = useState(false); const ids = env.pipelineIds ?? []; const inEnv = new Set(ids); const available = allPipelines.filter((p) => !inEnv.has(p.id)); async function reorder(next: string[]) { try { await api.reorderEnvPipelines(env.name, next); await onChanged(); } catch (e) { onError(e instanceof Error ? e.message : "reorder failed"); } } function move(i: number, dir: -1 | 1) { const j = i + dir; if (j < 0 || j >= ids.length) return; const next = ids.slice(); [next[i], next[j]] = [next[j], next[i]]; reorder(next); } return (
Pipelines in this environment (promotion order)
{ids.length === 0 && (

No pipelines yet. Build one on the{" "} Pipelines page and add it here.

)} {ids.length > 0 && (
    {ids.map((pid, i) => (
  • {i + 1} {pipelineName(pid)}
  • ))}
)} {picking && (
{available.length === 0 ? (

All pipelines are already in this environment.

) : (
    {available.map((p) => (
  • {p.name}
  • ))}
)}
)}
); }