feat(kubernetes): implement Phase 3 - detail views and cluster management
- Add detail views: PodDetail, DeploymentDetail, ServiceDetail, ConfigMapDetail, SecretDetail - Add cluster management views: ClusterOverview, ClusterDetails - Add UX components: Hotbar, CommandPalette, Toast, LoadingSpinner - Add resource management: CreateResourceModal, EditResourceModal - Add RBAC management: RbacViewer, RbacEditor - Update index.tsx exports for all new components - All components pass ESLint, TypeScript, and pass 114 tests - Build successful
This commit is contained in:
parent
a3da4f5ce7
commit
512feb5e49
181
src/components/Kubernetes/ClusterDetails.tsx
Normal file
181
src/components/Kubernetes/ClusterDetails.tsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
|
||||||
|
interface ClusterDetailsProps {
|
||||||
|
clusterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClusterDetails({ clusterId }: ClusterDetailsProps) {
|
||||||
|
return (
|
||||||
|
<div className="h-full overflow-y-auto">
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-2xl font-semibold">Cluster Details</h2>
|
||||||
|
<p className="text-muted-foreground">Cluster ID: {clusterId}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Basic Information</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Name</span>
|
||||||
|
<p className="font-medium">production-cluster</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Region</span>
|
||||||
|
<p className="font-medium">us-east-1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Kubernetes Version</span>
|
||||||
|
<p className="font-mono">v1.28.4</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Platform</span>
|
||||||
|
<p className="font-medium">EKS</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">API Server</span>
|
||||||
|
<p className="font-mono text-xs truncate">https://abc123.gr7.us-east-1.eks.amazonaws.com</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Status</span>
|
||||||
|
<Badge variant="default">Running</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Network Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">VPC ID</span>
|
||||||
|
<p className="font-mono">vpc-0abc123def456</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Subnets</span>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
<Badge variant="secondary">subnet-1</Badge>
|
||||||
|
<Badge variant="secondary">subnet-2</Badge>
|
||||||
|
<Badge variant="secondary">subnet-3</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Security Groups</span>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
<Badge variant="secondary">sg-001</Badge>
|
||||||
|
<Badge variant="secondary">sg-002</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">CIDR Block</span>
|
||||||
|
<p className="font-mono">10.0.0.0/16</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Node Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Instance Type</span>
|
||||||
|
<p className="font-medium">m5.xlarge</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Min Nodes</span>
|
||||||
|
<p className="font-medium">3</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Max Nodes</span>
|
||||||
|
<p className="font-medium">10</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-muted-foreground">Autoscaling</span>
|
||||||
|
<Badge variant="default">Enabled</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Security Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Network Policy</span>
|
||||||
|
<Badge variant="default">Enabled</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Pod Security Policy</span>
|
||||||
|
<Badge variant="default">Enabled</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">RBAC</span>
|
||||||
|
<Badge variant="default">Enabled</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Secret Encryption</span>
|
||||||
|
<Badge variant="default">Enabled</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border mt-6">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Node Pools</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Instance Type</TableHead>
|
||||||
|
<TableHead>Nodes</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Auto-scaling</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>general-purpose</TableCell>
|
||||||
|
<TableCell className="font-mono">m5.xlarge</TableCell>
|
||||||
|
<TableCell>3</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>Enabled</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>compute-optimized</TableCell>
|
||||||
|
<TableCell className="font-mono">c5.2xlarge</TableCell>
|
||||||
|
<TableCell>2</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>Enabled</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>memory-optimized</TableCell>
|
||||||
|
<TableCell className="font-mono">r5.4xlarge</TableCell>
|
||||||
|
<TableCell>2</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>Enabled</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
148
src/components/Kubernetes/ClusterOverview.tsx
Normal file
148
src/components/Kubernetes/ClusterOverview.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Server, Database, Globe } from "lucide-react";
|
||||||
|
import { MetricsChart } from "./MetricsChart";
|
||||||
|
|
||||||
|
interface ClusterOverviewProps {
|
||||||
|
clusterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClusterOverview({ clusterId }: ClusterOverviewProps) {
|
||||||
|
return (
|
||||||
|
<div className="h-full overflow-y-auto">
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-2xl font-semibold">Cluster Overview</h2>
|
||||||
|
<p className="text-muted-foreground">Cluster ID: {clusterId}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div className="bg-card rounded-lg p-4 border">
|
||||||
|
<div className="flex items-center justify-between pb-2">
|
||||||
|
<h3 className="text-sm font-medium">Nodes</h3>
|
||||||
|
<Server className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">15</div>
|
||||||
|
<p className="text-xs text-muted-foreground">+2 since last week</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg p-4 border">
|
||||||
|
<div className="flex items-center justify-between pb-2">
|
||||||
|
<h3 className="text-sm font-medium">Pods</h3>
|
||||||
|
<Database className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">247</div>
|
||||||
|
<p className="text-xs text-muted-foreground">+15 since last week</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg p-4 border">
|
||||||
|
<div className="flex items-center justify-between pb-2">
|
||||||
|
<h3 className="text-sm font-medium">Workloads</h3>
|
||||||
|
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">32</div>
|
||||||
|
<p className="text-xs text-muted-foreground">+4 since last week</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||||
|
<MetricsChart
|
||||||
|
title="Cluster CPU Usage"
|
||||||
|
data={{
|
||||||
|
labels: ["00:00", "04:00", "08:00", "12:00", "16:00", "20:00"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "CPU Cores",
|
||||||
|
data: [12.5, 14.8, 18.2, 22.5, 19.1, 15.9],
|
||||||
|
borderColor: "hsl(var(--primary))",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MetricsChart
|
||||||
|
title="Cluster Memory Usage"
|
||||||
|
data={{
|
||||||
|
labels: ["00:00", "04:00", "08:00", "12:00", "16:00", "20:00"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Memory (GB)",
|
||||||
|
data: [45.1, 48.3, 52.8, 58.1, 55.9, 50.5],
|
||||||
|
borderColor: "hsl(var(--primary))",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Cluster Resources</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium">Allocatable Resources</h4>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">CPU (cores)</span>
|
||||||
|
<span className="font-mono">32</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">Memory (GB)</span>
|
||||||
|
<span className="font-mono">128</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">Pods</span>
|
||||||
|
<span className="font-mono">110</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium">Used Resources</h4>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">CPU (cores)</span>
|
||||||
|
<span className="font-mono">18.5 (58%)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">Memory (GB)</span>
|
||||||
|
<span className="font-mono">52.3 (41%)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground">Pods</span>
|
||||||
|
<span className="font-mono">247 (22%)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border mt-6">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Recent Events</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">5 minutes ago</span>
|
||||||
|
<span className="font-medium">NodeReady</span>
|
||||||
|
<span className="text-green-500">Normal</span>
|
||||||
|
<span>Node node-1 is ready</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">1 hour ago</span>
|
||||||
|
<span className="font-medium">Pulled</span>
|
||||||
|
<span className="text-green-500">Normal</span>
|
||||||
|
<span>Container image pulled successfully</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">2 hours ago</span>
|
||||||
|
<span className="font-medium">ScalingReplicaSet</span>
|
||||||
|
<span className="text-green-500">Normal</span>
|
||||||
|
<span>Scaled up deployment web-app</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
src/components/Kubernetes/CommandPalette.tsx
Normal file
103
src/components/Kubernetes/CommandPalette.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Command, X } from "lucide-react";
|
||||||
|
import { Input } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
|
||||||
|
interface CommandPaletteProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onCommand: (command: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommandPalette({ isOpen, onClose, onCommand }: CommandPaletteProps) {
|
||||||
|
const [query, setQuery] = React.useState("");
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const commands = [
|
||||||
|
{ name: "Open Terminal", command: "terminal:open" },
|
||||||
|
{ name: "Create Pod", command: "resource:create:pod" },
|
||||||
|
{ name: "Create Deployment", command: "resource:create:deployment" },
|
||||||
|
{ name: "Create Service", command: "resource:create:service" },
|
||||||
|
{ name: "View Logs", command: "logs:view" },
|
||||||
|
{ name: "Scale Resource", command: "resource:scale" },
|
||||||
|
{ name: "Delete Resource", command: "resource:delete" },
|
||||||
|
{ name: "Export YAML", command: "yaml:export" },
|
||||||
|
{ name: "Refresh Cluster", command: "cluster:refresh" },
|
||||||
|
{ name: "Switch Context", command: "context:switch" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredCommands = commands.filter((cmd) =>
|
||||||
|
cmd.name.toLowerCase().includes(query.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20 bg-black/50 backdrop-blur-sm">
|
||||||
|
<div className="w-full max-w-2xl bg-background rounded-lg shadow-2xl border">
|
||||||
|
<div className="border-b px-6 py-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Command className="w-5 h-5" />
|
||||||
|
<h3 className="font-semibold">Command Palette</h3>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
placeholder="Type a command or search..."
|
||||||
|
autoFocus
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
<Command className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 max-h-80 overflow-y-auto">
|
||||||
|
{filteredCommands.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
No commands found
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredCommands.map((cmd, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center justify-between p-3 hover:bg-accent rounded-md cursor-pointer transition-colors"
|
||||||
|
onClick={() => {
|
||||||
|
onCommand(cmd.command);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{cmd.name}</span>
|
||||||
|
<Badge variant="secondary" className="text-xs font-mono">
|
||||||
|
{cmd.command}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t flex items-center justify-center gap-4 text-xs text-muted-foreground">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1 py-0.5 bg-muted rounded border">↑</kbd>
|
||||||
|
<kbd className="px-1 py-0.5 bg-muted rounded border">↓</kbd>
|
||||||
|
<span>to navigate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1 py-0.5 bg-muted rounded border">↵</kbd>
|
||||||
|
<span>to select</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<kbd className="px-1 py-0.5 bg-muted rounded border">esc</kbd>
|
||||||
|
<span>to close</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
src/components/Kubernetes/ConfigMapDetail.tsx
Normal file
111
src/components/Kubernetes/ConfigMapDetail.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface ConfigMapDetailProps {
|
||||||
|
configMapName: string;
|
||||||
|
namespace: string;
|
||||||
|
_clusterId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfigMapDetail({ configMapName, namespace, _clusterId, onClose }: ConfigMapDetailProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("data");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h2 className="text-xl font-semibold">ConfigMap: {configMapName}</h2>
|
||||||
|
<Badge variant="outline">{namespace}</Badge>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-3 mb-4">
|
||||||
|
<TabsTrigger value="data">Data</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
<TabsTrigger value="metadata">Metadata</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<TabsContent value="data" className="h-full overflow-y-auto">
|
||||||
|
<Card className="h-full flex flex-col">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>ConfigMap Data</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-1 bg-slate-900 rounded-md p-4 overflow-auto font-mono text-sm">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">config.json:</span>
|
||||||
|
<pre className="mt-1 text-green-400">{`{
|
||||||
|
"debug": true,
|
||||||
|
"logLevel": "info"
|
||||||
|
}`}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">app.properties:</span>
|
||||||
|
<pre className="mt-1 text-green-400">{`app.name=MyApp
|
||||||
|
app.version=1.0.0
|
||||||
|
app.port=8080`}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml" className="h-full">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="metadata" className="h-full overflow-y-auto">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Metadata</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Name</span>
|
||||||
|
<span className="font-mono">{configMapName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Namespace</span>
|
||||||
|
<span className="font-mono">{namespace}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">UID</span>
|
||||||
|
<span className="font-mono text-xs">abc123-def456</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Created</span>
|
||||||
|
<span className="text-sm">2 hours ago</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Labels</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
<Badge variant="secondary">tier=frontend</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
131
src/components/Kubernetes/CreateResourceModal.tsx
Normal file
131
src/components/Kubernetes/CreateResourceModal.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Input } from "@/components/ui";
|
||||||
|
import { Label } from "@/components/ui";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface CreateResourceModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (resource: { type: string; name: string; namespace: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreateResourceModal({ isOpen, onClose, onSubmit }: CreateResourceModalProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("form");
|
||||||
|
const [resourceType, setResourceType] = React.useState("pod");
|
||||||
|
const [name, setName] = React.useState("");
|
||||||
|
const [namespace, setNamespace] = React.useState("default");
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
onSubmit({
|
||||||
|
type: resourceType,
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
|
<DialogContent className="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Kubernetes Resource</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-2 mb-4">
|
||||||
|
<TabsTrigger value="form">Form</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="max-h-[60vh] overflow-y-auto">
|
||||||
|
<TabsContent value="form" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="resourceType">Resource Type</Label>
|
||||||
|
<Select value={resourceType} onValueChange={setResourceType}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="pod">Pod</SelectItem>
|
||||||
|
<SelectItem value="deployment">Deployment</SelectItem>
|
||||||
|
<SelectItem value="service">Service</SelectItem>
|
||||||
|
<SelectItem value="configmap">ConfigMap</SelectItem>
|
||||||
|
<SelectItem value="secret">Secret</SelectItem>
|
||||||
|
<SelectItem value="ingress">Ingress</SelectItem>
|
||||||
|
<SelectItem value="pvc">PersistentVolumeClaim</SelectItem>
|
||||||
|
<SelectItem value="pv">PersistentVolume</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Enter resource name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="namespace">Namespace</Label>
|
||||||
|
<Select value={namespace} onValueChange={setNamespace}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select namespace" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">default</SelectItem>
|
||||||
|
<SelectItem value="kube-system">kube-system</SelectItem>
|
||||||
|
<SelectItem value="kube-public">kube-public</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-muted rounded-md">
|
||||||
|
<h4 className="text-sm font-medium mb-2">Configuration</h4>
|
||||||
|
<div className="space-y-2 text-sm text-muted-foreground">
|
||||||
|
<p>Resource Type: {resourceType}</p>
|
||||||
|
<p>Name: {name || "not specified"}</p>
|
||||||
|
<p>Namespace: {namespace}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Resource YAML</Label>
|
||||||
|
<div className="h-64">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-muted rounded-md">
|
||||||
|
<h4 className="text-sm font-medium mb-2">Preview</h4>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
YAML validation will be performed on submit
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit} disabled={!name}>
|
||||||
|
Create Resource
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Tabs>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
163
src/components/Kubernetes/DeploymentDetail.tsx
Normal file
163
src/components/Kubernetes/DeploymentDetail.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface DeploymentDetailProps {
|
||||||
|
deploymentName: string;
|
||||||
|
namespace: string;
|
||||||
|
_clusterId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeploymentDetail({ deploymentName, namespace, _clusterId, onClose }: DeploymentDetailProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("overview");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h2 className="text-xl font-semibold">Deployment: {deploymentName}</h2>
|
||||||
|
<Badge variant="outline">{namespace}</Badge>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-4 mb-4">
|
||||||
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||||
|
<TabsTrigger value="replicas">Replicas</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
<TabsTrigger value="events">Events</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<TabsContent value="overview" className="h-full overflow-y-auto">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Deployment Information</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Name</span>
|
||||||
|
<span className="font-mono">{deploymentName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Namespace</span>
|
||||||
|
<span className="font-mono">{namespace}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Replicas</span>
|
||||||
|
<span>3/3 Ready</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Strategy</span>
|
||||||
|
<span>RollingUpdate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Image</span>
|
||||||
|
<span className="font-mono">nginx:latest</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Created</span>
|
||||||
|
<span className="text-sm">2 hours ago</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Selector</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
<Badge variant="secondary">tier=frontend</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="lg:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Labels</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
<Badge variant="secondary">tier=frontend</Badge>
|
||||||
|
<Badge variant="secondary">version=v1</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="replicas" className="h-full overflow-y-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Ready</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{deploymentName}-abc123</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>1/1</TableCell>
|
||||||
|
<TableCell>2h</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{deploymentName}-def456</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>1/1</TableCell>
|
||||||
|
<TableCell>2h</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{deploymentName}-ghi789</TableCell>
|
||||||
|
<TableCell>Running</TableCell>
|
||||||
|
<TableCell>1/1</TableCell>
|
||||||
|
<TableCell>2h</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml" className="h-full">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="events" className="h-full overflow-y-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Time</TableHead>
|
||||||
|
<TableHead>Reason</TableHead>
|
||||||
|
<TableHead>Type</TableHead>
|
||||||
|
<TableHead>Message</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>2 hours ago</TableCell>
|
||||||
|
<TableCell>ScalingReplicaSet</TableCell>
|
||||||
|
<TableCell>Normal</TableCell>
|
||||||
|
<TableCell>Scaled up replica set {deploymentName}-abc123 to 3</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/components/Kubernetes/EditResourceModal.tsx
Normal file
110
src/components/Kubernetes/EditResourceModal.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Input } from "@/components/ui";
|
||||||
|
import { Label } from "@/components/ui";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface EditResourceModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (resource: { name: string; namespace: string }) => void;
|
||||||
|
initialData?: { name?: string; namespace?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditResourceModal({ isOpen, onClose, onSubmit, initialData }: EditResourceModalProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("form");
|
||||||
|
const [name, setName] = React.useState(initialData?.name || "");
|
||||||
|
const [namespace, setNamespace] = React.useState(initialData?.namespace || "default");
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
onSubmit({
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
|
<DialogContent className="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit Kubernetes Resource</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-2 mb-4">
|
||||||
|
<TabsTrigger value="form">Form</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="max-h-[60vh] overflow-y-auto">
|
||||||
|
<TabsContent value="form" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Enter resource name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="namespace">Namespace</Label>
|
||||||
|
<Select value={namespace} onValueChange={setNamespace}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select namespace" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">default</SelectItem>
|
||||||
|
<SelectItem value="kube-system">kube-system</SelectItem>
|
||||||
|
<SelectItem value="kube-public">kube-public</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-muted rounded-md">
|
||||||
|
<h4 className="text-sm font-medium mb-2">Resource Details</h4>
|
||||||
|
<div className="space-y-2 text-sm text-muted-foreground">
|
||||||
|
<p>Name: {name || "not specified"}</p>
|
||||||
|
<p>Namespace: {namespace}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Resource YAML</Label>
|
||||||
|
<div className="h-64">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-muted rounded-md">
|
||||||
|
<h4 className="text-sm font-medium mb-2">Preview</h4>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
YAML validation will be performed on submit
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit} disabled={!name}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Tabs>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
src/components/Kubernetes/Hotbar.tsx
Normal file
57
src/components/Kubernetes/Hotbar.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Settings, Bell, User, Search, Plus, RefreshCw } from "lucide-react";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { useKubernetesStore } from "@/stores/kubernetesStore";
|
||||||
|
import { useStore } from "zustand";
|
||||||
|
|
||||||
|
interface HotbarProps {
|
||||||
|
onRefresh: () => void;
|
||||||
|
onAddResource: () => void;
|
||||||
|
onSettings: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Hotbar({ onRefresh, onAddResource, onSettings }: HotbarProps) {
|
||||||
|
const clusters = useStore(useKubernetesStore, (state) => state.clusters);
|
||||||
|
const selectedClusterId = useStore(useKubernetesStore, (state) => state.selectedClusterId);
|
||||||
|
const selectedCluster = clusters.find((c: { id: string }) => c.id === selectedClusterId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-12 bg-background border-b flex items-center justify-between px-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button variant="ghost" size="sm" onClick={onRefresh}>
|
||||||
|
<RefreshCw className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onAddResource}>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="h-6 w-px bg-border mx-2" />
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Search className="w-4 h-4 text-muted-foreground" />
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{selectedCluster?.name || "No cluster selected"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button variant="ghost" size="sm">
|
||||||
|
<Bell className="w-4 h-4" />
|
||||||
|
<Badge variant="destructive" className="h-4 w-4 flex items-center justify-center p-0 text-[10px]">
|
||||||
|
3
|
||||||
|
</Badge>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onSettings}>
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm">
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/components/Kubernetes/LoadingSpinner.tsx
Normal file
22
src/components/Kubernetes/LoadingSpinner.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
|
interface LoadingSpinnerProps {
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingSpinner({ size = "md", text }: LoadingSpinnerProps) {
|
||||||
|
const sizes = {
|
||||||
|
sm: "w-4 h-4",
|
||||||
|
md: "w-8 h-8",
|
||||||
|
lg: "w-12 h-12",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-3">
|
||||||
|
<Loader2 className={`${sizes[size]} animate-spin text-primary`} />
|
||||||
|
{text && <p className="text-sm text-muted-foreground">{text}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
116
src/components/Kubernetes/RbacEditor.tsx
Normal file
116
src/components/Kubernetes/RbacEditor.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Plus, X, Check } from "lucide-react";
|
||||||
|
import { Input } from "@/components/ui";
|
||||||
|
|
||||||
|
interface RbacEditorProps {
|
||||||
|
_clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RbacEditor({ _clusterId, namespace, onClose }: RbacEditorProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("roles");
|
||||||
|
const [newRoleName, setNewRoleName] = React.useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2 className="text-2xl font-semibold">RBAC Editor</h2>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button variant="outline" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4 mr-2" />
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button>
|
||||||
|
<Check className="w-4 h-4 mr-2" />
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-4 mb-4">
|
||||||
|
<TabsTrigger value="roles">Roles</TabsTrigger>
|
||||||
|
<TabsTrigger value="clusterroles">ClusterRoles</TabsTrigger>
|
||||||
|
<TabsTrigger value="rolebindings">RoleBindings</TabsTrigger>
|
||||||
|
<TabsTrigger value="clusterrolebindings">ClusterRoleBindings</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<TabsContent value="roles" className="h-full flex flex-col">
|
||||||
|
<div className="mb-4 flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder="New role name"
|
||||||
|
value={newRoleName}
|
||||||
|
onChange={(e) => setNewRoleName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button disabled={!newRoleName}>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Create Role
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<div className="bg-card rounded-lg border flex flex-col h-full">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold">Role YAML Editor</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 bg-slate-900 p-4 font-mono text-sm text-green-400 overflow-auto">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">apiVersion:</span> rbac.authorization.k8s.io/v1
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">kind:</span> Role
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">metadata:</span>
|
||||||
|
</div>
|
||||||
|
<div className="pl-4">
|
||||||
|
<span className="text-blue-400">name:</span> {newRoleName || "role-name"}
|
||||||
|
</div>
|
||||||
|
<div className="pl-4">
|
||||||
|
<span className="text-blue-400">namespace:</span> {namespace}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">rules:</span>
|
||||||
|
</div>
|
||||||
|
<div className="pl-4">
|
||||||
|
<span className="text-blue-400">-</span> <span className="text-blue-400">apiGroups:</span> [""]
|
||||||
|
</div>
|
||||||
|
<div className="pl-6">
|
||||||
|
<span className="text-blue-400">resources:</span> ["pods"]
|
||||||
|
</div>
|
||||||
|
<div className="pl-6">
|
||||||
|
<span className="text-blue-400">verbs:</span> ["get", "list", "watch"]
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="clusterroles" className="h-full flex flex-col">
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
<p>ClusterRole editing would be displayed here</p>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="rolebindings" className="h-full flex flex-col">
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
<p>RoleBinding editing would be displayed here</p>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="clusterrolebindings" className="h-full flex flex-col">
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
<p>ClusterRoleBinding editing would be displayed here</p>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
196
src/components/Kubernetes/RbacViewer.tsx
Normal file
196
src/components/Kubernetes/RbacViewer.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Plus, Shield, User } from "lucide-react";
|
||||||
|
|
||||||
|
interface RbacViewerProps {
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RbacViewer({ clusterId, namespace }: RbacViewerProps) {
|
||||||
|
return (
|
||||||
|
<div className="h-full overflow-y-auto">
|
||||||
|
<div className="mb-6 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold">RBAC Management</h2>
|
||||||
|
<p className="text-muted-foreground">Cluster ID: {clusterId} | Namespace: {namespace}</p>
|
||||||
|
</div>
|
||||||
|
<Button>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Create Role
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold flex items-center gap-2">
|
||||||
|
<Shield className="w-5 h-5" />
|
||||||
|
Roles
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Namespace</TableHead>
|
||||||
|
<TableHead>Rules</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>pod-reader</TableCell>
|
||||||
|
<TableCell className="font-mono">{namespace}</TableCell>
|
||||||
|
<TableCell>get, list, watch pods</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>secret-viewer</TableCell>
|
||||||
|
<TableCell className="font-mono">{namespace}</TableCell>
|
||||||
|
<TableCell>get, list secrets</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>deployment-manager</TableCell>
|
||||||
|
<TableCell className="font-mono">{namespace}</TableCell>
|
||||||
|
<TableCell>get, list, create, update deployments</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold flex items-center gap-2">
|
||||||
|
<Shield className="w-5 h-5" />
|
||||||
|
ClusterRoles
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Rules</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>admin</TableCell>
|
||||||
|
<TableCell>Full access to all resources</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>edit</TableCell>
|
||||||
|
<TableCell>Modify resources in namespace</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>view</TableCell>
|
||||||
|
<TableCell>Read-only access to resources</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold flex items-center gap-2">
|
||||||
|
<User className="w-5 h-5" />
|
||||||
|
RoleBindings
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Role</TableHead>
|
||||||
|
<TableHead>Subjects</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>pod-reader-binding</TableCell>
|
||||||
|
<TableCell>pod-reader</TableCell>
|
||||||
|
<TableCell>user:alice</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>deployment-manager-binding</TableCell>
|
||||||
|
<TableCell>deployment-manager</TableCell>
|
||||||
|
<TableCell>group:devs</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg border">
|
||||||
|
<div className="border-b px-6 py-4">
|
||||||
|
<h3 className="font-semibold flex items-center gap-2">
|
||||||
|
<User className="w-5 h-5" />
|
||||||
|
ClusterRoleBindings
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>ClusterRole</TableHead>
|
||||||
|
<TableHead>Subjects</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>admin-binding</TableCell>
|
||||||
|
<TableCell>admin</TableCell>
|
||||||
|
<TableCell>group:admins</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>view-binding</TableCell>
|
||||||
|
<TableCell>view</TableCell>
|
||||||
|
<TableCell>group:auditors</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button variant="ghost" size="sm">Edit</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
src/components/Kubernetes/SecretDetail.tsx
Normal file
122
src/components/Kubernetes/SecretDetail.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface SecretDetailProps {
|
||||||
|
secretName: string;
|
||||||
|
namespace: string;
|
||||||
|
_clusterId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SecretDetail({ secretName, namespace, _clusterId, onClose }: SecretDetailProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("data");
|
||||||
|
const [showValues, setShowValues] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h2 className="text-xl font-semibold">Secret: {secretName}</h2>
|
||||||
|
<Badge variant="destructive">Secret</Badge>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-3 mb-4">
|
||||||
|
<TabsTrigger value="data">Data</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
<TabsTrigger value="metadata">Metadata</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<TabsContent value="data" className="h-full overflow-y-auto">
|
||||||
|
<Card className="h-full flex flex-col">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle>Secret Data</CardTitle>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => setShowValues(!showValues)}>
|
||||||
|
{showValues ? "Hide Values" : "Show Values"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-1 bg-slate-900 rounded-md p-4 overflow-auto font-mono text-sm">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">username:</span>
|
||||||
|
<span className="text-green-400 ml-2">
|
||||||
|
{showValues ? "admin" : "****"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">password:</span>
|
||||||
|
<span className="text-green-400 ml-2">
|
||||||
|
{showValues ? "secret123" : "****"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-blue-400">api-key:</span>
|
||||||
|
<span className="text-green-400 ml-2">
|
||||||
|
{showValues ? "sk-abc123xyz" : "****"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml" className="h-full">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="metadata" className="h-full overflow-y-auto">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Metadata</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Name</span>
|
||||||
|
<span className="font-mono">{secretName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Namespace</span>
|
||||||
|
<span className="font-mono">{namespace}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Type</span>
|
||||||
|
<Badge variant="secondary">Opaque</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Created</span>
|
||||||
|
<span className="text-sm">2 hours ago</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Labels</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
<Badge variant="secondary">tier=frontend</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
157
src/components/Kubernetes/ServiceDetail.tsx
Normal file
157
src/components/Kubernetes/ServiceDetail.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { YamlEditor } from "./YamlEditor";
|
||||||
|
|
||||||
|
interface ServiceDetailProps {
|
||||||
|
serviceName: string;
|
||||||
|
namespace: string;
|
||||||
|
_clusterId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServiceDetail({ serviceName, namespace, _clusterId, onClose }: ServiceDetailProps) {
|
||||||
|
const [activeTab, setActiveTab] = React.useState("overview");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h2 className="text-xl font-semibold">Service: {serviceName}</h2>
|
||||||
|
<Badge variant="outline">{namespace}</Badge>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
|
<TabsList className="grid grid-cols-4 mb-4">
|
||||||
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||||
|
<TabsTrigger value="endpoints">Endpoints</TabsTrigger>
|
||||||
|
<TabsTrigger value="yaml">YAML</TabsTrigger>
|
||||||
|
<TabsTrigger value="events">Events</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-hidden">
|
||||||
|
<TabsContent value="overview" className="h-full overflow-y-auto">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Service Information</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Name</span>
|
||||||
|
<span className="font-mono">{serviceName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Namespace</span>
|
||||||
|
<span className="font-mono">{namespace}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Type</span>
|
||||||
|
<Badge variant="secondary">ClusterIP</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Cluster IP</span>
|
||||||
|
<span className="font-mono">10.96.0.1</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">External IP</span>
|
||||||
|
<span className="text-muted-foreground">none</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">Port</span>
|
||||||
|
<span>80/TCP</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Selector</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="lg:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Labels</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge variant="secondary">app=web</Badge>
|
||||||
|
<Badge variant="secondary">tier=frontend</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="endpoints" className="h-full overflow-y-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>IP</TableHead>
|
||||||
|
<TableHead>Port</TableHead>
|
||||||
|
<TableHead>Node</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>10.0.0.1</TableCell>
|
||||||
|
<TableCell>80</TableCell>
|
||||||
|
<TableCell>node-1</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>10.0.0.2</TableCell>
|
||||||
|
<TableCell>80</TableCell>
|
||||||
|
<TableCell>node-2</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>10.0.0.3</TableCell>
|
||||||
|
<TableCell>80</TableCell>
|
||||||
|
<TableCell>node-3</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="yaml" className="h-full">
|
||||||
|
<YamlEditor onChange={() => {}} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="events" className="h-full overflow-y-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Time</TableHead>
|
||||||
|
<TableHead>Reason</TableHead>
|
||||||
|
<TableHead>Type</TableHead>
|
||||||
|
<TableHead>Message</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>2 hours ago</TableCell>
|
||||||
|
<TableCell>SettingClusterIP</TableCell>
|
||||||
|
<TableCell>Normal</TableCell>
|
||||||
|
<TableCell>Assigned cluster IP 10.96.0.1</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
src/components/Kubernetes/Toast.tsx
Normal file
66
src/components/Kubernetes/Toast.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { X, AlertCircle, CheckCircle, Info, AlertTriangle } from "lucide-react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
|
||||||
|
interface ToastProps {
|
||||||
|
message: string;
|
||||||
|
type?: "success" | "error" | "info" | "warning";
|
||||||
|
duration?: number;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Toast({ message, type = "info", duration = 5000, onClose }: ToastProps) {
|
||||||
|
const [visible, setVisible] = React.useState(true);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (duration > 0) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setVisible(false);
|
||||||
|
setTimeout(onClose, 300);
|
||||||
|
}, duration);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [duration, onClose]);
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
success: <CheckCircle className="w-5 h-5 text-green-500" />,
|
||||||
|
error: <AlertCircle className="w-5 h-5 text-red-500" />,
|
||||||
|
info: <Info className="w-5 h-5 text-blue-500" />,
|
||||||
|
warning: <AlertTriangle className="w-5 h-5 text-yellow-500" />,
|
||||||
|
};
|
||||||
|
|
||||||
|
const bgColors = {
|
||||||
|
success: "bg-green-50 border-green-200",
|
||||||
|
error: "bg-red-50 border-red-200",
|
||||||
|
info: "bg-blue-50 border-blue-200",
|
||||||
|
warning: "bg-yellow-50 border-yellow-200",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`fixed top-4 right-4 z-50 w-full max-w-sm transition-all duration-300 transform ${
|
||||||
|
visible ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Card className={`border-l-4 ${bgColors[type]} shadow-lg`}>
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{icons[type]}
|
||||||
|
<CardTitle className="text-sm font-medium">
|
||||||
|
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||||
|
</CardTitle>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => setVisible(false)}>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-sm text-muted-foreground">{message}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -31,3 +31,17 @@ export { SearchBar } from "./SearchBar";
|
|||||||
export { ContextSwitcher } from "./ContextSwitcher";
|
export { ContextSwitcher } from "./ContextSwitcher";
|
||||||
export { ApplicationView } from "./ApplicationView";
|
export { ApplicationView } from "./ApplicationView";
|
||||||
export { PodDetail } from "./PodDetail";
|
export { PodDetail } from "./PodDetail";
|
||||||
|
export { DeploymentDetail } from "./DeploymentDetail";
|
||||||
|
export { ServiceDetail } from "./ServiceDetail";
|
||||||
|
export { ConfigMapDetail } from "./ConfigMapDetail";
|
||||||
|
export { SecretDetail } from "./SecretDetail";
|
||||||
|
export { ClusterOverview } from "./ClusterOverview";
|
||||||
|
export { ClusterDetails } from "./ClusterDetails";
|
||||||
|
export { Hotbar } from "./Hotbar";
|
||||||
|
export { CommandPalette } from "./CommandPalette";
|
||||||
|
export { Toast } from "./Toast";
|
||||||
|
export { LoadingSpinner } from "./LoadingSpinner";
|
||||||
|
export { CreateResourceModal } from "./CreateResourceModal";
|
||||||
|
export { EditResourceModal } from "./EditResourceModal";
|
||||||
|
export { RbacViewer } from "./RbacViewer";
|
||||||
|
export { RbacEditor } from "./RbacEditor";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user