import { getErrorMessage } from "@/lib/error-message.js"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Boxes, Check, ChevronLeft, Download, ExternalLink, Loader2, Package, Play, Plus, RefreshCw, Search, Server, Settings2, Share2, Shield, Trash2, Upload, User, X, } from "lucide-react"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { Textarea } from "@/components/textarea"; import { Checkbox } from "@/components/checkbox"; import { Badge } from "@/components/badge"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogDescription, } from "@/components/dialog"; import { FOLDER_COLORS } from "@/lib/theme"; import { getSSHHosts } from "@/api/ssh-host-management-api"; import { getUserList } from "@/api/user-management-api"; import { getRoles, type SharePermissionLevel, type ShareTarget, } from "@/api/rbac-api"; import { Select2 } from "@/components/select2"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/dropdown-menu"; import { extractSnippetInputs, type SnippetInput, } from "@/lib/snippet-variables"; import { listFleets, createFleet, updateFleet, deleteFleet, getFleetMembers, addFleetMember, removeFleetMember, runFleetCommand, pushFleetFile, pullFleetFile, getFleetInventory, refreshFleetInventory, runFleetPackageAction, shareFleet, type FleetRow, type FleetMemberRow, type FleetHostResult, type FleetInventoryEntry, type FleetPackageAction, type FleetShareHostResult, } from "@/api/fleets-api"; import type { SSHHost } from "@/types/index"; const SHARE_PERMISSION_LEVELS: SharePermissionLevel[] = [ "connect", "view", "edit", "manage", ]; const SHARE_EXPIRY_PRESETS = [ { key: "never", hours: undefined }, { key: "oneHour", hours: 1 }, { key: "oneDay", hours: 24 }, { key: "sevenDays", hours: 24 * 7 }, { key: "thirtyDays", hours: 24 * 30 }, { key: "custom", hours: undefined }, ] as const; type ShareExpiryPresetKey = (typeof SHARE_EXPIRY_PRESETS)[number]["key"]; function formatUptime(seconds: number | null): string { if (seconds === null) return "-"; const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); if (d > 0) return `${d}d ${h}h`; if (h > 0) return `${h}h ${m}m`; return `${m}m`; } function ResultsList({ results }: { results: FleetHostResult[] }) { const { t } = useTranslation(); if (results.length === 0) return null; return (
{results.map((r) => (
{r.hostName} {r.success ? t("newUi.sidebar.fleets.resultSuccess") : t("newUi.sidebar.fleets.resultFailed")}
{(r.output || r.error) && (
              {r.error ? r.error : r.output}
            
)}
))}
); } function FleetFormDialog({ open, onClose, fleet, onSaved, }: { open: boolean; onClose: () => void; fleet: FleetRow | null; onSaved: () => void; }) { const { t } = useTranslation(); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [color, setColor] = useState(FOLDER_COLORS[0]); const [tagRulesText, setTagRulesText] = useState(""); const [saving, setSaving] = useState(false); useEffect(() => { if (!open) return; setName(fleet?.name ?? ""); setDescription(fleet?.description ?? ""); setColor(fleet?.color ?? FOLDER_COLORS[0]); setTagRulesText((fleet?.tagRules ?? []).join(", ")); }, [open, fleet]); async function handleSave() { if (!name.trim()) { toast.error(t("newUi.sidebar.fleets.nameRequired")); return; } const tagRules = tagRulesText .split(",") .map((tag) => tag.trim()) .filter(Boolean); setSaving(true); try { if (fleet) { await updateFleet(fleet.id, { name: name.trim(), description: description.trim() || null, color, tagRules, }); toast.success(t("newUi.sidebar.fleets.fleetUpdated")); } else { await createFleet({ name: name.trim(), description: description.trim() || null, color, tagRules, }); toast.success(t("newUi.sidebar.fleets.fleetCreated")); } onSaved(); onClose(); } catch (error) { const message = getErrorMessage(error, ""); toast.error(message || t("newUi.sidebar.fleets.saveFailed")); } finally { setSaving(false); } } return ( !next && onClose()}> {fleet ? t("newUi.sidebar.fleets.editFleetTitle") : t("newUi.sidebar.fleets.createFleetTitle")} {t("newUi.sidebar.fleets.createFleetDescription")}
setName(e.target.value)} placeholder={t("newUi.sidebar.fleets.namePlaceholder")} autoFocus />