From c9b815775404a2a35932329bbe2e2dd7bc7169c1 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sun, 7 Dec 2025 15:29:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99ssh=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E9=80=9A=E7=94=A8=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ui/toast.tsx | 4 +- frontend/src/components/ui/toaster.tsx | 28 +- .../Server/List/components/ServerCard.tsx | 157 +++--- .../List/components/ServerEditDialog.tsx | 459 ++++++++++-------- .../src/pages/Resource/Server/List/index.tsx | 2 + .../src/pages/Resource/Server/List/schema.ts | 49 +- 6 files changed, 408 insertions(+), 291 deletions(-) diff --git a/frontend/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx index 2e73c232..ed455000 100644 --- a/frontend/src/components/ui/toast.tsx +++ b/frontend/src/components/ui/toast.tsx @@ -42,12 +42,12 @@ const Toast = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & VariantProps ->(({ className, variant, ...props }, ref) => { +>(({ className, variant, duration = 3000, ...props }, ref) => { return ( ) diff --git a/frontend/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx index a5211957..4efa32af 100644 --- a/frontend/src/components/ui/toaster.tsx +++ b/frontend/src/components/ui/toaster.tsx @@ -13,18 +13,22 @@ export function Toaster() { return ( - {toasts.map(({ id, title, description, action, ...props }) => ( - -
- {title && {title}} - {description && ( - {description} - )} -
- {action} - -
- ))} + {toasts.map(({ id, title, description, action, ...props }) => { + // 确保duration有默认值,不被undefined覆盖 + const duration = props.duration ?? 3000 + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })}
) diff --git a/frontend/src/pages/Resource/Server/List/components/ServerCard.tsx b/frontend/src/pages/Resource/Server/List/components/ServerCard.tsx index 36d1502c..ceae4696 100644 --- a/frontend/src/pages/Resource/Server/List/components/ServerCard.tsx +++ b/frontend/src/pages/Resource/Server/List/components/ServerCard.tsx @@ -11,6 +11,10 @@ import { Loader2, ChevronDown, Terminal, + MonitorDot, + Tags, + Tag, + FileText, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; @@ -69,17 +73,21 @@ export const ServerCard: React.FC = ({ server, onTest, onEdit, {/* 基础信息 */}
-
+ {/* 左侧:操作系统大图标 */} +
{server.osType ? ( -
{getOsIcon(server.osType)}
+
{getOsIcon(server.osType)}
) : ( - + )}
-
+ + {/* 主要内容区域 */} +
+ {/* 第一行:状态徽章 + 分类 */}
= ({ server, onTest, onEdit, )}
+ + {/* 服务器名图标:NAS(QC-NAS) */} {renderValue( -

- {server.serverName} -

, +
+ + + {server.serverName} + {server.hostname && ( + ({server.hostname}) + )} + +
, 'w-32', { skeleton: true } )} -
- + + {/* IP图标:172.22.222.111 */} +
+ {renderValue( - {server.hostIp}, + {server.hostIp}, 'w-24', { skeleton: true } )}
- {server.hostname && ( - {server.hostname} - )} - {server.osType && ( - - {(server.osType && OsTypeLabels[server.osType]?.label) || server.osType} - {server.osVersion && ` ${server.osVersion}`} - - )} + + {/* 操作系统版本图标:Linux Debian... */} +
+ + {renderValue( + server.osType ? ( + + {OsTypeLabels[server.osType]?.label || server.osType} + {server.osVersion && ` ${server.osVersion}`} + + ) : null, + 'w-40', + { skeleton: true } + )} +
+ + {/* 标签图标:华为云 阿里云 */} +
+ + {renderValue( + (() => { + try { + const tags = server.tags ? JSON.parse(server.tags) : []; + if (Array.isArray(tags) && tags.length > 0) { + return ( +
+ {tags.slice(0, 3).map((tag: string, index: number) => ( + + {tag} + + ))} + {tags.length > 3 && ( + + +{tags.length - 3} + + )} +
+ ); + } + return null; + } catch { + return null; + } + })(), + 'w-32', + { skeleton: true } + )} +
+ + {/* 描述图标:测试 */} +
+ + {renderValue( + server.description ? ( +

+ {server.description} +

+ ) : null, + 'w-full', + { skeleton: true } + )} +
+ + {/* 右侧:展开按钮 */}
- {/* 展开内容 */} + {/* 展开内容 - 仅显示SSH信息和大图标配置 */} {expanded && (
+ {/* SSH连接信息 */}
{renderValue( @@ -241,10 +315,12 @@ export const ServerCard: React.FC = ({ server, onTest, onEdit, )}
+ + {/* 详细配置信息 - 大图标展示 */}
-
- +
+
{renderValue( server.cpuCores ? ( @@ -255,8 +331,8 @@ export const ServerCard: React.FC = ({ server, onTest, onEdit, )}
-
- +
+
{renderValue( server.memorySize ? ( @@ -267,8 +343,8 @@ export const ServerCard: React.FC = ({ server, onTest, onEdit, )}
-
- +
+
{renderValue( server.diskSize ? ( @@ -279,33 +355,6 @@ export const ServerCard: React.FC = ({ server, onTest, onEdit, )}
- - {server.tags && (() => { - try { - const tags = JSON.parse(server.tags); - if (Array.isArray(tags) && tags.length > 0) { - return ( -
- {tags.slice(0, 3).map((tag: string, index: number) => ( - - {tag} - - ))} - {tags.length > 3 && ( - +{tags.length - 3} - )} -
- ); - } - } catch { - return null; - } - return null; - })()} - - {server.description ? ( -

{server.description}

- ) : null}
)} diff --git a/frontend/src/pages/Resource/Server/List/components/ServerEditDialog.tsx b/frontend/src/pages/Resource/Server/List/components/ServerEditDialog.tsx index e9132720..c046a78c 100644 --- a/frontend/src/pages/Resource/Server/List/components/ServerEditDialog.tsx +++ b/frontend/src/pages/Resource/Server/List/components/ServerEditDialog.tsx @@ -29,11 +29,17 @@ import { SelectValue, } from '@/components/ui/select'; import { useToast } from '@/components/ui/use-toast'; +import { Separator } from '@/components/ui/separator'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; import type { ServerResponse, ServerCategoryResponse } from '../types'; import { OsType, OsTypeLabels, AuthType, AuthTypeLabels, ServerStatus } from '../types'; import { createServer, updateServer, getServerCategories } from '../service'; import { serverFormSchema, type ServerFormValues } from '../schema'; -import { Eye, EyeOff, X, Plus } from 'lucide-react'; +import { Eye, EyeOff, X, Plus, ChevronDown } from 'lucide-react'; interface ServerEditDialogProps { open: boolean; @@ -54,6 +60,7 @@ export const ServerEditDialog: React.FC = ({ const [showPassword, setShowPassword] = useState(false); const [showPassphrase, setShowPassphrase] = useState(false); const [tagInput, setTagInput] = useState(''); + const [systemInfoOpen, setSystemInfoOpen] = useState(true); const isEdit = !!server?.id; const form = useForm({ @@ -162,11 +169,7 @@ export const ServerEditDialog: React.FC = ({ onSuccess?.(); onOpenChange(false); } catch (error: any) { - toast({ - variant: 'destructive', - title: isEdit ? '更新失败' : '创建失败', - description: error.response?.data?.message || '操作失败', - }); + // 错误已在request拦截器中处理,这里不再重复显示toast } finally { setLoading(false); } @@ -181,25 +184,192 @@ export const ServerEditDialog: React.FC = ({
- -
- {/* 基本信息 */} - ( - - - 服务器名称 * - - - - - - - )} - /> + + {/* 基础信息区 */} +
+

+ 基础信息 +

+
+ ( + + + 服务器名称 * + + + + + + + )} + /> + ( + + 服务器分类 + + + + )} + /> + + ( + + + 操作系统类型 * + + + + + )} + /> + + {/* 标签 */} + ( + + 标签(可选) + +
+
+ setTagInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + if (tagInput.trim()) { + const currentTags = field.value ? JSON.parse(field.value) : []; + if (!currentTags.includes(tagInput.trim())) { + const newTags = [...currentTags, tagInput.trim()]; + field.onChange(JSON.stringify(newTags)); + } + setTagInput(''); + } + } + }} + /> + +
+ {field.value && (() => { + try { + const tags = JSON.parse(field.value); + return Array.isArray(tags) && tags.length > 0 && ( +
+ {tags.map((tag, index) => ( + + {tag} + { + const newTags = tags.filter((_, i) => i !== index); + field.onChange(newTags.length > 0 ? JSON.stringify(newTags) : undefined); + }} + /> + + ))} +
+ ); + } catch { + return null; + } + })()} +
+
+ +
+ )} + /> + + ( + + 描述 + +