diff --git a/src/components/Kubernetes/ClusterDetails.tsx b/src/components/Kubernetes/ClusterDetails.tsx new file mode 100644 index 00000000..09a95aff --- /dev/null +++ b/src/components/Kubernetes/ClusterDetails.tsx @@ -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 ( +
+
+

Cluster Details

+

Cluster ID: {clusterId}

+
+ +
+
+
+

Basic Information

+
+
+
+
+ Name +

production-cluster

+
+
+ Region +

us-east-1

+
+
+ Kubernetes Version +

v1.28.4

+
+
+ Platform +

EKS

+
+
+ API Server +

https://abc123.gr7.us-east-1.eks.amazonaws.com

+
+
+ Status + Running +
+
+
+
+ +
+
+

Network Configuration

+
+
+
+
+ VPC ID +

vpc-0abc123def456

+
+
+ Subnets +
+ subnet-1 + subnet-2 + subnet-3 +
+
+
+ Security Groups +
+ sg-001 + sg-002 +
+
+
+ CIDR Block +

10.0.0.0/16

+
+
+
+
+ +
+
+

Node Configuration

+
+
+
+
+ Instance Type +

m5.xlarge

+
+
+ Min Nodes +

3

+
+
+ Max Nodes +

10

+
+
+ Autoscaling + Enabled +
+
+
+
+ +
+
+

Security Configuration

+
+
+
+
+ Network Policy + Enabled +
+
+ Pod Security Policy + Enabled +
+
+ RBAC + Enabled +
+
+ Secret Encryption + Enabled +
+
+
+
+
+ +
+
+

Node Pools

+
+
+ + + + Name + Instance Type + Nodes + Status + Auto-scaling + + + + + general-purpose + m5.xlarge + 3 + Running + Enabled + + + compute-optimized + c5.2xlarge + 2 + Running + Enabled + + + memory-optimized + r5.4xlarge + 2 + Running + Enabled + + +
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ClusterOverview.tsx b/src/components/Kubernetes/ClusterOverview.tsx new file mode 100644 index 00000000..22463f4d --- /dev/null +++ b/src/components/Kubernetes/ClusterOverview.tsx @@ -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 ( +
+
+

Cluster Overview

+

Cluster ID: {clusterId}

+
+ +
+
+
+

Nodes

+ +
+
15
+

+2 since last week

+
+ +
+
+

Pods

+ +
+
247
+

+15 since last week

+
+ +
+
+

Workloads

+ +
+
32
+

+4 since last week

+
+
+ +
+ + +
+ +
+
+

Cluster Resources

+
+
+
+
+

Allocatable Resources

+
+
+ CPU (cores) + 32 +
+
+ Memory (GB) + 128 +
+
+ Pods + 110 +
+
+
+
+

Used Resources

+
+
+ CPU (cores) + 18.5 (58%) +
+
+ Memory (GB) + 52.3 (41%) +
+
+ Pods + 247 (22%) +
+
+
+
+
+
+ +
+
+

Recent Events

+
+
+
+
+ 5 minutes ago + NodeReady + Normal + Node node-1 is ready +
+
+ 1 hour ago + Pulled + Normal + Container image pulled successfully +
+
+ 2 hours ago + ScalingReplicaSet + Normal + Scaled up deployment web-app +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/CommandPalette.tsx b/src/components/Kubernetes/CommandPalette.tsx new file mode 100644 index 00000000..8d716394 --- /dev/null +++ b/src/components/Kubernetes/CommandPalette.tsx @@ -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 ( +
+
+
+
+ +

Command Palette

+
+ +
+
+
+ setQuery(e.target.value)} + placeholder="Type a command or search..." + autoFocus + className="pl-10" + /> + +
+ +
+ {filteredCommands.length === 0 ? ( +
+ No commands found +
+ ) : ( + filteredCommands.map((cmd, index) => ( +
{ + onCommand(cmd.command); + onClose(); + }} + > + {cmd.name} + + {cmd.command} + +
+ )) + )} +
+ +
+
+ + + to navigate +
+
+ + to select +
+
+ esc + to close +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ConfigMapDetail.tsx b/src/components/Kubernetes/ConfigMapDetail.tsx new file mode 100644 index 00000000..fab03bf0 --- /dev/null +++ b/src/components/Kubernetes/ConfigMapDetail.tsx @@ -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 ( +
+
+
+

ConfigMap: {configMapName}

+ {namespace} +
+ +
+ + + + Data + YAML + Metadata + + +
+ + + + ConfigMap Data + + +
+
+ config.json: +
{`{
+  "debug": true,
+  "logLevel": "info"
+}`}
+
+
+ app.properties: +
{`app.name=MyApp
+app.version=1.0.0
+app.port=8080`}
+
+
+
+
+
+ + + {}} /> + + + +
+ + + Metadata + + +
+ Name + {configMapName} +
+
+ Namespace + {namespace} +
+
+ UID + abc123-def456 +
+
+ Created + 2 hours ago +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/CreateResourceModal.tsx b/src/components/Kubernetes/CreateResourceModal.tsx new file mode 100644 index 00000000..7898bad2 --- /dev/null +++ b/src/components/Kubernetes/CreateResourceModal.tsx @@ -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 ( + + + + Create Kubernetes Resource + + + + + Form + YAML + + +
+ +
+
+ + +
+ +
+ + setName(e.target.value)} + placeholder="Enter resource name" + /> +
+ +
+ + +
+
+ +
+

Configuration

+
+

Resource Type: {resourceType}

+

Name: {name || "not specified"}

+

Namespace: {namespace}

+
+
+
+ + +
+
+ +
+ {}} /> +
+
+
+

Preview

+
+ YAML validation will be performed on submit +
+
+
+
+
+ + + + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/DeploymentDetail.tsx b/src/components/Kubernetes/DeploymentDetail.tsx new file mode 100644 index 00000000..ee144058 --- /dev/null +++ b/src/components/Kubernetes/DeploymentDetail.tsx @@ -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 ( +
+
+
+

Deployment: {deploymentName}

+ {namespace} +
+ +
+ + + + Overview + Replicas + YAML + Events + + +
+ +
+ + + Deployment Information + + +
+ Name + {deploymentName} +
+
+ Namespace + {namespace} +
+
+ Replicas + 3/3 Ready +
+
+ Strategy + RollingUpdate +
+
+ Image + nginx:latest +
+
+ Created + 2 hours ago +
+
+
+ + + + Selector + + +
+ app=web + tier=frontend +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend + version=v1 +
+
+
+
+
+ + + + + + Name + Status + Ready + Age + + + + + {deploymentName}-abc123 + Running + 1/1 + 2h + + + {deploymentName}-def456 + Running + 1/1 + 2h + + + {deploymentName}-ghi789 + Running + 1/1 + 2h + + +
+
+ + + {}} /> + + + + + + + Time + Reason + Type + Message + + + + + 2 hours ago + ScalingReplicaSet + Normal + Scaled up replica set {deploymentName}-abc123 to 3 + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/EditResourceModal.tsx b/src/components/Kubernetes/EditResourceModal.tsx new file mode 100644 index 00000000..02e19217 --- /dev/null +++ b/src/components/Kubernetes/EditResourceModal.tsx @@ -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 ( + + + + Edit Kubernetes Resource + + + + + Form + YAML + + +
+ +
+
+ + setName(e.target.value)} + placeholder="Enter resource name" + /> +
+ +
+ + +
+
+ +
+

Resource Details

+
+

Name: {name || "not specified"}

+

Namespace: {namespace}

+
+
+
+ + +
+
+ +
+ {}} /> +
+
+
+

Preview

+
+ YAML validation will be performed on submit +
+
+
+
+
+ + + + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/Hotbar.tsx b/src/components/Kubernetes/Hotbar.tsx new file mode 100644 index 00000000..cf3a67b5 --- /dev/null +++ b/src/components/Kubernetes/Hotbar.tsx @@ -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 ( +
+
+
+ + +
+
+
+ + + {selectedCluster?.name || "No cluster selected"} + +
+
+ +
+
+ + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/LoadingSpinner.tsx b/src/components/Kubernetes/LoadingSpinner.tsx new file mode 100644 index 00000000..7c575c21 --- /dev/null +++ b/src/components/Kubernetes/LoadingSpinner.tsx @@ -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 ( +
+ + {text &&

{text}

} +
+ ); +} diff --git a/src/components/Kubernetes/RbacEditor.tsx b/src/components/Kubernetes/RbacEditor.tsx new file mode 100644 index 00000000..5a9e71d5 --- /dev/null +++ b/src/components/Kubernetes/RbacEditor.tsx @@ -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 ( +
+
+

RBAC Editor

+
+ + +
+
+ + + + Roles + ClusterRoles + RoleBindings + ClusterRoleBindings + + +
+ +
+ setNewRoleName(e.target.value)} + /> + +
+ +
+
+
+

Role YAML Editor

+
+
+
+
+ apiVersion: rbac.authorization.k8s.io/v1 +
+
+ kind: Role +
+
+ metadata: +
+
+ name: {newRoleName || "role-name"} +
+
+ namespace: {namespace} +
+
+ rules: +
+
+ - apiGroups: [""] +
+
+ resources: ["pods"] +
+
+ verbs: ["get", "list", "watch"] +
+
+
+
+
+
+ + +
+

ClusterRole editing would be displayed here

+
+
+ + +
+

RoleBinding editing would be displayed here

+
+
+ + +
+

ClusterRoleBinding editing would be displayed here

+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/RbacViewer.tsx b/src/components/Kubernetes/RbacViewer.tsx new file mode 100644 index 00000000..996e101c --- /dev/null +++ b/src/components/Kubernetes/RbacViewer.tsx @@ -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 ( +
+
+
+

RBAC Management

+

Cluster ID: {clusterId} | Namespace: {namespace}

+
+ +
+ +
+
+
+

+ + Roles +

+
+
+ + + + Name + Namespace + Rules + Actions + + + + + pod-reader + {namespace} + get, list, watch pods + + + + + + secret-viewer + {namespace} + get, list secrets + + + + + + deployment-manager + {namespace} + get, list, create, update deployments + + + + + +
+
+
+ +
+
+

+ + ClusterRoles +

+
+
+ + + + Name + Rules + Actions + + + + + admin + Full access to all resources + + + + + + edit + Modify resources in namespace + + + + + + view + Read-only access to resources + + + + + +
+
+
+ +
+
+

+ + RoleBindings +

+
+
+ + + + Name + Role + Subjects + Actions + + + + + pod-reader-binding + pod-reader + user:alice + + + + + + deployment-manager-binding + deployment-manager + group:devs + + + + + +
+
+
+ +
+
+

+ + ClusterRoleBindings +

+
+
+ + + + Name + ClusterRole + Subjects + Actions + + + + + admin-binding + admin + group:admins + + + + + + view-binding + view + group:auditors + + + + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/SecretDetail.tsx b/src/components/Kubernetes/SecretDetail.tsx new file mode 100644 index 00000000..0c91a43a --- /dev/null +++ b/src/components/Kubernetes/SecretDetail.tsx @@ -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 ( +
+
+
+

Secret: {secretName}

+ Secret +
+ +
+ + + + Data + YAML + Metadata + + +
+ + + +
+ Secret Data + +
+
+ +
+
+ username: + + {showValues ? "admin" : "****"} + +
+
+ password: + + {showValues ? "secret123" : "****"} + +
+
+ api-key: + + {showValues ? "sk-abc123xyz" : "****"} + +
+
+
+
+
+ + + {}} /> + + + +
+ + + Metadata + + +
+ Name + {secretName} +
+
+ Namespace + {namespace} +
+
+ Type + Opaque +
+
+ Created + 2 hours ago +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ServiceDetail.tsx b/src/components/Kubernetes/ServiceDetail.tsx new file mode 100644 index 00000000..613c5548 --- /dev/null +++ b/src/components/Kubernetes/ServiceDetail.tsx @@ -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 ( +
+
+
+

Service: {serviceName}

+ {namespace} +
+ +
+ + + + Overview + Endpoints + YAML + Events + + +
+ +
+ + + Service Information + + +
+ Name + {serviceName} +
+
+ Namespace + {namespace} +
+
+ Type + ClusterIP +
+
+ Cluster IP + 10.96.0.1 +
+
+ External IP + none +
+
+ Port + 80/TCP +
+
+
+ + + + Selector + + +
+ app=web +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+ + + + + + IP + Port + Node + + + + + 10.0.0.1 + 80 + node-1 + + + 10.0.0.2 + 80 + node-2 + + + 10.0.0.3 + 80 + node-3 + + +
+
+ + + {}} /> + + + + + + + Time + Reason + Type + Message + + + + + 2 hours ago + SettingClusterIP + Normal + Assigned cluster IP 10.96.0.1 + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/Toast.tsx b/src/components/Kubernetes/Toast.tsx new file mode 100644 index 00000000..f15499cb --- /dev/null +++ b/src/components/Kubernetes/Toast.tsx @@ -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: , + error: , + info: , + warning: , + }; + + 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 ( +
+ + +
+
+ {icons[type]} + + {type.charAt(0).toUpperCase() + type.slice(1)} + +
+ +
+
+ +

{message}

+
+
+
+ ); +} diff --git a/src/components/Kubernetes/index.tsx b/src/components/Kubernetes/index.tsx index 3def9745..3619ee2d 100644 --- a/src/components/Kubernetes/index.tsx +++ b/src/components/Kubernetes/index.tsx @@ -31,3 +31,17 @@ export { SearchBar } from "./SearchBar"; export { ContextSwitcher } from "./ContextSwitcher"; export { ApplicationView } from "./ApplicationView"; 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";