diff --git a/backend/app/main.py b/backend/app/main.py
index b50203e..66b0c53 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -1,4 +1,5 @@
import asyncio
+import datetime
import json
import uvicorn
import uuid
@@ -11,6 +12,7 @@ from tortoise.functions import Max
from fastapi import FastAPI, Depends, HTTPException
from models.schemas.system import InitStatusResponse, InitConfigRequest, BaseResponse, TableResponse
from models.schemas.user import LoginRequest, LoginResponse
+from models.schemas.client import ClientRequest
from core.config import load_init_config, save_init_config
from core.db import init_tortoise, close_tortoise
from core.logger import logger
@@ -413,6 +415,35 @@ def create_app():
return StreamingResponse(_event(), media_type="text/event-stream")
+ @app.get("/api/clients", tags=["Client"], description="终端列表", response_model=TableResponse)
+ async def clients_list(user: DB.User = Depends(get_current_user)):
+ res = await DB.Client.filter(user_id=user.id).all()
+ return TableResponse(data=[x.json() for x in res], total=len(res))
+
+ @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()
+ 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()
+ 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()
+ if obj:
+ obj.update_from_dict(body.model_dump())
+ obj.update_time = datetime.datetime.now()
+ await obj.save()
+ 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()
+ return BaseResponse()
+
return app
diff --git a/backend/app/models/db/__init__.py b/backend/app/models/db/__init__.py
index f9f1d0a..2474279 100644
--- a/backend/app/models/db/__init__.py
+++ b/backend/app/models/db/__init__.py
@@ -1,3 +1,4 @@
from .system import *
from .user import *
-from .work import *
\ No newline at end of file
+from .work import *
+from .client import *
\ No newline at end of file
diff --git a/backend/app/models/db/client.py b/backend/app/models/db/client.py
new file mode 100644
index 0000000..4e236f0
--- /dev/null
+++ b/backend/app/models/db/client.py
@@ -0,0 +1,14 @@
+from tortoise import fields
+from .system import BaseModel
+
+
+class Client(BaseModel):
+ name = fields.CharField(max_length=50, null=True)
+ host = fields.CharField(max_length=50, null=True)
+ port = fields.IntField(default=22, null=True)
+ user = fields.CharField(max_length=50, null=True)
+ password = fields.CharField(max_length=50, null=True)
+ user_id = fields.IntField(null=True)
+
+ class Meta:
+ table = "tb_client"
diff --git a/backend/app/models/schemas/client.py b/backend/app/models/schemas/client.py
new file mode 100644
index 0000000..6dbc940
--- /dev/null
+++ b/backend/app/models/schemas/client.py
@@ -0,0 +1,11 @@
+from pydantic import BaseModel
+from .system import BaseResponse
+import hashlib
+
+
+class ClientRequest(BaseModel):
+ port: int
+ host: str
+ password: str
+ user: str
+ name: str
\ No newline at end of file
diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js
new file mode 100644
index 0000000..ed77fae
--- /dev/null
+++ b/frontend/src/api/client.js
@@ -0,0 +1,21 @@
+import { request } from "./base";
+
+export const tableList = async (data = {}) => {
+ return request("/api/clients?" + new URLSearchParams(data).toString(), "GET")
+}
+
+export const create = async (data = {}) => {
+ return request("/api/clients", "POST", data)
+}
+
+export const update = async (id, data = {}) => {
+ return request("/api/clients?id=" + id, "PUT", data)
+}
+
+export const del = async (id) => {
+ return request("/api/clients?id=" + id, "DELETE")
+}
+
+
+
+
diff --git a/frontend/src/pages/Client/components/Create.jsx b/frontend/src/pages/Client/components/Create.jsx
new file mode 100644
index 0000000..f76468e
--- /dev/null
+++ b/frontend/src/pages/Client/components/Create.jsx
@@ -0,0 +1,11 @@
+import { ModalForm, ProForm } from "@ant-design/pro-components";
+import { Button } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+
+const Create = ({ onFinish }) => {
+ return } type='primary'>创建}
+ >
+}
+
+export default Create;
\ No newline at end of file
diff --git a/frontend/src/pages/Client/index.jsx b/frontend/src/pages/Client/index.jsx
new file mode 100644
index 0000000..532d5c1
--- /dev/null
+++ b/frontend/src/pages/Client/index.jsx
@@ -0,0 +1,63 @@
+import { ProTable } from "@ant-design/pro-components"
+import { Card } from "antd"
+import { useRef } from "react"
+import { useNavigate } from "react-router-dom"
+import { tableList } from "../../api/client"
+import Create from "./components/Create"
+
+const ClientPage = () => {
+ const navigate = useNavigate();
+ const actionRef = useRef(null);
+ const onRequest = async () => {
+ const res = await tableList();
+ return res;
+ }
+ const columns = [
+ {
+ title: '#ID',
+ dataIndex: "id",
+ },
+ {
+ title: '名字',
+ dataIndex: "name",
+ },
+ {
+ title: '地址',
+ dataIndex: "host",
+ },
+ {
+ title: '创建时间',
+ dataIndex: "update_time",
+ },
+ {
+ title: '操作',
+ valueType: 'option',
+ key: 'option',
+ with: 100,
+ fixed: 'right',
+ render: (_, record) => [
+ ,
+ ,
+ ]
+ }
+
+ ]
+
+ return
+ }
+ actionRef={actionRef}
+ rowKey='id'
+ search={false}
+ scroll={{ x: 'max-content' }}
+ options={false}
+ columns={columns}
+ request={onRequest}
+ />
+
+
+}
+
+export default ClientPage
\ No newline at end of file
diff --git a/frontend/src/pages/Layout.jsx b/frontend/src/pages/Layout.jsx
index 88de368..ecd5793 100644
--- a/frontend/src/pages/Layout.jsx
+++ b/frontend/src/pages/Layout.jsx
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Outlet, useNavigate, useLocation } from "react-router-dom";
import { Layout, Menu, Button, Flex, Dropdown, Steps } from "antd";
-import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LoginOutlined, ProfileOutlined, NodeExpandOutlined } from "@ant-design/icons";
+import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LoginOutlined, ProfileOutlined, NodeExpandOutlined, LaptopOutlined } from "@ant-design/icons";
import { useAuth } from "../context/AuthContext";
import './Layout.css';
@@ -15,6 +15,7 @@ export default function LayoutPage() {
const menuItems = [
{ key: "1", icon: , label: "工作台", path: "/flow" },
+ { key: "2", icon: , label: "节点管理", path: "/client" },
];
const selectedKey = menuItems.find(item => location.pathname.startsWith(item.path))?.key || "";
diff --git a/frontend/src/routes.jsx b/frontend/src/routes.jsx
index df79b59..23da8ef 100644
--- a/frontend/src/routes.jsx
+++ b/frontend/src/routes.jsx
@@ -8,6 +8,7 @@ import Profile from "./pages/User/Profile";
import Flow from "./pages/Flow";
import FlowDetail from "./pages/Flow/Detail";
import LayoutPage from "./pages/Layout";
+import ClientPage from "./pages/Client";
// 受保护的路由:没登录就跳转到登录页
@@ -29,6 +30,7 @@ const routes = [
element: ,
children: [
{ path: "/flow", element: },
+ { path: "/client", element: },
{ path: "/flow/detail/:work_uuid", element: },
{ path: "/user", element: },
{ path: "/user/detail", element: }