Spaces:
Sleeping
Sleeping
File size: 5,298 Bytes
177c3f8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | "use client";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
MessageSquare,
GitGraph,
ExternalLink,
RefreshCw,
Bell
} from "lucide-react";
import { useEffect, useState } from "react";
import { api } from "@/lib/api";
const IntegrationsPage = () => {
const [loading, setLoading] = useState(false);
const [lastSync, setLastSync] = useState("Never");
const [isHealthy, setIsHealthy] = useState(false);
const syncIntegrations = async () => {
setLoading(true);
try {
const [health] = await Promise.all([
api.getHealth(),
api.getDashboard(),
]);
setIsHealthy(Boolean(health?.environment_ready));
setLastSync(new Date().toLocaleTimeString());
} catch (error) {
console.error("Failed to sync integration status:", error);
setIsHealthy(false);
setLastSync("Sync failed");
} finally {
setLoading(false);
}
};
useEffect(() => {
const initialSync = setTimeout(() => {
void syncIntegrations();
}, 0);
return () => clearTimeout(initialSync);
}, []);
const integrations = [
{
id: "slack",
name: "Slack",
description: "Automated rollout notifications and approval workflows.",
icon: MessageSquare,
status: isHealthy ? "connected" : "disconnected",
details: isHealthy ? "Alerts can be routed through backend automation" : "Backend unavailable",
lastSync
},
{
id: "github",
name: "GitHub",
description: "Trigger rollouts from PRs and sync deployment status.",
icon: GitGraph,
status: "disconnected",
details: "Requires OAuth",
lastSync
}
];
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">Integrations</h1>
<p className="text-muted-foreground mt-1">Manage external connections and observability sync.</p>
</div>
<Button variant="outline" size="sm" className="rounded-full" onClick={syncIntegrations} disabled={loading}>
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? "animate-spin" : ""}`} />
{loading ? "Syncing..." : "Sync All"}
</Button>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{integrations.map((item) => (
<Card key={item.id} className="border-border/50 bg-card/50">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted/50">
<item.icon className="h-6 w-6 text-primary" />
</div>
<Badge
variant={item.status === "connected" ? "default" : "secondary"}
className={item.status === "connected" ? "bg-green-500/10 text-green-600 hover:bg-green-500/10" : ""}
>
{item.status === "connected" ? "Connected" : "Disconnected"}
</Badge>
</div>
<CardTitle className="mt-4">{item.name}</CardTitle>
<CardDescription>{item.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Status</span>
<span className="font-medium">{item.details}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Last Sync</span>
<span className="text-xs">{item.lastSync}</span>
</div>
<div className="pt-4 flex gap-2">
<Button variant="outline" className="w-full text-xs h-8 rounded-full">
Configure
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full">
<ExternalLink className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
<Card className="border-primary/20 bg-primary/5">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-primary">
<Bell className="h-5 w-5" />
Webhook Endpoints
</CardTitle>
<CardDescription>Receive automated alerts from your own infrastructure.</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-lg border bg-background/50 p-4 font-mono text-xs">
GET {(api.getApiBaseUrl() || "http://localhost:8000")}/monitoring/alerts
</div>
<p className="text-[10px] text-muted-foreground mt-4">
Uses live backend monitoring routes for alert ingestion and observability polling.
</p>
</CardContent>
</Card>
</div>
);
};
export default IntegrationsPage;
|