feat: 更新节点
This commit is contained in:
parent
03885d5a52
commit
78067519c3
|
|
@ -386,6 +386,10 @@ def create_app():
|
|||
res.append(obj)
|
||||
return TableResponse(data=res, total=len(res))
|
||||
|
||||
@app.put("/api/flows", tags=["Flow"], description="任务修改", response_model=BaseResponse)
|
||||
async def update_flow(work_uuid: int, body: ClientRequest, user: DB.User = Depends(get_current_user)):
|
||||
return BaseResponse()
|
||||
|
||||
@app.get("/api/flows/detail", tags=["Flow"], description="任务详情", response_model=BaseResponse)
|
||||
async def flows_detail(work_uuid: str, user: DB.User = Depends(get_current_user)):
|
||||
work = await DB.Work.filter(uuid=work_uuid).first()
|
||||
|
|
@ -422,17 +426,17 @@ def create_app():
|
|||
|
||||
@app.post("/api/clients", tags=["Client"], description="终端创建", response_model=BaseResponse)
|
||||
async def create_client(body: ClientRequest, user: DB.User = Depends(get_current_user)):
|
||||
await DB.Client(**body, user_id=user.id).save()
|
||||
await DB.Client(**body.model_dump(), user_id=user.id).save()
|
||||
return BaseResponse()
|
||||
|
||||
@app.post("/api/clients", tags=["Client"], description="终端创建", response_model=BaseResponse)
|
||||
async def create_client(body: ClientRequest, user: DB.User = Depends(get_current_user)):
|
||||
await DB.Client(**body, user_id=user.id).save()
|
||||
await DB.Client(**body.model_dump, user_id=user.id).save()
|
||||
return BaseResponse()
|
||||
|
||||
@app.put("/api/clients", tags=["Client"], description="终端修改", response_model=BaseResponse)
|
||||
async def update_client(client_id:int, body: ClientRequest, user: DB.User = Depends(get_current_user)):
|
||||
obj = await DB.Client.filter(id=client_id).first()
|
||||
async def update_client(id:int, body: ClientRequest, user: DB.User = Depends(get_current_user)):
|
||||
obj = await DB.Client.filter(id=id).first()
|
||||
if obj:
|
||||
obj.update_from_dict(body.model_dump())
|
||||
obj.update_time = datetime.datetime.now()
|
||||
|
|
@ -440,8 +444,8 @@ def create_app():
|
|||
return BaseResponse()
|
||||
|
||||
@app.delete("/api/clients", tags=["Client"], description="终端删除", response_model=BaseResponse)
|
||||
async def del_client(client_id: int, user: DB.User = Depends(get_current_user)):
|
||||
await DB.Client.filter(id=client_id).delete()
|
||||
async def del_client(id: int, user: DB.User = Depends(get_current_user)):
|
||||
await DB.Client.filter(id=id).delete()
|
||||
return BaseResponse()
|
||||
|
||||
return app
|
||||
|
|
|
|||
|
|
@ -1,11 +1,29 @@
|
|||
import { ModalForm, ProForm } from "@ant-design/pro-components";
|
||||
import { Button } from "antd";
|
||||
import { ModalForm, ProFormDigit, ProFormText } from "@ant-design/pro-components";
|
||||
import { Button, message } from "antd";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { create } from "../../../api/client";
|
||||
|
||||
const required = [{ required: true}]
|
||||
|
||||
const Create = ({ onFinish }) => {
|
||||
return <ModalForm
|
||||
title="创建节点"
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
initialValues={{ port: 22}}
|
||||
trigger={<Button icon={<PlusOutlined />} type='primary'>创建</Button>}
|
||||
></ModalForm>
|
||||
onFinish={async params => {
|
||||
await create(params);
|
||||
message.success("创建成功");
|
||||
onFinish && onFinish();
|
||||
return true;
|
||||
}}
|
||||
>
|
||||
<ProFormText label="名字" name="name" rules={required} />
|
||||
<ProFormText label="地址" name="host" rules={required} />
|
||||
<ProFormDigit label="端口" name="port" rules={required} />
|
||||
<ProFormText label="用户" name="user" rules={required} />
|
||||
<ProFormText.Password label="密码" name="password" rules={required} />
|
||||
</ModalForm>
|
||||
}
|
||||
|
||||
export default Create;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { ModalForm, ProFormDigit, ProFormText } from "@ant-design/pro-components";
|
||||
import { Button, message } from "antd";
|
||||
import { update } from "../../../api/client";
|
||||
|
||||
const required = [{ required: true}]
|
||||
|
||||
const Edit = ({ data, onFinish }) => {
|
||||
return <ModalForm
|
||||
title="编辑"
|
||||
modalProps={{ destroyOnHidden: true }}
|
||||
initialValues={{ ...data }}
|
||||
trigger={<Button type="link" style={{ padding: '0 0', border: 'none' }}>编辑</Button>}
|
||||
onFinish={async params => {
|
||||
await update(data.id, params);
|
||||
message.success("编辑成功");
|
||||
onFinish && onFinish();
|
||||
return true;
|
||||
}}
|
||||
>
|
||||
<ProFormText label="名字" name="name" rules={required} />
|
||||
<ProFormText label="地址" name="host" rules={required} />
|
||||
<ProFormDigit label="端口" name="port" rules={required} />
|
||||
<ProFormText label="用户" name="user" rules={required} />
|
||||
<ProFormText.Password label="密码" name="password" rules={required} />
|
||||
</ModalForm>
|
||||
}
|
||||
|
||||
export default Edit;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { ProTable } from "@ant-design/pro-components"
|
||||
import { Card } from "antd"
|
||||
import { Button, Card, message, Popconfirm } from "antd"
|
||||
import { useRef } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { tableList } from "../../api/client"
|
||||
import { del, tableList } from "../../api/client"
|
||||
import Create from "./components/Create"
|
||||
import Edit from "./components/Edit"
|
||||
|
||||
const ClientPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -12,6 +13,11 @@ const ClientPage = () => {
|
|||
const res = await tableList();
|
||||
return res;
|
||||
}
|
||||
const onDelete = async (record) => {
|
||||
const res = await del(record.id);
|
||||
message.success("删除成功");
|
||||
actionRef?.current?.reload();
|
||||
}
|
||||
const columns = [
|
||||
{
|
||||
title: '#ID',
|
||||
|
|
@ -27,27 +33,28 @@ const ClientPage = () => {
|
|||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: "update_time",
|
||||
dataIndex: "create_time",
|
||||
render: (_, record) => new Date(record?.create_time * 1000).toLocaleString()
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
with: 100,
|
||||
width: 100,
|
||||
fixed: 'right',
|
||||
render: (_, record) => [
|
||||
<Button key='2' type="link" style={{ padding: '0 0', border: 'none' }} onClick={() => {
|
||||
navigate("/flow/detail/" + record.uuid);
|
||||
}}>编辑</Button>,
|
||||
<Button key='3' type="link" danger style={{ padding: '0 0', border: 'none' }}>删除</Button>,
|
||||
<Edit data={record} key='1' onFinish={() => actionRef?.current?.reload()} />,
|
||||
<Popconfirm key='3' title="是否删除节点?" okText="删除" cancelText="取消" onConfirm={async () => await onDelete(record)}>
|
||||
<Button type="link" danger style={{ padding: '0 0', border: 'none' }}>删除</Button>
|
||||
</Popconfirm>
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
return <Card title="节点管理">
|
||||
return <Card title="节点管理" extra={<Create onFinish={() => actionRef?.current?.reload()} />}>
|
||||
<ProTable
|
||||
toolBarRender={() => <Create />}
|
||||
actionRef={actionRef}
|
||||
rowKey='id'
|
||||
search={false}
|
||||
|
|
|
|||
|
|
@ -16,28 +16,40 @@ import {
|
|||
import '@xyflow/react/dist/style.css';
|
||||
import { flowDetail } from "../../api/flow";
|
||||
import EditNode from "./components/EditNode";
|
||||
import CustomNode from "./components/CustomNode";
|
||||
|
||||
const COLORS = {
|
||||
input: "#89A8B2",
|
||||
first: "#89A8B2",
|
||||
git: "#B3C8CF",
|
||||
cmd: "#E5E1DA",
|
||||
status: "#F1F0E8"
|
||||
}
|
||||
|
||||
const POSITION = {
|
||||
first: ['right'],
|
||||
git: ['right', 'left'],
|
||||
cmd: ['right', 'left'],
|
||||
status: ['left'],
|
||||
}
|
||||
|
||||
const initialNodes = [
|
||||
{
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { label: '开始' },
|
||||
data: { label: '开始', style: { backgroundColor: COLORS['first'] }, position: POSITION['first'] },
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
type: 'input',
|
||||
style: {
|
||||
backgroundColor: COLORS["input"],
|
||||
}
|
||||
type: 'first',
|
||||
},
|
||||
];
|
||||
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||
const initialEdges = [];
|
||||
|
||||
const nodeTypes = {
|
||||
first: CustomNode,
|
||||
git: CustomNode,
|
||||
cmd: CustomNode,
|
||||
status: CustomNode,
|
||||
}
|
||||
|
||||
const FlowDetail = () => {
|
||||
const { work_uuid } = useParams()
|
||||
|
|
@ -55,22 +67,32 @@ const FlowDetail = () => {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
const onEdit = ({ id, ...detail }) => {
|
||||
const temp = [...nodes];
|
||||
temp.forEach(e => {
|
||||
if (e.id == id) {
|
||||
e.detail = detail;
|
||||
e.data = { ...e.data, label: detail.title }
|
||||
}
|
||||
})
|
||||
controlRef?.current?.updateNode(id);
|
||||
setNodes(temp);
|
||||
}
|
||||
const onAdd = (type) => {
|
||||
setNodes([
|
||||
...nodes,
|
||||
{
|
||||
id: new Date().getTime() + '_' + nodes.length,
|
||||
id: 'temp_' + new Date().getTime() + '_' + nodes.length,
|
||||
position: { x: nodes.length * 5, y: 0 },
|
||||
data: { label: '2' },
|
||||
data: { label: type, style: { backgroundColor: COLORS[type] }, position: POSITION[type] },
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
style: { backgroundColor: COLORS[type] },
|
||||
type: type,
|
||||
},
|
||||
]);
|
||||
}
|
||||
const onNodeClick = (_, record) => {
|
||||
if (record?.type === 'input') return;
|
||||
if (record?.type === 'first') return;
|
||||
editRef?.current?.open(record);
|
||||
}
|
||||
const loadDetail = async () => {
|
||||
|
|
@ -86,9 +108,8 @@ const FlowDetail = () => {
|
|||
id: '1',
|
||||
position: { x: startX, y: 0 },
|
||||
sourcePosition: 'right',
|
||||
type: 'input',
|
||||
style: { backgroundColor: 'input' },
|
||||
data: { label: '开始' },
|
||||
type: 'first',
|
||||
data: { label: '开始', style: { backgroundColor: COLORS['first'] }, position: POSITION['first'] },
|
||||
}]
|
||||
infoList.forEach((e, i) => {
|
||||
tempNodes.push({
|
||||
|
|
@ -97,8 +118,7 @@ const FlowDetail = () => {
|
|||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
type: e.type,
|
||||
style: { backgroundColor: COLORS[e.type] },
|
||||
data: { label: e.title },
|
||||
data: { label: e.title, style: { backgroundColor: COLORS[e.type] }, position: POSITION[e.type] },
|
||||
detail: e
|
||||
})
|
||||
|
||||
|
|
@ -144,6 +164,8 @@ const FlowDetail = () => {
|
|||
onNodeDoubleClick={onNodeClick}
|
||||
style={{ background: '#f2f3f5' }}
|
||||
position={Position.Right}
|
||||
nodeTypes={nodeTypes}
|
||||
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
|
||||
fitView
|
||||
>
|
||||
<MiniMap />
|
||||
|
|
@ -156,7 +178,7 @@ const FlowDetail = () => {
|
|||
</Col>
|
||||
<Col span={6}>
|
||||
<RightCard onCreate={onAdd} />
|
||||
<EditNode ref={editRef} />
|
||||
<EditNode ref={editRef} onFinish={onEdit} />
|
||||
</Col>
|
||||
</Row>
|
||||
</ReactFlowProvider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
.custom-node {
|
||||
height: 40.5px;
|
||||
width: 150px;
|
||||
border: 1px solid #eee;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.custom-node:hover {
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React, { memo } from 'react';
|
||||
import { Handle, Position } from '@xyflow/react';
|
||||
import './CustomNode.css';
|
||||
|
||||
const CustomNode = memo(({ data: { style, position, ...data}, isConnectable }) => {
|
||||
return (
|
||||
<div className='custom-node' style={{ ...style }}>
|
||||
<div >
|
||||
{data.label}
|
||||
</div>
|
||||
{position?.includes('left') && <Handle type="target" position={Position.Left} isConnectable={isConnectable} />}
|
||||
{position?.includes('right') && <Handle type="source" position={Position.Right} isConnectable={isConnectable} />}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
|
||||
export default CustomNode;
|
||||
|
|
@ -1,43 +1,71 @@
|
|||
import { DrawerForm, ModalForm, ProFormSelect, ProFormText, ProFormTextArea } from "@ant-design/pro-components";
|
||||
import { Button, Flex, Space } from "antd";
|
||||
import { DrawerForm, ModalForm, ProFormDigit, ProFormGroup, ProFormSelect, ProFormText, ProFormTextArea } from "@ant-design/pro-components";
|
||||
import { Button, Divider, Flex, Space, Typography } from "antd";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { tableList } from "../../../api/client";
|
||||
|
||||
const EditNode = forwardRef(({ onFinish }, ref) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const formRef = useRef(null);
|
||||
const [record, setRecord] = useState(null);
|
||||
const [clients, setClients] = useState([{ label: "Local", value: 'local'}]);
|
||||
const [clients, setClients] = useState([{ label: "Local", value: 'local' }]);
|
||||
const onRequest = async () => {
|
||||
const res = await tableList();
|
||||
return [{label: "[LOCAL]", value: 0},...res?.data?.map(e => ({ label: e.name, value: e.id }))];
|
||||
}
|
||||
const onSubmit = async (params) => {
|
||||
onFinish && onFinish({ ...params, id: record?.id})
|
||||
return true;
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: (data) => {
|
||||
setOpen(true);
|
||||
setRecord(data);
|
||||
setTimeout(() => setRecord(data), 100);
|
||||
}
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if (record && formRef.current) {
|
||||
if (record && formRef.current && open) {
|
||||
formRef.current.setFieldsValue(record.detail);
|
||||
}
|
||||
}, [record, formRef.current]);
|
||||
}, [record, open, formRef.current]);
|
||||
|
||||
return <DrawerForm
|
||||
return <ModalForm
|
||||
title="编辑"
|
||||
trigger={<></>}
|
||||
open={open}
|
||||
onOpenChange={e => setOpen(e)}
|
||||
formRef={formRef}
|
||||
width={600}
|
||||
drawerProps={{ maskClosable: false, destroyOnHidden: true}}
|
||||
onFinish={onSubmit}
|
||||
modalProps={{ maskClosable: false, destroyOnHidden: true }}
|
||||
>
|
||||
<ProFormText name='uuid' disabled label='UUID' />
|
||||
<Flex align="center">
|
||||
<ProFormSelect width={480} label="目标" rules={[{ required: true }]} name="client" options={clients} />
|
||||
<Button style={{ marginLeft: 6, marginTop: 6}}>添加</Button>
|
||||
</Flex>
|
||||
<ProFormText name='title' rules={[{ required: true}]} label='标题' fieldProps={{ maxLength: 20}} />
|
||||
<ProFormTextArea fieldProps={{ maxLength: 50}} name='description' label='描述' />
|
||||
<Divider orientation="left">基础信息</Divider>
|
||||
<ProFormGroup>
|
||||
<ProFormText name='uuid' disabled label='UUID' width='md'/>
|
||||
<ProFormSelect label="节点" rules={[{ required: true }]} name={["detail", "client"]} options={clients} request={onRequest} width='md' />
|
||||
<ProFormText name='title' rules={[{ required: true }]} label='标题' fieldProps={{ maxLength: 20 }} width='md' />
|
||||
<ProFormText fieldProps={{ maxLength: 50 }} name='description' label='描述' width="md" />
|
||||
</ProFormGroup>
|
||||
{record?.type === 'git' && <>
|
||||
<Divider orientation="left">Git</Divider>
|
||||
<ProFormGroup>
|
||||
<ProFormText name={["detail", "git_url"]} rules={[{ required: true }]} label='地址' width="md" />
|
||||
<ProFormText name={["detail", "git_branch"]} rules={[{ required: true }]} label='分支' width='md' />
|
||||
<ProFormSelect name={["detail", "git_clone_type"]} rules={[{ required: true }]} label='协议' width='md' options={["http/https"]} />
|
||||
<ProFormDigit name={["detail", "git_clone_depth"]} rules={[{ required: true }]} label='Depth' width='md' />
|
||||
<ProFormText name={["detail", "git_path"]} rules={[{ required: true }]} label='路径' width="md" />
|
||||
<ProFormText name={["detail", "git_user"]} rules={[{ required: true }]} label='帐号' width='md' />
|
||||
<ProFormText.Password name={["detail", "git_password"]} rules={[{ required: true }]} label='密码' width='md' />
|
||||
</ProFormGroup>
|
||||
</>}
|
||||
{record?.type === 'cmd' && <>
|
||||
<Divider orientation="left">命令</Divider>
|
||||
<ProFormTextArea fieldProps={{ rows: 10 }} name={["detail", "cmd"]} rules={[{ required: true }]} label='命令' width={690} />
|
||||
</>}
|
||||
{record?.type === 'status' && <>
|
||||
<Divider orientation="left">健康监测</Divider>
|
||||
<ProFormText name={["detail", "status_url"]} rules={[{ required: true }]} label='检测地址' width={690} />
|
||||
</>}
|
||||
|
||||
</DrawerForm>
|
||||
</ModalForm>
|
||||
})
|
||||
|
||||
export default EditNode;
|
||||
|
|
@ -129,7 +129,6 @@ export default () => {
|
|||
}
|
||||
// 状态检测
|
||||
useEffect(() => {
|
||||
if (!pageInit.current) return;
|
||||
if (!user?.token) return;
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
|
|
@ -167,9 +166,6 @@ export default () => {
|
|||
};
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
pageInit.current = true;
|
||||
}, [])
|
||||
return (
|
||||
<Card title="工作台">
|
||||
<ProTable
|
||||
|
|
|
|||
Loading…
Reference in New Issue