feat(web): patching page with policies, windows and runs

This commit is contained in:
2026-09-15 11:19:01 +00:00
parent 3a7618f82f
commit 70c239021d
4 changed files with 407 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
"use client";
import { Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useAuth } from "@/components/AuthProvider";
import { Card, CenteredSpinner } from "@/components/ui";
import { PolicyList } from "@/components/patching/PolicyList";
import { WindowList } from "@/components/patching/WindowList";
import { RunList } from "@/components/patching/RunList";
const TABS = [
{ id: "policies", label: "Policies" },
{ id: "windows", label: "Windows" },
{ id: "runs", label: "Runs" },
] as const;
type TabId = (typeof TABS)[number]["id"];
// useSearchParams is only allowed inside a Suspense boundary, as on the
// servers page.
export default function PatchingPage() {
return (
<Suspense fallback={<CenteredSpinner label="Loading patching" />}>
<PatchingInner />
</Suspense>
);
}
function PatchingInner() {
const router = useRouter();
const params = useSearchParams();
const { isAdmin } = useAuth();
const tab: TabId = (TABS.find((t) => t.id === params.get("tab"))?.id ?? "policies") as TabId;
return (
<div className="p-4 sm:p-6 lg:p-8">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-text-primary">Patching</h1>
<p className="mt-1 max-w-2xl text-sm text-text-secondary">
Patch servers inside maintenance windows. Every patch Vantage performs, scheduled or clicked, is recorded as a run with a result for each server.
</p>
</div>
<div role="tablist" aria-label="Patching" className="mb-4 flex gap-1 border-b border-border">
{TABS.map((t) => (
<button
key={t.id}
role="tab"
id={`patching-tab-${t.id}`}
aria-selected={tab === t.id}
onClick={() => router.replace(`/patching?tab=${t.id}`)}
className={`-mb-px border-b-2 px-4 py-2 text-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${
tab === t.id ? "border-accent text-text-primary" : "border-transparent text-text-secondary hover:text-text-primary"
}`}
>
{t.label}
</button>
))}
</div>
<Card padding={false} role="tabpanel" aria-labelledby={`patching-tab-${tab}`}>
{tab === "policies" && <PolicyList canEdit={isAdmin} />}
{tab === "windows" && <WindowList canEdit={isAdmin} />}
{tab === "runs" && <RunList />}
</Card>
</div>
);
}