Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b5ab072fa | ||
|
|
1c8952e063 | ||
|
|
4385f9aa19 | ||
|
|
7609c35d7d | ||
|
|
73b8e6ad47 | ||
|
|
6ad31778a2 | ||
|
|
6042967b33 | ||
|
|
20736f0fb1 | ||
|
|
fca4c517e4 | ||
|
|
813f24751f | ||
|
|
364cbfe6c8 | ||
|
|
58d41e5852 | ||
|
|
7c2b7fb4cf | ||
|
|
a99f049589 | ||
|
|
2512ef72b7 | ||
|
|
4409d6ed5b | ||
|
|
4acfd06184 | ||
|
|
5450c0e248 | ||
|
|
cc697ad399 | ||
|
|
7d5a1d0c6f | ||
|
|
dc2ed9b970 | ||
|
|
d1a89cda51 | ||
|
|
bd220050c7 | ||
|
|
c4020f2725 | ||
|
|
e36c82c123 | ||
|
|
c8f8ffc3e8 | ||
|
|
aaa4f9e202 | ||
|
|
89831aee21 | ||
|
|
8ff1902937 | ||
|
|
b5fe33ce47 | ||
|
|
d3586b4c77 | ||
|
|
e127003ee9 | ||
|
|
9125cd7ae4 | ||
|
|
d1c01ea847 | ||
|
|
1dea91f295 | ||
|
|
ff795eb21e | ||
|
|
c0fa083328 | ||
|
|
ad9887b102 | ||
|
|
4ea20216f1 | ||
|
|
676d1362d8 | ||
|
|
7b9db415b1 | ||
|
|
70deedfdb1 | ||
|
|
64afcc47d9 | ||
|
|
3df45e9767 | ||
|
|
f4858ee73f | ||
|
|
3eface01cd | ||
|
|
725c0c6f79 | ||
|
|
76b9e76bf7 | ||
|
|
c8f4060988 | ||
|
|
1dba371374 | ||
|
|
2ffa8724af | ||
|
|
73cca10bbb | ||
|
|
f368fd590f | ||
|
|
b04e5f7fa6 | ||
|
|
a610fb6e08 | ||
|
|
68ab70c7eb | ||
|
|
c7ad2ac2ca | ||
|
|
2d79408644 | ||
|
|
86b8fb7485 | ||
|
|
0349b0c75c | ||
|
|
559133bcb2 | ||
|
|
126e571ed1 | ||
|
|
5692db6621 | ||
|
|
7545ead561 | ||
|
|
e3531e9e96 | ||
|
|
92d51710c1 | ||
|
|
45ec4b0321 | ||
|
|
fa492b1528 | ||
|
|
361e00de1f | ||
|
|
abe175d87f | ||
|
|
3e5f33851c | ||
|
|
d7b399ef76 | ||
|
|
44ea460b28 | ||
|
|
5cf474c7c2 | ||
|
|
e957ae978c | ||
|
|
6043ddf69e | ||
|
|
5b9fd5a12d | ||
|
|
ffa7dee85a | ||
|
|
c6dc0832e2 | ||
|
|
62b96b3390 | ||
|
|
842ba96631 | ||
|
|
fd5bfc653e | ||
|
|
6f71760057 | ||
|
|
191c74b061 | ||
|
|
4ab6b24793 | ||
|
|
bfdde2b1c2 | ||
|
|
7069ec3827 | ||
|
|
940428b683 | ||
|
|
3316ac45ff | ||
|
|
d883c982b3 | ||
|
|
6644050290 | ||
|
|
cb4bcd67c5 | ||
|
|
9dfd6c94dc | ||
|
|
5f7b64a68f | ||
|
|
c9d0241369 | ||
|
|
72de66f87b | ||
|
|
f3cb259965 | ||
|
|
30905c6e6a | ||
|
|
273947b733 | ||
|
|
54683bf87b | ||
|
|
1db7b10ae6 | ||
|
|
b63bcaf495 | ||
|
|
6f9442f7fb | ||
|
|
54a8646b94 | ||
|
|
def2f09c3d |
@@ -12,7 +12,6 @@ import Workers from '@/pages/Workers';
|
||||
import Reviews from '@/pages/Reviews';
|
||||
import Alerts from '@/pages/Alerts';
|
||||
import KnowledgeBase from '@/pages/KnowledgeBase';
|
||||
import DagCanvas from '@/pages/DagCanvas';
|
||||
|
||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||
const { isAuthenticated } = useAuthStore();
|
||||
@@ -62,7 +61,6 @@ export default function App() {
|
||||
<Route path="reviews" element={<Reviews />} />
|
||||
<Route path="alerts" element={<Alerts />} />
|
||||
<Route path="knowledge" element={<KnowledgeBase />} />
|
||||
<Route path="dag" element={<DagCanvas />} />
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
AuditOutlined,
|
||||
AlertOutlined,
|
||||
BookOutlined,
|
||||
ApartmentOutlined,
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
@@ -63,11 +62,6 @@ const menuItems: MenuProps['items'] = [
|
||||
icon: <CheckSquareOutlined />,
|
||||
label: '任务管理',
|
||||
},
|
||||
{
|
||||
key: '/dag',
|
||||
icon: <ApartmentOutlined />,
|
||||
label: 'DAG 编排',
|
||||
},
|
||||
{
|
||||
key: '/workers',
|
||||
icon: <RobotOutlined />,
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Modal, Descriptions, Spin, Tag, Typography, Empty } from 'antd';
|
||||
import { taskApi } from '@/api';
|
||||
import type { Task } from '@/types';
|
||||
import { TaskStatusTag, PriorityTag } from '@/components/constants';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
interface Props {
|
||||
taskId: number | null;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/** 任务详情弹窗:在 DAG 画布/项目页内点击任务卡片时弹出,不跳转页面 */
|
||||
export default function TaskDetailModal({ taskId, open, onClose }: Props) {
|
||||
const [task, setTask] = useState<Task | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || taskId == null) return;
|
||||
setLoading(true);
|
||||
setTask(null);
|
||||
taskApi
|
||||
.get(taskId)
|
||||
.then(setTask)
|
||||
.catch(() => setTask(null))
|
||||
.finally(() => setLoading(false));
|
||||
}, [open, taskId]);
|
||||
|
||||
const renderJsonBlock = (raw?: string) => {
|
||||
if (!raw) return <Text type="secondary">无</Text>;
|
||||
let pretty = raw;
|
||||
try {
|
||||
pretty = JSON.stringify(JSON.parse(raw), null, 2);
|
||||
} catch {
|
||||
/* keep raw */
|
||||
}
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
background: '#f6f8fa',
|
||||
border: '1px solid #e8e8e8',
|
||||
borderRadius: 6,
|
||||
padding: 12,
|
||||
maxHeight: 260,
|
||||
overflow: 'auto',
|
||||
fontSize: 12,
|
||||
margin: 0,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{pretty}
|
||||
</pre>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={task ? `任务 #${task.id} ${task.title}` : '任务详情'}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={720}
|
||||
destroyOnClose
|
||||
>
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: 40 }}>
|
||||
<Spin />
|
||||
</div>
|
||||
) : !task ? (
|
||||
<Empty description="任务不存在或加载失败" />
|
||||
) : (
|
||||
<div>
|
||||
<Descriptions column={2} bordered size="small">
|
||||
<Descriptions.Item label="状态">
|
||||
<TaskStatusTag status={task.status} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="优先级">
|
||||
<PriorityTag priority={task.priority} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="任务类型">{task.task_type}</Descriptions.Item>
|
||||
<Descriptions.Item label="需要审核">
|
||||
{task.requires_review ? (
|
||||
<Tag color="purple">是</Tag>
|
||||
) : (
|
||||
<Tag>否</Tag>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
{task.review_status && (
|
||||
<Descriptions.Item label="审核状态">{task.review_status}</Descriptions.Item>
|
||||
)}
|
||||
{task.worker_id && (
|
||||
<Descriptions.Item label="AI Worker">#{task.worker_id}</Descriptions.Item>
|
||||
)}
|
||||
{task.due_date && (
|
||||
<Descriptions.Item label="截止时间">
|
||||
{dayjs(task.due_date).format('YYYY-MM-DD HH:mm')}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{task.started_at && (
|
||||
<Descriptions.Item label="开始时间">
|
||||
{dayjs(task.started_at).format('YYYY-MM-DD HH:mm')}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{task.completed_at && (
|
||||
<Descriptions.Item label="完成时间">
|
||||
{dayjs(task.completed_at).format('YYYY-MM-DD HH:mm')}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label="创建时间">
|
||||
{dayjs(task.created_at).format('YYYY-MM-DD HH:mm')}
|
||||
</Descriptions.Item>
|
||||
{task.description && (
|
||||
<Descriptions.Item label="描述" span={2}>
|
||||
{task.description}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Paragraph strong style={{ marginBottom: 8 }}>
|
||||
输入(Prompt / 参数)
|
||||
</Paragraph>
|
||||
{renderJsonBlock(task.input_data)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Paragraph strong style={{ marginBottom: 8 }}>
|
||||
输出结果
|
||||
</Paragraph>
|
||||
{renderJsonBlock(task.output_data)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
import type { Node, Edge, Connection, NodeProps } from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import {
|
||||
Card,
|
||||
Select,
|
||||
Button,
|
||||
Space,
|
||||
message,
|
||||
@@ -30,9 +28,10 @@ import {
|
||||
ReloadOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { projectApi, dagApi } from '@/api';
|
||||
import type { Project, TaskStatus, DagGraphNode, DagGraphEdge } from '@/types';
|
||||
import { dagApi } from '@/api';
|
||||
import type { TaskStatus, DagGraphNode, DagGraphEdge } from '@/types';
|
||||
import { taskStatusConfig, priorityConfig } from '@/components/constants';
|
||||
import TaskDetailModal from '@/components/TaskDetailModal';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -52,6 +51,7 @@ function TaskNode({ data }: NodeProps) {
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Handle type="target" position={Position.Top} />
|
||||
@@ -121,23 +121,19 @@ function layoutNodes(graphNodes: DagGraphNode[], graphEdges: DagGraphEdge[]) {
|
||||
return positions;
|
||||
}
|
||||
|
||||
export default function DagCanvas() {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [projectId, setProjectId] = useState<number | undefined>();
|
||||
interface Props {
|
||||
projectId: number;
|
||||
}
|
||||
|
||||
/** DAG 编排画布(嵌入项目详情页使用) */
|
||||
export default function DagCanvas({ projectId }: Props) {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [executing, setExecuting] = useState(false);
|
||||
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
|
||||
|
||||
// Load projects
|
||||
useEffect(() => {
|
||||
projectApi.list().then((ps) => {
|
||||
setProjects(ps);
|
||||
if (ps.length > 0 && !projectId) setProjectId(ps[0].id);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const [detailTaskId, setDetailTaskId] = useState<number | null>(null);
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
|
||||
const loadGraph = useCallback(async (pid: number) => {
|
||||
setLoading(true);
|
||||
@@ -182,7 +178,7 @@ export default function DagCanvas() {
|
||||
// Connect: add dependency (drag from source(bottom) to target(top))
|
||||
const onConnect = useCallback(
|
||||
async (conn: Connection) => {
|
||||
if (!conn.source || !conn.target || !projectId) return;
|
||||
if (!conn.source || !conn.target) return;
|
||||
try {
|
||||
await dagApi.addDependency({
|
||||
task_id: Number(conn.target),
|
||||
@@ -205,9 +201,15 @@ export default function DagCanvas() {
|
||||
message.error(e.response?.data?.detail || '添加依赖失败');
|
||||
}
|
||||
},
|
||||
[projectId, setEdges]
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
// Node click -> open task detail modal
|
||||
const onNodeClick = useCallback((_: unknown, node: Node) => {
|
||||
setDetailTaskId(Number(node.id));
|
||||
setDetailOpen(true);
|
||||
}, []);
|
||||
|
||||
// Delete selected edge
|
||||
const removeSelectedEdge = useCallback(async () => {
|
||||
if (!selectedEdge) return;
|
||||
@@ -269,70 +271,73 @@ export default function DagCanvas() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
title="DAG 编排画布"
|
||||
extra={
|
||||
<Space>
|
||||
<Select
|
||||
style={{ width: 220 }}
|
||||
placeholder="选择项目"
|
||||
value={projectId}
|
||||
onChange={setProjectId}
|
||||
options={projects.map((p) => ({ label: p.name, value: p.id }))}
|
||||
/>
|
||||
<Tooltip title="刷新">
|
||||
<Button icon={<ReloadOutlined />} onClick={() => projectId && loadGraph(projectId)} loading={loading} />
|
||||
</Tooltip>
|
||||
<Tooltip title={selectedEdge ? '删除选中的依赖连线' : '先点击选中一条连线'}>
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
disabled={!selectedEdge}
|
||||
onClick={removeSelectedEdge}
|
||||
>
|
||||
删除依赖
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ThunderboltOutlined />}
|
||||
onClick={executeAll}
|
||||
loading={executing}
|
||||
disabled={nodes.length === 0}
|
||||
>
|
||||
执行 DAG
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
操作说明:从节点底部拖拽到另一节点顶部可建立依赖(上游 → 下游);点击连线可选中并删除;支持拖拽移动节点、缩放画布。
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ height: 'calc(100vh - 260px)', minHeight: 480, border: '1px solid #f0f0f0', borderRadius: 8 }}>
|
||||
{nodes.length === 0 && !loading ? (
|
||||
<Empty description="该项目暂无任务,请先在任务管理中创建任务" style={{ marginTop: 120 }} />
|
||||
) : (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onEdgeClick={(_, edge) => setSelectedEdge(edge)}
|
||||
onPaneClick={() => setSelectedEdge(null)}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
deleteKeyCode={[]}
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
拖拽节点底部锚点到另一节点顶部建立依赖;点击节点查看详情;点击连线可选中删除。
|
||||
</Text>
|
||||
<Space>
|
||||
<Tooltip title="刷新">
|
||||
<Button size="small" icon={<ReloadOutlined />} onClick={() => loadGraph(projectId)} loading={loading} />
|
||||
</Tooltip>
|
||||
<Tooltip title={selectedEdge ? '删除选中的依赖连线' : '先点击选中一条连线'}>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
disabled={!selectedEdge}
|
||||
onClick={removeSelectedEdge}
|
||||
>
|
||||
<Background gap={16} />
|
||||
<Controls />
|
||||
{minimap}
|
||||
</ReactFlow>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
删除依赖
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<ThunderboltOutlined />}
|
||||
onClick={executeAll}
|
||||
loading={executing}
|
||||
disabled={nodes.length === 0}
|
||||
>
|
||||
执行 DAG
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ height: 560, border: '1px solid #f0f0f0', borderRadius: 8 }}>
|
||||
{nodes.length === 0 && !loading ? (
|
||||
<Empty description="该项目暂无任务,请先在任务管理中创建任务" style={{ marginTop: 160 }} />
|
||||
) : (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={(_, edge) => setSelectedEdge(edge)}
|
||||
onPaneClick={() => setSelectedEdge(null)}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
deleteKeyCode={[]}
|
||||
>
|
||||
<Background gap={16} />
|
||||
<Controls />
|
||||
{minimap}
|
||||
</ReactFlow>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TaskDetailModal
|
||||
taskId={detailTaskId}
|
||||
open={detailOpen}
|
||||
onClose={() => setDetailOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,15 +12,20 @@ import {
|
||||
Tag,
|
||||
Empty,
|
||||
List,
|
||||
Tabs,
|
||||
} from 'antd';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
ReloadOutlined,
|
||||
ApartmentOutlined,
|
||||
ProfileOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { projectApi, taskApi, artifactApi } from '@/api';
|
||||
import type { Project, ProjectStats, Task, Artifact } from '@/types';
|
||||
import { ProjectStatusTag, TaskStatusTag } from '@/components/constants';
|
||||
import DagCanvas from '@/pages/DagCanvas';
|
||||
import TaskDetailModal from '@/components/TaskDetailModal';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@@ -28,11 +33,20 @@ const { Title, Text } = Typography;
|
||||
export default function ProjectDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const activeTab = searchParams.get('tab') || 'overview';
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [stats, setStats] = useState<ProjectStats | null>(null);
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [artifacts, setArtifacts] = useState<Artifact[]>([]);
|
||||
const [detailTaskId, setDetailTaskId] = useState<number | null>(null);
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
|
||||
const openTaskDetail = (taskId: number) => {
|
||||
setDetailTaskId(taskId);
|
||||
setDetailOpen(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
@@ -75,36 +89,8 @@ export default function ProjectDetail() {
|
||||
].filter((item) => item.count > 0)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/projects')}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{project.name}
|
||||
</Title>
|
||||
<ProjectStatusTag status={project.status} />
|
||||
</Space>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
const overviewTab = (
|
||||
<>
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Descriptions column={2} bordered size="small">
|
||||
<Descriptions.Item label="项目名称">{project.name}</Descriptions.Item>
|
||||
@@ -198,7 +184,7 @@ export default function ProjectDetail() {
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<a onClick={() => navigate('/tasks')}>{task.title}</a>
|
||||
<a onClick={() => openTaskDetail(task.id)}>{task.title}</a>
|
||||
}
|
||||
description={dayjs(task.created_at).format('MM-DD HH:mm')}
|
||||
/>
|
||||
@@ -231,6 +217,73 @@ export default function ProjectDetail() {
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/projects')}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{project.name}
|
||||
</Title>
|
||||
<ProjectStatusTag status={project.status} />
|
||||
</Space>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setSearchParams({ tab: key })}
|
||||
items={[
|
||||
{
|
||||
key: 'overview',
|
||||
label: (
|
||||
<span>
|
||||
<ProfileOutlined /> 概览
|
||||
</span>
|
||||
),
|
||||
children: overviewTab,
|
||||
},
|
||||
{
|
||||
key: 'dag',
|
||||
label: (
|
||||
<span>
|
||||
<ApartmentOutlined /> DAG 编排
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<Card size="small" title="DAG 编排画布">
|
||||
<DagCanvas projectId={project.id} />
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<TaskDetailModal
|
||||
taskId={detailTaskId}
|
||||
open={detailOpen}
|
||||
onClose={() => setDetailOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/components/AppLayout.tsx","./src/components/constants.tsx","./src/pages/Alerts.tsx","./src/pages/DagCanvas.tsx","./src/pages/Dashboard.tsx","./src/pages/KnowledgeBase.tsx","./src/pages/Login.tsx","./src/pages/ProjectDetail.tsx","./src/pages/Projects.tsx","./src/pages/Reviews.tsx","./src/pages/Tasks.tsx","./src/pages/Workers.tsx","./src/stores/auth.ts","./src/types/index.ts"],"version":"5.9.3"}
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/components/AppLayout.tsx","./src/components/TaskDetailModal.tsx","./src/components/constants.tsx","./src/pages/Alerts.tsx","./src/pages/DagCanvas.tsx","./src/pages/Dashboard.tsx","./src/pages/KnowledgeBase.tsx","./src/pages/Login.tsx","./src/pages/ProjectDetail.tsx","./src/pages/Projects.tsx","./src/pages/Reviews.tsx","./src/pages/Tasks.tsx","./src/pages/Workers.tsx","./src/stores/auth.ts","./src/types/index.ts"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user