288 lines
9.4 KiB
TypeScript
288 lines
9.4 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import {
|
|
ClipboardList,
|
|
FolderKanban,
|
|
TestTube2,
|
|
Rocket,
|
|
GitCommit,
|
|
AlertTriangle,
|
|
Bot,
|
|
ShieldAlert,
|
|
RefreshCw,
|
|
PlayCircle,
|
|
} from "lucide-react";
|
|
import { Card, CardBody, CardHeader, CardTitle, CardFooter } from "@/components/ui/Card";
|
|
import { MetricCard } from "@/components/ui/MetricCard";
|
|
import { ProgressRing } from "@/components/ui/ProgressRing";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|
import { overview, health } from "@/lib/api";
|
|
import type { OverviewMetrics } from "@/lib/types";
|
|
|
|
const DEMO_METRICS: OverviewMetrics = {
|
|
tenant_id: "demo-tenant",
|
|
total_projects: 6,
|
|
total_requirements: 142,
|
|
total_tasks: 318,
|
|
total_tests: 512,
|
|
total_deployments: 27,
|
|
total_commits: 1204,
|
|
jobs_running: 3,
|
|
jobs_queued: 8,
|
|
jobs_failed_24h: 2,
|
|
implementation_coverage_pct: 74.3,
|
|
test_coverage_pct: 68.9,
|
|
traceability_score_pct: 81.5,
|
|
project_health_pct: 77.2,
|
|
security_findings: 14,
|
|
failed_builds: 3,
|
|
active_ai_jobs: 5,
|
|
featured_projects: [
|
|
{ key: "ASF", name: "Azurian Software Factory" },
|
|
{ key: "NOVA", name: "Nova Customer Portal" },
|
|
{ key: "ORB", name: "Orbital Billing" },
|
|
],
|
|
mock_fields: ["*"],
|
|
};
|
|
|
|
export default function OverviewPage() {
|
|
const metricsQ = useQuery({
|
|
queryKey: ["overview"],
|
|
queryFn: () => overview.get(),
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
const healthQ = useQuery({
|
|
queryKey: ["health"],
|
|
queryFn: () => health(),
|
|
retry: false,
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
|
|
const m: OverviewMetrics = metricsQ.data ?? DEMO_METRICS;
|
|
const isMock = !metricsQ.data;
|
|
|
|
return (
|
|
<div className="p-6 lg:p-8 space-y-6 max-w-[1600px] mx-auto">
|
|
<header className="flex flex-wrap items-start justify-between gap-4">
|
|
<div className="min-w-0">
|
|
<div className="flex items-center gap-2">
|
|
<h1 className="text-2xl font-semibold tracking-tight text-slate-900">
|
|
Overview
|
|
</h1>
|
|
{isMock && (
|
|
<Badge tone="amber" className="shrink-0">
|
|
Demo data
|
|
</Badge>
|
|
)}
|
|
{healthQ.data && (
|
|
<Badge tone={healthQ.data.status === "ok" ? "green" : "red"}>
|
|
{healthQ.data.service} v{healthQ.data.version} · {healthQ.data.status}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<p className="mt-1 text-sm text-slate-500">
|
|
From Requirement to Production — everything contextualized, traceable and AI-accessible.
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={() => {
|
|
void metricsQ.refetch();
|
|
void healthQ.refetch();
|
|
}}
|
|
>
|
|
<RefreshCw className="w-4 h-4" />
|
|
Refresh
|
|
</Button>
|
|
<Button variant="primary" size="sm">
|
|
<PlayCircle className="w-4 h-4" />
|
|
New AI Job
|
|
</Button>
|
|
</div>
|
|
</header>
|
|
|
|
<section className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-4">
|
|
<MetricCard
|
|
label="Projects"
|
|
value={m.total_projects}
|
|
tone="brand"
|
|
icon={<FolderKanban className="w-5 h-5" />}
|
|
/>
|
|
<MetricCard
|
|
label="Requirements"
|
|
value={m.total_requirements}
|
|
tone="violet"
|
|
delta={3.4}
|
|
icon={<ClipboardList className="w-5 h-5" />}
|
|
/>
|
|
<MetricCard
|
|
label="Tasks"
|
|
value={m.total_tasks}
|
|
tone="blue"
|
|
icon={<ClipboardList className="w-5 h-5" />}
|
|
/>
|
|
<MetricCard
|
|
label="Tests"
|
|
value={m.total_tests}
|
|
tone="green"
|
|
delta={1.1}
|
|
icon={<TestTube2 className="w-5 h-5" />}
|
|
/>
|
|
<MetricCard
|
|
label="Deployments"
|
|
value={m.total_deployments}
|
|
tone="brand"
|
|
icon={<Rocket className="w-5 h-5" />}
|
|
/>
|
|
<MetricCard
|
|
label="Commits"
|
|
value={m.total_commits}
|
|
tone="neutral"
|
|
icon={<GitCommit className="w-5 h-5" />}
|
|
/>
|
|
</section>
|
|
|
|
<section className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Health & Coverage</CardTitle>
|
|
<Badge tone="neutral">Across all projects</Badge>
|
|
</CardHeader>
|
|
<CardBody className="space-y-4">
|
|
<ProgressRing
|
|
value={m.project_health_pct}
|
|
tone="green"
|
|
label="Project Health"
|
|
sublabel="Weighted stability score"
|
|
/>
|
|
<ProgressRing
|
|
value={m.implementation_coverage_pct}
|
|
tone="brand"
|
|
label="Implementation Coverage"
|
|
sublabel="Requirements → Tasks → Code"
|
|
/>
|
|
<ProgressRing
|
|
value={m.test_coverage_pct}
|
|
tone="amber"
|
|
label="Test Coverage"
|
|
sublabel="Automated vs total tests"
|
|
/>
|
|
<ProgressRing
|
|
value={m.traceability_score_pct}
|
|
tone="violet"
|
|
label="Traceability Score"
|
|
sublabel="Graph connectivity"
|
|
/>
|
|
</CardBody>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Jobs & AI</CardTitle>
|
|
<Badge tone="brand">
|
|
<Bot className="w-3 h-3" /> AI
|
|
</Badge>
|
|
</CardHeader>
|
|
<CardBody className="space-y-3">
|
|
<MetricCard
|
|
label="Jobs running"
|
|
value={m.jobs_running}
|
|
tone="blue"
|
|
icon={<PlayCircle className="w-4 h-4" />}
|
|
/>
|
|
<MetricCard
|
|
label="Jobs queued"
|
|
value={m.jobs_queued}
|
|
tone="neutral"
|
|
icon={<RefreshCw className="w-4 h-4" />}
|
|
/>
|
|
<MetricCard
|
|
label="Jobs failed (24h)"
|
|
value={m.jobs_failed_24h}
|
|
tone="red"
|
|
icon={<AlertTriangle className="w-4 h-4" />}
|
|
/>
|
|
<MetricCard
|
|
label="Active AI jobs"
|
|
value={m.active_ai_jobs}
|
|
tone="violet"
|
|
icon={<Bot className="w-4 h-4" />}
|
|
/>
|
|
</CardBody>
|
|
<CardFooter>
|
|
<span className="text-slate-500">Priority queue: high → low</span>
|
|
<a className="text-brand-600 font-medium hover:underline cursor-pointer" href="/pipelines">
|
|
View pipelines →
|
|
</a>
|
|
</CardFooter>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Alerts</CardTitle>
|
|
<Badge tone="red">{m.security_findings + m.failed_builds}</Badge>
|
|
</CardHeader>
|
|
<CardBody className="space-y-3">
|
|
<div className="flex items-start gap-3 p-3 rounded-lg border border-red-100 bg-red-50/60">
|
|
<ShieldAlert className="w-4 h-4 text-red-600 mt-0.5" />
|
|
<div className="min-w-0 flex-1">
|
|
<div className="text-sm font-medium text-red-900">
|
|
{m.security_findings} Security findings
|
|
</div>
|
|
<div className="text-xs text-red-700/80 mt-0.5">
|
|
Review findings in Quality → Security
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-3 p-3 rounded-lg border border-amber-100 bg-amber-50/60">
|
|
<AlertTriangle className="w-4 h-4 text-amber-600 mt-0.5" />
|
|
<div className="min-w-0 flex-1">
|
|
<div className="text-sm font-medium text-amber-900">
|
|
{m.failed_builds} Failed builds
|
|
</div>
|
|
<div className="text-xs text-amber-800/80 mt-0.5">
|
|
Last 24 hours — inspect for root causes
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{m.featured_projects && m.featured_projects.length > 0 ? (
|
|
<div className="pt-2">
|
|
<div className="text-xs uppercase tracking-wide text-slate-500 mb-2">
|
|
Featured projects
|
|
</div>
|
|
<ul className="divide-y divide-slate-100 border border-slate-100 rounded-lg overflow-hidden">
|
|
{m.featured_projects.map((p) => (
|
|
<li
|
|
key={p.key}
|
|
className="flex items-center justify-between px-3 py-2.5 text-sm hover:bg-slate-50"
|
|
>
|
|
<div className="min-w-0">
|
|
<div className="font-medium text-slate-800 truncate">{p.name}</div>
|
|
<div className="text-xs text-slate-500 asf-monospace">{p.key}</div>
|
|
</div>
|
|
<Badge tone="brand">Open</Badge>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
) : (
|
|
<EmptyState
|
|
title="No projects yet"
|
|
description="Create a project to start tracking your software factory."
|
|
actionLabel="Create project"
|
|
/>
|
|
)}
|
|
</CardBody>
|
|
</Card>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|