Mahakii's picture
Swagger ui done
177c3f8
Raw
History Blame Contribute Delete
4.9 kB
"use client";
import { useMemo, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Search, MoreHorizontal } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { useEnv } from "@/components/env-provider";
export default function FeatureFlagsPage() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const { state } = useEnv();
const backendFlag = useMemo(() => {
const lastObs = state?.history?.[state.history.length - 1]?.observation;
if (!lastObs) return null;
return {
id: lastObs.feature_name,
name: lastObs.feature_name.replace(/_/g, " "),
rollout: lastObs.current_rollout_percentage,
status: !(state?.is_done ?? false),
scenario: state?.scenario_name ?? "unknown",
difficulty: state?.difficulty ?? "unknown",
lastUpdated: `Step ${state?.step_count ?? 0}`,
};
}, [state]);
const filtered = useMemo(() => {
if (!backendFlag) return [];
const q = searchQuery.trim().toLowerCase();
if (!q) return [backendFlag];
if (backendFlag.id.toLowerCase().includes(q) || backendFlag.name.toLowerCase().includes(q)) {
return [backendFlag];
}
return [];
}, [backendFlag, searchQuery]);
return (
<div className="flex-1 space-y-6 p-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Control</h1>
<p className="text-muted-foreground mt-1">The UI reflects only what the backend supports.</p>
</div>
<Button variant="outline" className="rounded-full" onClick={() => router.push("/monitoring")}>
View monitoring
</Button>
</div>
<div className="flex items-center justify-between gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search…"
className="pl-9 rounded-full bg-card/50 backdrop-blur-sm border-border/50"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<div className="grid gap-4 mt-6">
{filtered.map((flag) => (
<Card
key={flag.id}
className="border-border/50 bg-card/50 backdrop-blur-sm hover:border-primary/30 hover:shadow-md transition-all cursor-pointer group"
onClick={() => router.push(`/flags/${flag.id}`)}
>
<CardContent className="p-0 flex items-center justify-between">
<div className="p-6 flex items-center gap-6 flex-1">
<div className="flex-1">
<div className="flex items-center gap-3">
<h3 className="font-semibold text-lg">{flag.name}</h3>
<Badge variant={flag.status ? "default" : "secondary"} className="rounded-full text-xs font-medium">
{flag.status ? "Active" : "Done"}
</Badge>
<Badge variant="outline" className="rounded-full text-xs font-medium bg-background/50">
{flag.rollout}% Rollout
</Badge>
</div>
<p className="text-sm text-muted-foreground mt-1">
Scenario: {flag.scenario} ({flag.difficulty})
</p>
</div>
<div className="flex items-center gap-6">
<div className="flex flex-col items-end">
<span className="text-sm font-medium">{flag.lastUpdated}</span>
<span className="text-xs text-muted-foreground">Last updated</span>
</div>
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => {
e.stopPropagation();
router.push(`/flags/${flag.id}`);
}}
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
{!backendFlag ? (
<Card className="border-border/50 bg-card/50">
<CardContent className="p-6 text-sm text-muted-foreground">
No active simulation found yet. Start the backend and open the Dashboard to initialize state.
</CardContent>
</Card>
) : null}
</div>
</div>
);
}