Files
wild-web-app/src/router/pages/UtilitiesPage.tsx

203 lines
7.0 KiB
TypeScript

import { useState } from 'react';
import { useParams } from 'react-router';
import { UtilityCard, CopyableValue } from '../../components/UtilityCard';
import { Button } from '../../components/ui/button';
import {
Key,
Info,
Network,
Server,
Copy,
AlertCircle,
} from 'lucide-react';
import {
useDashboardToken,
useClusterVersions,
useNodeIPs,
useControlPlaneIP,
useCopySecret,
} from '../../services/api/hooks/useUtilities';
export function UtilitiesPage() {
const { instanceId } = useParams<{ instanceId: string }>();
const [secretToCopy, setSecretToCopy] = useState('');
const [sourceNamespace, setSourceNamespace] = useState('');
const [destinationNamespace, setDestinationNamespace] = useState('');
const dashboardToken = useDashboardToken(instanceId || '');
const versions = useClusterVersions(instanceId || '');
const nodeIPs = useNodeIPs(instanceId || '');
const controlPlaneIP = useControlPlaneIP(instanceId || '');
const copySecret = useCopySecret();
const handleCopySecret = () => {
if (secretToCopy && sourceNamespace && destinationNamespace && instanceId) {
copySecret.mutate({
instanceName: instanceId,
secret: secretToCopy,
sourceNamespace,
destinationNamespace
});
}
};
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight">Utilities</h2>
<p className="text-muted-foreground">
Additional tools and utilities for cluster management
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Dashboard Token */}
<UtilityCard
title="Dashboard Access Token"
description="Retrieve your Kubernetes dashboard authentication token"
icon={<Key className="h-5 w-5 text-primary" />}
isLoading={dashboardToken.isLoading}
error={dashboardToken.error}
>
{dashboardToken.data && (
<CopyableValue
label="Token"
value={dashboardToken.data.token}
multiline
/>
)}
</UtilityCard>
{/* Cluster Versions */}
<UtilityCard
title="Cluster Version Information"
description="View Kubernetes and Talos versions running on your cluster"
icon={<Info className="h-5 w-5 text-primary" />}
isLoading={versions.isLoading}
error={versions.error}
>
{versions.data && (
<div className="space-y-3">
<div className="flex justify-between items-center p-3 bg-muted rounded-lg">
<span className="text-sm font-medium">Kubernetes</span>
<span className="text-sm font-mono">{versions.data.version}</span>
</div>
{Object.entries(versions.data)
.filter(([key]) => key !== 'version')
.map(([key, value]) => (
<div
key={key}
className="flex justify-between items-center p-3 bg-muted rounded-lg"
>
<span className="text-sm font-medium capitalize">
{key.replace(/([A-Z])/g, ' $1').trim()}
</span>
<span className="text-sm font-mono">{String(value)}</span>
</div>
))}
</div>
)}
</UtilityCard>
{/* Node IPs */}
<UtilityCard
title="Node IP Addresses"
description="List all node IP addresses in your cluster"
icon={<Network className="h-5 w-5 text-primary" />}
isLoading={nodeIPs.isLoading}
error={nodeIPs.error}
>
{nodeIPs.data && (
<div className="space-y-2">
{nodeIPs.data.ips.map((ip, index) => (
<CopyableValue key={index} value={ip} label={`Node ${index + 1}`} />
))}
{nodeIPs.data.ips.length === 0 && (
<div className="flex items-center gap-2 text-muted-foreground text-sm">
<AlertCircle className="h-4 w-4" />
<span>No nodes found</span>
</div>
)}
</div>
)}
</UtilityCard>
{/* Control Plane IP */}
<UtilityCard
title="Control Plane IP"
description="Display the control plane endpoint IP address"
icon={<Server className="h-5 w-5 text-primary" />}
isLoading={controlPlaneIP.isLoading}
error={controlPlaneIP.error}
>
{controlPlaneIP.data && (
<CopyableValue label="Control Plane IP" value={controlPlaneIP.data.ip} />
)}
</UtilityCard>
{/* Secret Copy Utility */}
<UtilityCard
title="Copy Secret"
description="Copy a secret between namespaces"
icon={<Copy className="h-5 w-5 text-primary" />}
>
<div className="space-y-4">
<div>
<label className="text-sm font-medium mb-2 block">Secret Name</label>
<input
type="text"
placeholder="e.g., my-secret"
value={secretToCopy}
onChange={(e) => setSecretToCopy(e.target.value)}
className="w-full px-3 py-2 border rounded-lg bg-background"
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
Source Namespace
</label>
<input
type="text"
placeholder="e.g., default"
value={sourceNamespace}
onChange={(e) => setSourceNamespace(e.target.value)}
className="w-full px-3 py-2 border rounded-lg bg-background"
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
Destination Namespace
</label>
<input
type="text"
placeholder="e.g., production"
value={destinationNamespace}
onChange={(e) => setDestinationNamespace(e.target.value)}
className="w-full px-3 py-2 border rounded-lg bg-background"
/>
</div>
<Button
onClick={handleCopySecret}
disabled={!secretToCopy || !sourceNamespace || !destinationNamespace || copySecret.isPending}
className="w-full"
>
{copySecret.isPending ? 'Copying...' : 'Copy Secret'}
</Button>
{copySecret.isSuccess && (
<div className="text-sm text-green-600 dark:text-green-400">
Secret copied successfully!
</div>
)}
{copySecret.error && (
<div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle className="h-4 w-4" />
<span>{copySecret.error.message}</span>
</div>
)}
</div>
</UtilityCard>
</div>
</div>
);
}