From a80eea3a1ef3a64d878be82b4781da7e136f0e6f Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 10 Nov 2025 13:40:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=89=8D=E7=AB=AF=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/CLAUDE.md | 585 +++++++++++------- .../components/DeployFlowGraphModal.tsx | 233 +++++-- .../components/DeployNodeLogDialog.tsx | 29 +- 3 files changed, 543 insertions(+), 304 deletions(-) diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index e0bd40ea..658cf8a3 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -1,267 +1,394 @@ -# CLAUDE.md +--- +alwaysApply: true +--- +--- +alwaysApply: true +--- +# RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED) -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## 目录 +- [RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)](#riper-5--o1-thinking--agent-execution-protocol-optimized) + - [目录](#目录) + - [上下文与设置](#上下文与设置) + - [核心思维原则](#核心思维原则) + - [模式详解](#模式详解) + - [模式1: RESEARCH](#模式1-research) + - [模式2: INNOVATE](#模式2-innovate) + - [模式3: PLAN](#模式3-plan) + - [模式4: EXECUTE](#模式4-execute) + - [模式5: REVIEW](#模式5-review) + - [关键协议指南](#关键协议指南) + - [代码处理指南](#代码处理指南) + - [任务文件模板](#任务文件模板) + - [性能期望](#性能期望) -## Project Overview +## 上下文与设置 + -Deploy Ease Platform is a modern enterprise deployment management system frontend built with React 18, TypeScript, and Vite. It provides a comprehensive UI for managing applications, workflows, deployments, servers, and system configurations. +你是超智能AI编程助手,集成在Cursor IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。 -## Tech Stack +**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。 -- **Framework**: React 18 + TypeScript 5.3 -- **Build Tool**: Vite 5.x -- **Routing**: React Router 6 -- **State Management**: Redux Toolkit -- **UI Components**: Radix UI + Ant Design + shadcn/ui -- **Styling**: TailwindCSS 3.x -- **Forms**: React Hook Form + Zod validation -- **HTTP Client**: Axios (with centralized request handler in `src/utils/request.ts`) -- **Code Editor**: Monaco Editor -- **Flow Visualization**: XYFlow + ReactFlow + Dagre -- **Form Builder**: @react-form-builder libraries +**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。 -## Development Commands +**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]` -```bash -# Install dependencies -pnpm install +**初始默认模式**:除非另有指示,每次新对话默认从RESEARCH模式开始。然而,如果用户的初始请求非常明确地指向特定阶段(例如,提供了一个完整的计划要求执行),可以直接进入相应的模式(如 EXECUTE)。 -# Start development server (runs on http://localhost:3000) -pnpm dev +**代码修复指令**:请修复所有预期表达式问题,从第x行到第y行,请确保修复所有问题,不要遗漏任何问题。 -# Build for production (TypeScript + Vite) -pnpm build +## 核心思维原则 + -# Lint code (ESLint for .ts, .tsx files) -pnpm lint +在所有模式中,这些基本思维原则将指导你的操作: -# Preview production build locally -pnpm preview +- **系统思维**:从整体架构到具体实现进行分析 +- **辩证思维**:评估多种解决方案及其利弊 +- **创新思维**:打破常规模式,寻求创新解决方案 +- **批判思维**:从多角度验证和优化解决方案 -# Compile page generator script -npx tsc -p scripts/tsconfig.json +在所有响应中平衡这些方面: +- 分析与直觉 +- 细节检查与全局视角 +- 理论理解与实际应用 +- 深度思考与前进动力 +- 复杂性与清晰度 -# Run page generator (interactive mode) -node dist/scripts/generate-page.js +## 模式详解 + -# Run page generator (with CLI arguments) -node dist/scripts/generate-page.js "" "" +### 模式1: RESEARCH + + +**目的**:信息收集和深入理解 + +**核心思维应用**: +- 系统性地分解技术组件 +- 清晰地映射已知/未知元素 +- 考虑更广泛的架构影响 +- 识别关键技术约束和需求 + +**允许**: +- 阅读文件 +- 提出澄清问题 +- 理解代码结构 +- 分析系统架构 +- 识别技术债务或约束 +- 创建任务文件(参见下方任务文件模板) +- 使用文件工具创建或更新任务文件的‘Analysis’部分 + +**禁止**: +- 提出建议 +- 实施任何改变 +- 规划 +- 任何行动或解决方案的暗示 + +**研究协议步骤**: +1. 分析与任务相关的代码: + - 识别核心文件/功能 + - 追踪代码流程 + - 记录发现以供后续使用 + +**思考过程**: +```md +嗯... [系统思维方法的推理过程] ``` -## High-Level Architecture +**输出格式**: +以[MODE: RESEARCH]开始,然后仅提供观察和问题。 +使用markdown语法格式化答案。 +除非明确要求,否则避免使用项目符号。 -### Directory Structure +**持续时间**:自动在完成研究后进入INNOVATE模式 -``` -src/ -├── components/ # Reusable components -│ └── ui/ # Radix UI + shadcn/ui component library -├── pages/ # Page components (organized by feature module) -│ ├── Dashboard/ # Dashboard and overview -│ ├── Deploy/ # Deployment features (applications, deployments, servers, etc.) -│ ├── System/ # System management (users, roles, menus, departments, etc.) -│ ├── Workflow/ # Workflow engine (design, instance, monitoring) -│ ├── Form/ # Form management system -│ ├── LogStream/ # Log streaming and viewing -│ └── Login/ # Authentication -├── layouts/ # Layout components (BasicLayout, etc.) -├── router/ # React Router configuration (index.tsx) -├── store/ # Redux state management (user slice, etc.) -├── services/ # API service layer (weather.ts, etc.) -├── utils/ # Utility functions -│ ├── request.ts # Axios HTTP client with interceptors and error handling -│ ├── table.ts # Table-related utilities -│ ├── page.ts # Page-related utilities -│ └── jsonSchemaUtils.ts -├── hooks/ # Custom React hooks (useTableData, usePageData, etc.) -├── types/ # TypeScript type definitions -├── config/ # Configuration files (icons, etc.) -└── main.tsx # Application entry point +### 模式2: INNOVATE + + +**目的**:头脑风暴潜在方法 + +**核心思维应用**: +- 运用辩证思维探索多种解决路径 +- 应用创新思维打破常规模式 +- 平衡理论优雅与实际实现 +- 考虑技术可行性、可维护性和可扩展性 + +**允许**: +- 讨论多种解决方案想法 +- 评估优点/缺点 +- 寻求方法反馈 +- 探索架构替代方案 +- 在"提议的解决方案"部分记录发现 +- 使用文件工具更新任务文件的‘Proposed Solution’部分 + +**禁止**: +- 具体规划 +- 实现细节 +- 任何代码编写 +- 承诺特定解决方案 + +**创新协议步骤**: +1. 基于研究分析创建方案: + - 研究依赖关系 + - 考虑多种实现方法 + - 评估每种方法的利弊 + - 添加到任务文件的"提议的解决方案"部分 +2. 暂不进行代码更改 + +**思考过程**: +```md +嗯... [创造性、辩证的推理过程] ``` -### Architecture Patterns +**输出格式**: +以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。 +以自然流畅的段落呈现想法。 +保持不同解决方案元素之间的有机联系。 -#### 1. **Page Structure** -Each feature page follows a consistent pattern: +**持续时间**:自动在完成创新阶段后进入PLAN模式 + +### 模式3: PLAN + + +**目的**:创建详尽的技术规范 + +**核心思维应用**: +- 应用系统思维确保全面的解决方案架构 +- 使用批判思维评估和优化计划 +- 制定彻底的技术规范 +- 确保目标专注,将所有计划与原始需求连接起来 + +**允许**: +- 带有确切文件路径的详细计划 +- 精确的函数名称和签名 +- 具体的更改规范 +- 完整的架构概述 + +**禁止**: +- 任何实现或代码编写 +- 甚至"示例代码"也不可实现 +- 跳过或简化规范 + +**规划协议步骤**: +1. 查看"任务进度"历史(如果存在) +2. 详细规划下一步更改 +3. 提供明确理由和详细说明: + ``` + [更改计划] + - 文件:[更改的文件] + - 理由:[解释] + ``` + +**所需规划元素**: +- 文件路径和组件关系 +- 函数/类修改及其签名 +- 数据结构更改 +- 错误处理策略 +- 完整依赖管理 +- 测试方法 + +**强制最终步骤**: +将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目 + +**检查清单格式**: ``` -pages/[Feature]/[Module]/ -├── List/ -│ ├── index.tsx # Main list component -│ ├── components/ # Page-specific components (modals, etc.) -│ ├── service.ts # API service calls -│ ├── types.ts # TypeScript interfaces -│ └── schema.ts # Form validation schemas (Zod) -└── Detail/ # If applicable +实施检查清单: +1. [具体操作1] +2. [具体操作2] +... +n. [最终操作] ``` -#### 2. **API Layer** -All HTTP requests use the centralized `http` client from `src/utils/request.ts`: -- **Request Interceptor**: Automatically adds Bearer token from localStorage -- **Response Interceptor**: Handles standardized Response format with error handling -- **Error Handling**: Global error handling with toast notifications -- **Token Expiration**: Auto-logout on 401 with localStorage cleanup +**输出格式**: +以[MODE: PLAN]开始,然后仅提供规范和实现细节。 +使用markdown语法格式化答案。 -Example: -```typescript -import http from '@/utils/request'; -const data = await http.get('/api/endpoint'); -const result = await http.post('/api/endpoint', payload); +**持续时间**:自动在计划完成后进入EXECUTE模式 + +### 模式4: EXECUTE + + +**目的**:完全按照模式3中的计划实施 + +**核心思维应用**: +- 专注于精确实现规范 +- 在实现过程中应用系统验证 +- 保持对计划的精确遵守 +- 实现完整功能,包括适当的错误处理 + +**允许**: +- 仅实现已在批准的计划中明确详述的内容 +- 严格按照编号的检查清单执行 +- 标记已完成的检查清单项目 +- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤) + +**禁止**: +- 任何偏离计划的行为 +- 计划中未规定的改进 +- 创意补充或"更好的想法" +- 跳过或简化代码部分 + +**执行协议步骤**: +1. 完全按计划实施更改 +2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤): + ``` + [日期时间] + - 修改:[文件和代码更改列表] + - 更改:[更改的摘要] + - 原因:[更改的原因] + - 阻碍:[阻止此更新成功的因素列表] + - 状态:[未确认|成功|失败] + ``` +3. 要求用户确认:"状态:成功/失败?" +4. 如果失败:返回PLAN模式 +5. 如果成功且需要更多更改:继续下一项 +6. 如果所有实施完成:进入REVIEW模式 + +**代码质量标准**: +- 始终显示完整代码上下文 +- 在代码块中指定语言和路径 +- 适当的错误处理 +- 标准化命名约定 +- 清晰简洁的注释 +- 格式:```language:file_path + +**偏差处理**: +如果发现任何需要偏离的问题,立即返回PLAN模式 + +**输出格式**: +以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。 +包括已完成的检查清单项目。 + +### 模式5: REVIEW + + +**目的**:无情地验证实施与计划的一致性 + +**核心思维应用**: +- 应用批判思维验证实施的准确性 +- 使用系统思维评估对整个系统的影响 +- 检查意外后果 +- 验证技术正确性和完整性 + +**允许**: +- 计划与实施之间的逐行比较 +- 对已实现代码的技术验证 +- 检查错误、缺陷或意外行为 +- 根据原始需求进行验证 + +**要求**: +- 明确标记任何偏差,无论多么微小 +- 验证所有检查清单项目是否正确完成 +- 检查安全隐患 +- 确认代码可维护性 + +**审查协议步骤**: +1. 根据计划验证所有实施 +2. **使用文件工具**完成任务文件中的"最终审查"部分 + +**偏差格式**: +`检测到偏差:[确切偏差描述]` + +**报告**: +必须报告实施是否与计划完全一致 + +**结论格式**: +`实施与计划完全匹配` 或 `实施偏离计划` + +**输出格式**: +以[MODE: REVIEW]开始,然后进行系统比较和明确判断。 +使用markdown语法格式化。 + +## 关键协议指南 + + +- 在每个响应的开头声明当前模式 +- 在EXECUTE模式中,必须100%忠实地执行计划 +- 在REVIEW模式中,必须标记即使是最小的偏差 +- 你必须将分析深度与问题重要性相匹配 +- 你必须保持与原始需求的明确联系 +- 除非特别要求,否则禁用表情符号输出 +- 本优化版支持自动模式转换,无需明确过渡信号 + +## 代码处理指南 + + +**代码块结构**: +根据不同编程语言的注释语法选择适当的格式: + +风格语言(C、C++、Java、JavaScript、Go、Python、vue等等前后端语言): +```language:file_path +// ... existing code ... +{{ modifications }} +// ... existing code ... ``` -#### 3. **State Management** -Redux Toolkit is used minimally for global state (currently just user state): -```typescript -// src/store/userSlice.ts - User authentication and info -// Access via: useSelector((state: RootState) => state.user) +如果语言类型不确定,使用通用格式: +```language:file_path +[... existing code ...] +{{ modifications }} +[... existing code ...] ``` -Form-level state uses React Hook Form (not Redux) for better performance and reduced boilerplate. +**编辑指南**: +- 仅显示必要的修改 +- 包括文件路径和语言标识符 +- 提供上下文注释 +- 考虑对代码库的影响 +- 验证与请求的相关性 +- 保持范围合规性 +- 避免不必要的更改 -#### 4. **Routing** -React Router 6 with lazy-loaded pages and route guards: -- Private routes check Redux store for user token -- Pages are code-split with Suspense boundaries -- Dynamic menu loading from backend (stored in localStorage) +**禁止行为**: +- 使用未经验证的依赖项 +- 留下不完整的功能 +- 包含未测试的代码 +- 使用过时的解决方案 +- 在未明确要求时使用项目符号 +- 跳过或简化代码部分 +- 修改不相关的代码 +- 使用代码占位符 -#### 5. **Form Management** -- **React Hook Form**: Form state and submission -- **Zod**: Schema validation (type-safe) -- **Modal Pattern**: Most forms are in modal dialogs using Radix UI Dialog +## 任务文件模板 + -#### 6. **Styling** -- **TailwindCSS**: Utility-first CSS framework -- **Ant Design**: Used for complex components (Table, Form, etc.) -- **Radix UI**: Headless UI for modals, dialogs, popover, etc. -- **shadcn/ui**: Pre-built components built on Radix UI +``` +# 上下文 +文件名:[任务文件名] +创建于:[日期时间] +创建者:[用户名] +Yolo模式:[YOLO模式] -## Key Development Patterns +# 任务描述 +[用户完整任务描述] -### HTTP Requests with Error Handling -All HTTP requests automatically: -1. Include Bearer token from localStorage -2. Handle 401 responses (logout and redirect to /login) -3. Display error toast notifications -4. Return typed data or reject with standardized error structure +# 项目概述 +[用户输入的项目详情] -### Type-Safe API Responses -```typescript -interface Response { - code: number; - message: string; - data: T; - success: boolean; -} +⚠️ 警告:切勿修改此部分 ⚠️ +[本部分应包含RIPER-5协议规则的核心摘要,确保在执行过程中可以参考] +⚠️ 警告:切勿修改此部分 ⚠️ + +# 分析 +[代码调查结果] + +# 提议的解决方案 +[行动计划] + +# 当前执行步骤:"[步骤编号和名称]" +- 例如:"2. 创建任务文件" + +# 任务进度 +[带时间戳的更改历史] + +# 最终审查 +[完成后的总结] ``` -### Common Hooks -- `useTableData`: Fetch and manage table data with pagination -- `usePageData`: Generic page data fetching -- `useSelector` / `useDispatch`: Redux integration +## 性能期望 + -### Form Pattern Example -```typescript -// Use Zod for schema -const schema = z.object({ - name: z.string().min(1), - email: z.string().email(), -}); - -// Use React Hook Form with Zod resolver -const form = useForm({ - resolver: zodResolver(schema), -}); -``` - -## Important Files Reference - -- **`src/utils/request.ts`**: HTTP client - read this to understand API communication -- **`src/store/index.ts`**: Redux store configuration -- **`src/router/index.tsx`**: All routing configuration -- **`src/layouts/BasicLayout.tsx`**: Main application layout -- **`vite.config.ts`**: Build configuration (Monaco Editor asset copying, code splitting) -- **`tsconfig.json`**: TypeScript configuration with `@/*` path alias - -## Code Splitting & Performance - -The Vite build config includes manual chunks for the System module to optimize loading: -```typescript -// System pages grouped in separate bundle -manualChunks: { - 'system': [ - './src/pages/System/User/index.tsx', - './src/pages/System/Role/index.tsx', - // ... other system pages - ] -} -``` - -## Environment & Backend Integration - -- **Dev Server**: Runs on port 3000 with Vite HMR -- **API Proxy**: `/api/*` requests proxy to `http://localhost:8080` (configured in vite.config.ts) -- **LocalStorage**: Stores token, userInfo, menus, tenantId for persistence - -## Common Tasks - -### Adding a New CRUD Page -Use the page generator: -```bash -node dist/scripts/generate-page.js myFeature "My Feature" "/api/v1/my-feature" -``` - -This generates complete page structure with types, schema, service, and components. - -### Making an API Call -```typescript -import http from '@/utils/request'; - -// GET request -const users = await http.get('/api/users'); - -// POST with data -const newUser = await http.post('/api/users', userData); - -// PUT/DELETE -await http.put('/api/users/1', updatedData); -await http.delete('/api/users/1'); - -// File upload -await http.upload('/api/upload', file); - -// File download -await http.download('/api/export', 'export.xlsx'); -``` - -### Form Validation with Zod -```typescript -import { z } from 'zod'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; - -const schema = z.object({ - username: z.string().min(1, 'Required'), - email: z.string().email('Invalid email'), -}); - -const { register, handleSubmit, formState: { errors } } = useForm({ - resolver: zodResolver(schema), -}); -``` - -## Known Patterns & Conventions - -1. **Prefixes**: Type files are named `types.ts`, validation schemas `schema.ts`, API services `service.ts` -2. **Components**: UI components in `components/ui/`, page-specific components in page folders -3. **Async Operations**: Use async/await, error handling via request interceptor -4. **Imports**: Use `@/*` path alias extensively -5. **Chinese Locale**: Ant Design configured for zh_CN, toast messages in Chinese -6. **Modules**: Pages are organized by business module first, then by feature (List, Detail, Design, etc.) - -## Notes for Future Development - -- The page generator tool is powerful for rapid CRUD page creation -- Monaco Editor assets need to be copied at build time (see vite.config.ts) -- Token authentication is automatically handled; focus on feature logic -- All error handling is centralized; individual components don't need error boundaries for HTTP calls -- Form state is separate from Redux; Redux only for user/auth data -- The architecture supports adding new Redux slices as the app grows +- 响应延迟应最小化,理想情况下≤360000ms +- 最大化计算能力和令牌限制 +- 寻求本质洞察而非表面枚举 +- 追求创新思维而非习惯性重复 +- 突破认知限制,调动所有计算资源 \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx b/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx index aa247cec..2ee79f6e 100644 --- a/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx +++ b/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react'; -import { ReactFlowProvider, ReactFlow, Background, Node, Edge, Handle, Position, BackgroundVariant, NodeProps } from '@xyflow/react'; +import { ReactFlowProvider, ReactFlow, Background, Node, Edge, Handle, Position, BackgroundVariant, NodeProps, Controls, MiniMap } from '@xyflow/react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -7,31 +7,35 @@ import { Progress } from '@/components/ui/progress'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { - Loader2, - AlertCircle, - User, - Building2, - Layers, +import { Button } from '@/components/ui/button'; +import { + Loader2, + AlertCircle, + User, + Building2, + Layers, Calendar, Clock, CheckCircle2, XCircle, AlertTriangle, - FileText + FileText, + ArrowRightLeft, + ArrowDownUp } from 'lucide-react'; import { cn } from '@/lib/utils'; import '@xyflow/react/dist/style.css'; +import dagre from 'dagre'; import { getDeployRecordFlowGraph } from '../service'; import type { DeployRecordFlowGraph, WorkflowNodeInstance } from '../types'; -import { - getStatusIcon, - getStatusText, +import { + getStatusIcon, + getStatusText, getNodeStatusText, getNodeStatusColor, - formatTime, + formatTime, formatDuration, - calculateRunningDuration + calculateRunningDuration } from '../utils/dashboardUtils'; import DeployNodeLogDialog from './DeployNodeLogDialog'; @@ -54,6 +58,55 @@ interface CustomNodeData { onViewLog?: (nodeId: string, nodeName: string) => void; } +type LayoutDirection = 'TB' | 'LR'; + +/** + * 使用 dagre 进行自动布局 + */ +const getLayoutedElements = ( + nodes: Node[], + edges: Edge[], + direction: LayoutDirection = 'TB' +) => { + const dagreGraph = new dagre.graphlib.Graph(); + dagreGraph.setDefaultEdgeLabel(() => ({})); + + const nodeWidth = 180; + const nodeHeight = 120; + + const isHorizontal = direction === 'LR'; + dagreGraph.setGraph({ + rankdir: direction, + nodesep: 50, + ranksep: 80, + marginx: 20, + marginy: 20, + }); + + nodes.forEach((node) => { + dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); + }); + + edges.forEach((edge) => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + const layoutedNodes = nodes.map((node) => { + const nodeWithPosition = dagreGraph.node(node.id); + return { + ...node, + position: { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeight / 2, + }, + }; + }); + + return { nodes: layoutedNodes, edges }; +}; + /** * 自定义流程节点组件 */ @@ -63,11 +116,11 @@ const CustomFlowNode: React.FC = ({ data }) => { const isNotStarted = status === 'NOT_STARTED'; const isRunning = status === 'RUNNING'; const hasFailed = status === 'FAILED'; - + // 判断是否可以查看日志(具有日志输出能力的节点类型) const loggableNodeTypes = ['JENKINS_BUILD', 'ServiceTask']; const canViewLog = loggableNodeTypes.includes(nodeType) && status !== 'NOT_STARTED'; - + // 计算显示的时长 const displayDuration = useMemo(() => { if (duration !== null && duration !== undefined) { @@ -84,7 +137,7 @@ const CustomFlowNode: React.FC = ({ data }) => { const nodeContent = (
= ({ data }) => { > {/* 节点名称 */}
-
{nodeName}
+
{nodeName}
{canViewLog && ( )}
- + {/* 节点状态 */} -
{getNodeStatusText(status)}
- + {/* 时间信息 */} {!isNotStarted && (
- {startTime &&
开始: {formatTime(startTime)}
} - {endTime &&
结束: {formatTime(endTime)}
} {displayDuration && (
- {isRunning ? '运行中: ' : '时长: '} + {isRunning ? '运行: ' : '时长: '} {displayDuration}
)}
)} - + {/* 错误提示 */} {hasFailed && errorMessage && ( -
+
有错误
)} - + {/* 查看日志提示 */} {canViewLog && ( -
+
点击查看日志
)} @@ -401,7 +452,8 @@ export const DeployFlowGraphModal: React.FC = ({ }) => { const [loading, setLoading] = useState(false); const [flowData, setFlowData] = useState(null); - + const [layoutDirection, setLayoutDirection] = useState('TB'); + // 日志对话框状态 const [logDialogOpen, setLogDialogOpen] = useState(false); const [selectedNodeId, setSelectedNodeId] = useState(''); @@ -414,6 +466,11 @@ export const DeployFlowGraphModal: React.FC = ({ setLogDialogOpen(true); }; + // 切换布局方向 + const toggleLayoutDirection = () => { + setLayoutDirection(prev => prev === 'TB' ? 'LR' : 'TB'); + }; + // ReactFlow 节点点击事件处理 const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => { const nodeData = node.data as unknown as CustomNodeData; @@ -516,7 +573,7 @@ export const DeployFlowGraphModal: React.FC = ({ if (!flowData?.graph?.nodes || !flowData?.nodeInstances) return []; // 过滤并转换为 React Flow 节点 - return flowData.graph.nodes + const nodes = flowData.graph.nodes .filter(node => visibleNodeIds.has(node.id)) // 只显示可见节点 .map((node) => { const instance = nodeInstanceMap.get(node.id); @@ -538,6 +595,8 @@ export const DeployFlowGraphModal: React.FC = ({ }, }; }); + + return nodes; }, [flowData, nodeInstanceMap, visibleNodeIds]); // 转换为 React Flow 边(只显示连接可见节点的边) @@ -556,7 +615,7 @@ export const DeployFlowGraphModal: React.FC = ({ const targetInstance = nodeInstanceMap.get(target); const sourceStatus = sourceInstance?.status || 'NOT_STARTED'; const targetStatus = targetInstance?.status || 'NOT_STARTED'; - + // 根据节点状态确定边的样式 let strokeColor = '#d1d5db'; // 默认灰色 let strokeWidth = 2; @@ -566,7 +625,7 @@ export const DeployFlowGraphModal: React.FC = ({ // 源节点已完成 + 目标节点也已完成/运行中 = 绿色实线 if (sourceStatus === 'COMPLETED' && (targetStatus === 'COMPLETED' || targetStatus === 'RUNNING')) { strokeColor = '#10b981'; // 绿色 - } + } // 源节点 TERMINATED = 橙色实线 else if (sourceStatus === 'TERMINATED') { strokeColor = '#f59e0b'; // 橙色 @@ -574,7 +633,7 @@ export const DeployFlowGraphModal: React.FC = ({ // 源节点失败 = 红色实线 else if (sourceStatus === 'FAILED') { strokeColor = '#ef4444'; // 红色 - } + } // 源节点运行中 = 蓝色动画 else if (sourceStatus === 'RUNNING') { strokeColor = '#3b82f6'; // 蓝色 @@ -590,7 +649,7 @@ export const DeployFlowGraphModal: React.FC = ({ id: edge.id || `edge-${source}-${target}-${index}`, source, target, - type: 'straight', // 使用直线类型 + type: 'smoothstep', // 使用平滑曲线类型 animated, style: { stroke: strokeColor, @@ -607,6 +666,14 @@ export const DeployFlowGraphModal: React.FC = ({ }); }, [flowData, nodeInstanceMap, visibleNodeIds]); + // 应用自动布局 + const { nodes: layoutedNodes, edges: layoutedEdges } = useMemo(() => { + if (flowNodes.length === 0) { + return { nodes: [], edges: [] }; + } + return getLayoutedElements(flowNodes, flowEdges, layoutDirection); + }, [flowNodes, flowEdges, layoutDirection]); + // 获取部署状态信息 const deployStatusInfo = flowData ? (() => { @@ -624,27 +691,49 @@ export const DeployFlowGraphModal: React.FC = ({ - - {flowData && ( - - #{flowData.deployRecordId} - - )} - 部署流程图 - {deployStatusInfo && ( - - - {deployStatusInfo.text} - - )} + +
+ {flowData && ( + + #{flowData.deployRecordId} + + )} + 部署流程图 + {deployStatusInfo && ( + + + {deployStatusInfo.text} + + )} +
+ + {/* 布局切换按钮 */} +
@@ -665,13 +754,13 @@ export const DeployFlowGraphModal: React.FC = ({
= ({ zoomOnScroll={true} zoomOnPinch={true} preventScrolling={false} + minZoom={0.1} + maxZoom={2} > - + + { + const nodeData = node.data as unknown as CustomNodeData; + return getNodeStatusColor(nodeData.status); + }} + maskColor="rgba(0, 0, 0, 0.1)" + className="!shadow-lg !border !border-border" + style={{ + height: 100, + width: 150, + }} + />
diff --git a/frontend/src/pages/Dashboard/components/DeployNodeLogDialog.tsx b/frontend/src/pages/Dashboard/components/DeployNodeLogDialog.tsx index b7788b72..6d849139 100644 --- a/frontend/src/pages/Dashboard/components/DeployNodeLogDialog.tsx +++ b/frontend/src/pages/Dashboard/components/DeployNodeLogDialog.tsx @@ -8,7 +8,7 @@ import { DialogFooter, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; -import { ScrollArea } from '@/components/ui/scroll-area'; +import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; import { Loader2, AlertCircle, Clock, FileText, RefreshCw } from 'lucide-react'; import { cn } from '@/lib/utils'; import { getDeployNodeLogs } from '../service'; @@ -149,46 +149,46 @@ const DeployNodeLogDialog: React.FC = ({
) : ( -
+
{logData?.logs && logData.logs.length > 0 ? ( logData.logs.map((log, index) => { // 计算行号宽度 const lineNumWidth = Math.max(4, String(logData.logs.length).length + 1); - + // 格式化时间戳为可读格式:2025-11-07 16:27:41.494 const timestamp = dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS'); - + return ( -
{/* 行号 - 动态宽度,右对齐 */} - {index + 1} - + {/* 时间戳 - 可读格式,23个字符 (2025-11-07 16:27:41.494) */} - {timestamp} - + {/* 日志级别 - 5个字符,右对齐 */} - {log.level} - - {/* 日志消息 - 占据剩余空间,不换行 */} - + + {/* 日志消息 - 不换行显示 */} + {log.message}
@@ -202,6 +202,7 @@ const DeployNodeLogDialog: React.FC = ({
)}
+
)}