import { useState } from "react"; import { useTranslation } from "react-i18next"; import { readFromClipboard, copyToClipboard } from "@/utils/clipboard"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { cn } from "@/lib/utils"; import { useSessionSecurityStore } from "@/store"; import { CheckCircle2, ClipboardPaste, Copy, FileUp, FolderDown, FolderUp, Upload, } from "lucide-react"; import { toast } from "sonner"; interface RdpToolsPanelProps { remoteClipboard: string; onSyncToRemote: (text: string) => void; onRequestRemoteCopy: () => void; onUploadVirtualFile: (name: string, base64: string) => boolean; } interface UploadState { name: string; sizeLabel: string; progress: number; status: "reading" | "sending" | "staged" | "failed"; } const RDP_UPLOAD_LIMIT = 50 * 1024 * 1024; function formatBytes(size: number) { if (size >= 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`; if (size >= 1024) return `${(size / 1024).toFixed(1)} KB`; return `${size} B`; } // RDP 工具面板:提供剪贴板同步和 Go-client 上的 AIOps 盘上传。 // Logan.Li export function RdpToolsPanel({ remoteClipboard, onSyncToRemote, onRequestRemoteCopy, onUploadVirtualFile, }: RdpToolsPanelProps) { const { t } = useTranslation(); const [localClipDraft, setLocalClipDraft] = useState(""); const [uploading, setUploading] = useState(false); const [uploadState, setUploadState] = useState(null); const [dragActive, setDragActive] = useState(false); const clipboardEnabled = useSessionSecurityStore( (state) => state.clipboardEnabled, ); const fileUploadEnabled = useSessionSecurityStore( (state) => state.fileUploadEnabled, ); const fileDownloadEnabled = useSessionSecurityStore( (state) => state.fileDownloadEnabled, ); const syncLocalClip = () => { const trimmed = localClipDraft.trim(); if (!trimmed) { toast.info(t("server.emptyContent")); return; } onSyncToRemote(trimmed); toast.success(t("server.syncedToRemote")); }; const uploadToVirtualDrive = async (file: File) => { if (file.size > RDP_UPLOAD_LIMIT) { toast.error( t("server.rdpUploadTooLarge", { limit: formatBytes(RDP_UPLOAD_LIMIT) }), ); return; } if (!fileUploadEnabled) { toast.info(t("server.sessionPolicy.fileUploadDisabled")); return; } setUploading(true); setUploadState({ name: file.name, sizeLabel: formatBytes(file.size), progress: 0, status: "reading", }); try { const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(new Error("read failed")); reader.onprogress = (event) => { if (!event.lengthComputable) return; // 当前链路先读入浏览器内存,进度条展示本地读取进度。 // Logan.Li const progress = Math.min( 90, Math.round((event.loaded / event.total) * 90), ); setUploadState((prev) => prev ? { ...prev, progress, status: "reading" } : prev, ); }; reader.onload = () => { const raw = String(reader.result || ""); resolve(raw.includes(",") ? raw.split(",")[1] : raw); }; reader.readAsDataURL(file); }); setUploadState((prev) => prev ? { ...prev, progress: 95, status: "sending" } : prev, ); const sent = onUploadVirtualFile(file.name, base64); if (!sent) throw new Error("websocket closed"); setUploadState((prev) => prev ? { ...prev, progress: 100, status: "staged" } : prev, ); toast.success(t("server.uploadedToVirtualDrive", { name: file.name })); } catch { setUploadState((prev) => (prev ? { ...prev, status: "failed" } : prev)); toast.error(t("server.rdpUploadFailed")); } finally { setUploading(false); } }; return (
{/* 剪贴板区块保持两个主动作,避免远端复制和本地粘贴混在一起。 // Logan.Li */}
{t("server.rdpClipboard")} {remoteClipboard ? ( {t("server.clipboardReady")} ) : null}
{/* 远端剪贴板预览 // Logan.Li */}