'use client'; import { useState, useCallback } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { apiClient } from '@/infrastructure/api/api-client'; import { queryKeys } from '@/infrastructure/api/query-keys'; import { cn } from '@/lib/utils'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- interface Cluster { id: string; name: string; description: string; environment: 'dev' | 'staging' | 'prod'; tags: string[]; serverIds: string[]; serverCount: number; healthySummary: { online: number; offline: number; maintenance: number }; createdAt: string; updatedAt: string; } interface Server { id: string; hostname: string; host: string; status: 'online' | 'offline' | 'maintenance'; environment: string; } type ClustersResponse = Cluster[]; type ServersResponse = Server[]; interface ClusterFormData { name: string; description: string; environment: 'dev' | 'staging' | 'prod'; tags: string; serverIds: string[]; } // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const EMPTY_FORM: ClusterFormData = { name: '', description: '', environment: 'dev', tags: '', serverIds: [], }; const CLUSTER_QUERY_KEY = ['clusters'] as const; const ENV_COLORS: Record = { dev: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', staging: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', prod: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', }; // --------------------------------------------------------------------------- // Environment badge // --------------------------------------------------------------------------- function EnvironmentBadge({ environment }: { environment: string }) { return ( {environment} ); } // --------------------------------------------------------------------------- // Health summary dots // --------------------------------------------------------------------------- function HealthSummary({ summary, }: { summary: Cluster['healthySummary']; }) { const { t } = useTranslation('servers'); const parts: { count: number; labelKey: string; color: string }[] = []; if (summary.online > 0) { parts.push({ count: summary.online, labelKey: 'clusters.health.online', color: 'bg-green-500' }); } if (summary.offline > 0) { parts.push({ count: summary.offline, labelKey: 'clusters.health.offline', color: 'bg-red-500' }); } if (summary.maintenance > 0) { parts.push({ count: summary.maintenance, labelKey: 'clusters.health.maintenance', color: 'bg-yellow-500' }); } if (parts.length === 0) { return ( {t('clusters.health.noServers')} ); } return (
{parts.map((part) => ( {part.count} {t(part.labelKey)} ))}
); } // --------------------------------------------------------------------------- // Cluster form dialog // --------------------------------------------------------------------------- function ClusterDialog({ open, title, form, errors, saving, servers, serversLoading, onClose, onChange, onToggleServer, onSubmit, }: { open: boolean; title: string; form: ClusterFormData; errors: Partial>; saving: boolean; servers: Server[]; serversLoading: boolean; onClose: () => void; onChange: (field: keyof ClusterFormData, value: string) => void; onToggleServer: (serverId: string) => void; onSubmit: () => void; }) { const { t } = useTranslation('servers'); const { t: tc } = useTranslation('common'); if (!open) return null; return (
{/* backdrop */}
{/* dialog */}

{title}

{/* name */}
onChange('name', e.target.value)} className={cn( 'w-full px-3 py-2 rounded-md border bg-background text-sm', errors.name ? 'border-destructive' : 'border-input', )} placeholder="Production Web Tier" /> {errors.name && (

{errors.name}

)}
{/* description */}