From a5b4f4bb5dcf55491c7c112b60926e435e0ec2d2 Mon Sep 17 00:00:00 2001 From: beibeizi Date: Mon, 16 Mar 2026 01:16:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=80=82=E9=85=8D?= =?UTF-8?q?=EF=BC=9A=E4=BC=9A=E8=AF=9D=E4=B8=8E=E7=BB=88=E7=AB=AF=E5=88=86?= =?UTF-8?q?=E6=94=AF=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/FileBrowser.tsx | 141 +- frontend/src/components/SystemMonitor.tsx | 8 +- frontend/src/lib/hooks/useIsMobile.ts | 27 + frontend/src/pages/Sessions.tsx | 1255 +---------------- frontend/src/pages/Terminal.tsx | 1032 +------------- .../sessions/SessionsConnectionsPanel.tsx | 236 ++++ .../src/pages/sessions/SessionsDesktop.tsx | 230 +++ .../src/pages/sessions/SessionsDialogs.tsx | 122 ++ .../src/pages/sessions/SessionsMobile.tsx | 223 +++ frontend/src/pages/sessions/sessionsUtils.ts | 47 + .../src/pages/sessions/usePasswordDialog.ts | 61 + .../src/pages/sessions/useSessionsOrdering.ts | 165 +++ .../src/pages/sessions/useSessionsPolling.ts | 128 ++ .../src/pages/sessions/useSessionsState.ts | 490 +++++++ .../src/pages/terminal/TerminalDesktop.tsx | 193 +++ .../src/pages/terminal/TerminalMobile.tsx | 211 +++ frontend/src/pages/terminal/terminalUtils.ts | 160 +++ .../src/pages/terminal/useTerminalSession.ts | 177 +++ .../pages/terminal/useTerminalSessionInfo.ts | 162 +++ .../src/pages/terminal/useTerminalSocket.ts | 465 ++++++ 20 files changed, 3202 insertions(+), 2331 deletions(-) create mode 100644 frontend/src/lib/hooks/useIsMobile.ts create mode 100644 frontend/src/pages/sessions/SessionsConnectionsPanel.tsx create mode 100644 frontend/src/pages/sessions/SessionsDesktop.tsx create mode 100644 frontend/src/pages/sessions/SessionsDialogs.tsx create mode 100644 frontend/src/pages/sessions/SessionsMobile.tsx create mode 100644 frontend/src/pages/sessions/sessionsUtils.ts create mode 100644 frontend/src/pages/sessions/usePasswordDialog.ts create mode 100644 frontend/src/pages/sessions/useSessionsOrdering.ts create mode 100644 frontend/src/pages/sessions/useSessionsPolling.ts create mode 100644 frontend/src/pages/sessions/useSessionsState.ts create mode 100644 frontend/src/pages/terminal/TerminalDesktop.tsx create mode 100644 frontend/src/pages/terminal/TerminalMobile.tsx create mode 100644 frontend/src/pages/terminal/terminalUtils.ts create mode 100644 frontend/src/pages/terminal/useTerminalSession.ts create mode 100644 frontend/src/pages/terminal/useTerminalSessionInfo.ts create mode 100644 frontend/src/pages/terminal/useTerminalSocket.ts diff --git a/frontend/src/components/FileBrowser.tsx b/frontend/src/components/FileBrowser.tsx index 41304a6..1c9c6c2 100644 --- a/frontend/src/components/FileBrowser.tsx +++ b/frontend/src/components/FileBrowser.tsx @@ -59,6 +59,7 @@ type FileBrowserProps = { currentDir?: string; onFileSelect?: (path: string | null) => void; networkProfile?: NetworkProfile; + compact?: boolean; }; type UploadStats = { @@ -106,10 +107,11 @@ function createRootTree(): TreeNode[] { return [{ name: "/", path: "/", expanded: true, loaded: false }]; } -export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, networkProfile }: FileBrowserProps) { +export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, networkProfile, compact }: FileBrowserProps) { const { networkProfile: globalNetworkProfile, language } = useApp(); const t = React.useCallback((zh: string, en: string) => localizeText(language, zh, en), [language]); const effectiveNetworkProfile = networkProfile ?? globalNetworkProfile; + const isCompact = compact === true; const directoryCacheTtlMs = effectiveNetworkProfile === "poor" ? 12000 : effectiveNetworkProfile === "degraded" ? 6000 : 3000; const currentDirDebounceMs = @@ -1018,7 +1020,7 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo return (
+

{t("文件浏览", "File Browser")} @@ -1082,48 +1088,50 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo

{/* 路径输入框 */} -
+
setPathInput(e.target.value)} onKeyDown={handlePathSubmit} placeholder={t("输入路径,按回车跳转", "Input path and press Enter")} - className={`flex-1 px-2 py-1 text-xs rounded border ${ + className={`flex-1 px-2 ${isCompact ? "py-2 text-sm" : "py-1 text-xs"} rounded border ${ isDark ? "bg-slate-800 border-slate-600 text-slate-200 placeholder:text-slate-500 focus:border-indigo-500" : "bg-white border-slate-300 text-slate-700 placeholder:text-slate-400 focus:border-indigo-400" } focus:outline-none`} /> - + +
)} {/* 左侧树形目录 */} -
+
{tree.map(node => renderTreeNode(node))}
@@ -1276,23 +1284,29 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo {error}
) : ( - +
- - + - - - + {!isCompact ? ( + <> + + + + + ) : null} @@ -1303,14 +1317,18 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo onClick={handleParentItemClick} onDoubleClick={handleNavigateToParent} > - - - - - + + {!isCompact ? ( + <> + + + + + ) : null} )} {files.map((file) => ( @@ -1327,27 +1345,31 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo handleContextMenu(e, file); }} > - - - - - + {!isCompact ? ( + <> + + + + + ) : null} ))} {files.length === 0 && !filesLoading && ( - @@ -1475,4 +1497,3 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo ); } - diff --git a/frontend/src/components/SystemMonitor.tsx b/frontend/src/components/SystemMonitor.tsx index 57f8d5a..5b3c930 100644 --- a/frontend/src/components/SystemMonitor.tsx +++ b/frontend/src/components/SystemMonitor.tsx @@ -65,12 +65,14 @@ type SystemMonitorProps = { isDark: boolean; selectedFilePath?: string; networkProfile?: NetworkProfile; + compact?: boolean; }; -export function SystemMonitor({ sessionId, isDark, selectedFilePath, networkProfile }: SystemMonitorProps) { +export function SystemMonitor({ sessionId, isDark, selectedFilePath, networkProfile, compact }: SystemMonitorProps) { const { networkProfile: globalNetworkProfile, language } = useApp(); const t = React.useCallback((zh: string, en: string) => localizeText(language, zh, en), [language]); const effectiveNetworkProfile = networkProfile ?? globalNetworkProfile; + const isCompact = compact === true; const [stats, setStats] = React.useState(null); const [network, setNetwork] = React.useState(null); const [processes, setProcesses] = React.useState([]); @@ -235,7 +237,7 @@ export function SystemMonitor({ sessionId, isDark, selectedFilePath, networkProf if (!stats) return null; return ( -
+

{t("系统状态", "System Status")} @@ -385,7 +387,7 @@ export function SystemMonitor({ sessionId, isDark, selectedFilePath, networkProf - - - -

-
- -
-
-
- setSearch(event.target.value)} - className={`max-w-xs ${isDark ? "" : "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400"}`} - /> -
- {[ - { value: "all", label: t("全部", "All") }, - { value: "active", label: t("在线", "Active") }, - { value: "disconnected", label: t("离线", "Disconnected") } - ].map((item) => ( - - ))} -
-
- -
- {filteredSessions.map((session) => { - const noteValue = noteDrafts[session.id] ?? ""; - return ( -
{ - if (element) { - cardRefs.current.set(session.id, element); - } else { - cardRefs.current.delete(session.id); - } - }} - onDragOver={(event) => handleDragOver(session.id, event)} - onDrop={(event) => event.preventDefault()} - className={`relative rounded-lg border p-4 pl-12 transition-transform duration-200 ease-out ${ - draggingSessionId === session.id ? "ring-2 ring-indigo-400/60" : "" - } ${isDark ? "border-slate-700 bg-slate-900/60" : "border-slate-200 bg-white shadow-sm"}`} - > - -
-
-

{session.name}

-

- {session.username}@{session.host} -

-

{t("状态", "Status")}: {mapSessionStatus(session.status)}

- {session.enhanced_enabled ? ( -

- {t("增强持久化连接", "Enhanced persistent connection")} -

- ) : null} -

{t("创建时间", "Created at")}: {new Date(session.started_at).toLocaleString()}

-

{t("最近活动", "Last activity")}: {new Date(session.last_activity).toLocaleString()}

- {session.disconnected_at ? ( -

- {t("断开时间", "Disconnected at")}: {new Date(session.disconnected_at).toLocaleString()} -

- ) : null} - {session.enhanced_enabled && session.status !== "active" && session.allow_auto_retry !== false ? ( -

- {t("本轮重试", "Retry cycle")}: {session.retry_cycle_count ?? 0}/3 -

- ) : null} -
-
- {session.status === "active" ? ( - - ) : null} - {session.enhanced_enabled && session.status !== "active" && session.allow_auto_retry !== false ? ( - - ) : null} - -
-
-
- handleNoteChange(session.id, event.target.value)} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> -
- {t("最多 1000 字", "Up to 1000 characters")} - {noteValue.trim() !== (session.note ?? "") ? ( - - ) : null} -
-
-
- ); - })} - {filteredSessions.length === 0 ? ( -
{t("暂无会话", "No sessions")}
- ) : null} -
-
- - -
- - {showCreateForm ? ( -
-
- {t( - "请确保该项目所运行的机器可以访问到目标连接。", - "Make sure the machine running this project can reach the target connection." - )} -
- setForm({ ...form, name: event.target.value })} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> - setForm({ ...form, host: event.target.value })} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> - setForm({ ...form, port: Number(event.target.value) })} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> - setForm({ ...form, username: event.target.value })} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> -
- - -
- {form.auth_type === "password" ? ( - setForm({ ...form, password: event.target.value })} - className={!isDark ? "border-slate-300 bg-white text-slate-900 placeholder:text-slate-400" : ""} - /> - ) : ( - <> -
{t("名称", "Name")} + {t("名称", "Name")} {t("大小", "Size")} -
setResizingCol("size")} /> + {!isCompact ? ( +
setResizingCol("size")} /> + ) : null}
- {t("权限", "Permissions")} -
setResizingCol("permissions")} /> -
- {t("用户", "Owner")} -
setResizingCol("owner")} /> -
{t("修改时间", "Modified Time")} + {t("权限", "Permissions")} +
setResizingCol("permissions")} /> +
+ {t("用户", "Owner")} +
setResizingCol("owner")} /> +
{t("修改时间", "Modified Time")}
+ 📁 ... ---{t("返回上一级", "Go to parent")}---{t("返回上一级", "Go to parent")}
+ {file.is_dir ? "📁" : "📄"} {file.name} + {file.is_dir ? "-" : formatFileSize(file.size)} - {file.permissions} - - {file.owner} - - {file.modified} - + {file.permissions} + + {file.owner} + + {file.modified} +
+ {t("空目录", "Empty directory")}