diff --git a/package-lock.json b/package-lock.json index d618e92e..9a99590a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "@types/jszip": "^3.4.0", "@types/multer": "^2.0.0", "@types/qrcode": "^1.5.5", - "@types/react-grid-layout": "^1.3.5", "@types/speakeasy": "^2.0.10", "@uiw/codemirror-extensions-langs": "^4.24.1", "@uiw/react-codemirror": "^4.24.1", @@ -69,7 +68,6 @@ "qrcode": "^1.5.4", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-grid-layout": "^1.5.2", "react-h5-audio-player": "^3.10.1", "react-hook-form": "^7.60.0", "react-i18next": "^15.7.3", @@ -3895,15 +3893,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/react-grid-layout": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", - "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/responselike": { "version": "1.0.3", "dev": true, @@ -7302,12 +7291,6 @@ "version": "3.1.3", "license": "MIT" }, - "node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "dev": true, @@ -11588,38 +11571,6 @@ "react": "^19.1.1" } }, - "node_modules/react-draggable": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", - "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/react-grid-layout": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.2.tgz", - "integrity": "sha512-vT7xmQqszTT+sQw/LfisrEO4le1EPNnSEMVHy6sBZyzS3yGkMywdOd+5iEFFwQwt0NSaGkxuRmYwa1JsP6OJdw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.1", - "fast-equals": "^4.0.3", - "prop-types": "^15.8.1", - "react-draggable": "^4.4.6", - "react-resizable": "^3.0.5", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, "node_modules/react-h5-audio-player": { "version": "3.10.1", "license": "MIT", @@ -11828,19 +11779,6 @@ } } }, - "node_modules/react-resizable": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", - "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", - "license": "MIT", - "dependencies": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - }, - "peerDependencies": { - "react": ">= 16.3" - } - }, "node_modules/react-resizable-panels": { "version": "3.0.6", "license": "MIT", @@ -12185,12 +12123,6 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "license": "MIT" - }, "node_modules/resolve-alpn": { "version": "1.2.1", "dev": true, diff --git a/package.json b/package.json index bdc1ce32..2ccb286a 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "@types/jszip": "^3.4.0", "@types/multer": "^2.0.0", "@types/qrcode": "^1.5.5", - "@types/react-grid-layout": "^1.3.5", "@types/speakeasy": "^2.0.10", "@uiw/codemirror-extensions-langs": "^4.24.1", "@uiw/react-codemirror": "^4.24.1", @@ -90,7 +89,6 @@ "qrcode": "^1.5.4", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-grid-layout": "^1.5.2", "react-h5-audio-player": "^3.10.1", "react-hook-form": "^7.60.0", "react-i18next": "^15.7.3", diff --git a/src/backend/database/routes/ssh.ts b/src/backend/database/routes/ssh.ts index 1cb5728d..870f423e 100644 --- a/src/backend/database/routes/ssh.ts +++ b/src/backend/database/routes/ssh.ts @@ -269,7 +269,7 @@ router.post( : null, enableFileManager: enableFileManager ? 1 : 0, defaultPath: defaultPath || null, - statsConfig: statsConfig || null, + statsConfig: statsConfig ? JSON.stringify(statsConfig) : null, }; if (effectiveAuthType === "password") { @@ -324,7 +324,9 @@ router.post( ? JSON.parse(createdHost.tunnelConnections) : [], enableFileManager: !!createdHost.enableFileManager, - statsConfig: createdHost.statsConfig || undefined, + statsConfig: createdHost.statsConfig + ? JSON.parse(createdHost.statsConfig) + : undefined, }; const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost; @@ -454,7 +456,7 @@ router.put( : null, enableFileManager: enableFileManager ? 1 : 0, defaultPath: defaultPath || null, - statsConfig: statsConfig || null, + statsConfig: statsConfig ? JSON.stringify(statsConfig) : null, }; if (effectiveAuthType === "password") { @@ -527,7 +529,9 @@ router.put( ? JSON.parse(updatedHost.tunnelConnections) : [], enableFileManager: !!updatedHost.enableFileManager, - statsConfig: updatedHost.statsConfig || undefined, + statsConfig: updatedHost.statsConfig + ? JSON.parse(updatedHost.statsConfig) + : undefined, }; const resolvedHost = (await resolveHostCredentials(baseHost)) || baseHost; @@ -596,7 +600,9 @@ router.get("/db/host", authenticateJWT, async (req: Request, res: Response) => { ? JSON.parse(row.tunnelConnections) : [], enableFileManager: !!row.enableFileManager, - statsConfig: row.statsConfig || undefined, + statsConfig: row.statsConfig + ? JSON.parse(row.statsConfig) + : undefined, }; return (await resolveHostCredentials(baseHost)) || baseHost; @@ -661,7 +667,9 @@ router.get( ? JSON.parse(host.tunnelConnections) : [], enableFileManager: !!host.enableFileManager, - statsConfig: host.statsConfig || undefined, + statsConfig: host.statsConfig + ? JSON.parse(host.statsConfig) + : undefined, }; res.json((await resolveHostCredentials(result)) || result); @@ -1431,6 +1439,9 @@ router.post( tunnelConnections: hostData.tunnelConnections ? JSON.stringify(hostData.tunnelConnections) : "[]", + statsConfig: hostData.statsConfig + ? JSON.stringify(hostData.statsConfig) + : null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index dd031389..b8904ee2 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -730,7 +730,10 @@ "folderRenamed": "Folder \"{{oldName}}\" renamed to \"{{newName}}\" successfully", "failedToRenameFolder": "Failed to rename folder", "movedToFolder": "Host \"{{name}}\" moved to \"{{folder}}\" successfully", - "failedToMoveToFolder": "Failed to move host to folder" + "failedToMoveToFolder": "Failed to move host to folder", + "statistics": "Statistics", + "enabledWidgets": "Enabled Widgets", + "enabledWidgetsDesc": "Select which statistics widgets to display for this host" }, "terminal": { "title": "Terminal", @@ -1143,6 +1146,7 @@ "loadAverageNA": "Avg: N/A", "cpuUsage": "CPU Usage", "memoryUsage": "Memory Usage", + "diskUsage": "Disk Usage", "rootStorageSpace": "Root Storage Space", "of": "of", "feedbackMessage": "Have ideas for what should come next for server management? Share them on", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 87cf6b0f..ec58bb9e 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -752,7 +752,10 @@ "folderRenamed": "文件夹\"{{oldName}}\"已成功重命名为\"{{newName}}\"", "failedToRenameFolder": "重命名文件夹失败", "movedToFolder": "主机\"{{name}}\"已成功移动到\"{{folder}}\"", - "failedToMoveToFolder": "移动主机到文件夹失败" + "failedToMoveToFolder": "移动主机到文件夹失败", + "statistics": "统计", + "enabledWidgets": "已启用组件", + "enabledWidgetsDesc": "选择要为此主机显示的统计组件" }, "terminal": { "title": "终端", @@ -1124,6 +1127,7 @@ "loadAverageNA": "平均: N/A", "cpuUsage": "CPU 使用率", "memoryUsage": "内存使用率", + "diskUsage": "磁盘使用率", "rootStorageSpace": "根目录存储空间", "of": "的", "feedbackMessage": "对服务器管理的下一步功能有想法?在这里分享吧", diff --git a/src/types/stats-widgets.ts b/src/types/stats-widgets.ts index 818c01d5..c5f86cef 100644 --- a/src/types/stats-widgets.ts +++ b/src/types/stats-widgets.ts @@ -1,53 +1,9 @@ -export type WidgetType = - | "cpu" // CPU 使用率 - | "memory" // 内存使用率 - | "disk"; // 磁盘使用率 -// 预留未来功能 -// | 'network' // 网络统计 -// | 'processes' // 进程数 -// | 'uptime'; // 运行时间 - -export type WidgetSize = "small" | "medium" | "large"; - -export interface Widget { - id: string; // 唯一 ID:"cpu-1", "memory-2" - type: WidgetType; // 卡片类型 - size: WidgetSize; // 尺寸:small/medium/large - x: number; // 网格X坐标 (0-11) - y: number; // 网格Y坐标 - w: number; // 宽度(网格单位 1-12) - h: number; // 高度(网格单位) -} +export type WidgetType = "cpu" | "memory" | "disk"; export interface StatsConfig { - widgets: Widget[]; + enabledWidgets: WidgetType[]; } export const DEFAULT_STATS_CONFIG: StatsConfig = { - widgets: [ - { id: "cpu-1", type: "cpu", size: "medium", x: 0, y: 0, w: 4, h: 2 }, - { id: "memory-1", type: "memory", size: "medium", x: 4, y: 0, w: 4, h: 2 }, - { id: "disk-1", type: "disk", size: "medium", x: 8, y: 0, w: 4, h: 2 }, - ], + enabledWidgets: ["cpu", "memory", "disk"], }; - -export const WIDGET_TYPE_CONFIG = { - cpu: { - label: "CPU Usage", - defaultSize: { w: 4, h: 2 }, - minSize: { w: 3, h: 2 }, - maxSize: { w: 12, h: 4 }, - }, - memory: { - label: "Memory Usage", - defaultSize: { w: 4, h: 2 }, - minSize: { w: 3, h: 2 }, - maxSize: { w: 12, h: 4 }, - }, - disk: { - label: "Disk Usage", - defaultSize: { w: 4, h: 2 }, - minSize: { w: 3, h: 2 }, - maxSize: { w: 12, h: 4 }, - }, -} as const; diff --git a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx index b5eff4da..660c1009 100644 --- a/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx +++ b/src/ui/Desktop/Apps/Host Manager/HostManagerEditor.tsx @@ -38,6 +38,9 @@ import { CredentialSelector } from "@/ui/Desktop/Apps/Credentials/CredentialSele import CodeMirror from "@uiw/react-codemirror"; import { oneDark } from "@codemirror/theme-one-dark"; import { EditorView } from "@codemirror/view"; +import type { StatsConfig, WidgetType } from "@/types/stats-widgets"; +import { DEFAULT_STATS_CONFIG } from "@/types/stats-widgets"; +import { Checkbox } from "@/components/ui/checkbox.tsx"; interface SSHHost { id: number; @@ -58,6 +61,7 @@ interface SSHHost { enableFileManager: boolean; defaultPath: string; tunnelConnections: any[]; + statsConfig?: StatsConfig; createdAt: string; updatedAt: string; credentialId?: number; @@ -210,6 +214,13 @@ export function HostManagerEditor({ .default([]), enableFileManager: z.boolean().default(true), defaultPath: z.string().optional(), + statsConfig: z + .object({ + enabledWidgets: z + .array(z.enum(["cpu", "memory", "disk"])) + .default(["cpu", "memory", "disk"]), + }) + .default({ enabledWidgets: ["cpu", "memory", "disk"] }), }) .superRefine((data, ctx) => { if (data.authType === "password") { @@ -292,6 +303,7 @@ export function HostManagerEditor({ enableFileManager: true, defaultPath: "/", tunnelConnections: [], + statsConfig: DEFAULT_STATS_CONFIG, }, }); @@ -348,6 +360,7 @@ export function HostManagerEditor({ enableFileManager: Boolean(cleanedHost.enableFileManager), defaultPath: cleanedHost.defaultPath || "/", tunnelConnections: cleanedHost.tunnelConnections || [], + statsConfig: cleanedHost.statsConfig || DEFAULT_STATS_CONFIG, }; if (defaultAuthType === "password") { @@ -383,6 +396,7 @@ export function HostManagerEditor({ enableFileManager: true, defaultPath: "/", tunnelConnections: [], + statsConfig: DEFAULT_STATS_CONFIG, }; form.reset(defaultFormData); @@ -421,6 +435,7 @@ export function HostManagerEditor({ enableFileManager: Boolean(data.enableFileManager), defaultPath: data.defaultPath || "/", tunnelConnections: data.tunnelConnections || [], + statsConfig: data.statsConfig || DEFAULT_STATS_CONFIG, }; submitData.credentialId = null; @@ -669,6 +684,9 @@ export function HostManagerEditor({ {t("hosts.fileManager")} + + {t("hosts.statistics")} + @@ -1533,6 +1551,48 @@ export function HostManagerEditor({ )} + + ( + + {t("hosts.enabledWidgets")} + + {t("hosts.enabledWidgetsDesc")} + +
+ {(["cpu", "memory", "disk"] as const).map((widget) => ( +
+ { + const currentWidgets = field.value || []; + if (checked) { + field.onChange([...currentWidgets, widget]); + } else { + field.onChange( + currentWidgets.filter((w) => w !== widget), + ); + } + }} + /> + +
+ ))} +
+
+ )} + /> +