From 03885d5a52bb9e1b9a88ca354e27c6735dfbfe60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=A6=82=E5=A8=81?= Date: Tue, 16 Sep 2025 22:57:56 +0800 Subject: [PATCH] feat: client --- backend/app/main.py | 31 +++++++++ backend/app/models/db/__init__.py | 3 +- backend/app/models/db/client.py | 14 +++++ backend/app/models/schemas/client.py | 11 ++++ frontend/src/api/client.js | 21 +++++++ .../src/pages/Client/components/Create.jsx | 11 ++++ frontend/src/pages/Client/index.jsx | 63 +++++++++++++++++++ frontend/src/pages/Layout.jsx | 3 +- frontend/src/routes.jsx | 2 + 9 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 backend/app/models/db/client.py create mode 100644 backend/app/models/schemas/client.py create mode 100644 frontend/src/api/client.js create mode 100644 frontend/src/pages/Client/components/Create.jsx create mode 100644 frontend/src/pages/Client/index.jsx 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: }