feat: client

This commit is contained in:
李如威 2025-09-16 22:57:56 +08:00
parent 25fa8a043a
commit 03885d5a52
9 changed files with 157 additions and 2 deletions

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import datetime
import json import json
import uvicorn import uvicorn
import uuid import uuid
@ -11,6 +12,7 @@ from tortoise.functions import Max
from fastapi import FastAPI, Depends, HTTPException from fastapi import FastAPI, Depends, HTTPException
from models.schemas.system import InitStatusResponse, InitConfigRequest, BaseResponse, TableResponse from models.schemas.system import InitStatusResponse, InitConfigRequest, BaseResponse, TableResponse
from models.schemas.user import LoginRequest, LoginResponse 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.config import load_init_config, save_init_config
from core.db import init_tortoise, close_tortoise from core.db import init_tortoise, close_tortoise
from core.logger import logger from core.logger import logger
@ -413,6 +415,35 @@ def create_app():
return StreamingResponse(_event(), media_type="text/event-stream") 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 return app

View File

@ -1,3 +1,4 @@
from .system import * from .system import *
from .user import * from .user import *
from .work import * from .work import *
from .client import *

View File

@ -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"

View File

@ -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

View File

@ -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")
}

View File

@ -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 <ModalForm
trigger={<Button icon={<PlusOutlined />} type='primary'>创建</Button>}
></ModalForm>
}
export default Create;

View File

@ -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) => [
<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>,
]
}
]
return <Card title="节点管理">
<ProTable
toolBarRender={() => <Create />}
actionRef={actionRef}
rowKey='id'
search={false}
scroll={{ x: 'max-content' }}
options={false}
columns={columns}
request={onRequest}
/>
</Card>
}
export default ClientPage

View File

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Outlet, useNavigate, useLocation } from "react-router-dom"; import { Outlet, useNavigate, useLocation } from "react-router-dom";
import { Layout, Menu, Button, Flex, Dropdown, Steps } from "antd"; 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 { useAuth } from "../context/AuthContext";
import './Layout.css'; import './Layout.css';
@ -15,6 +15,7 @@ export default function LayoutPage() {
const menuItems = [ const menuItems = [
{ key: "1", icon: <NodeExpandOutlined />, label: "工作台", path: "/flow" }, { key: "1", icon: <NodeExpandOutlined />, label: "工作台", path: "/flow" },
{ key: "2", icon: <LaptopOutlined />, label: "节点管理", path: "/client" },
]; ];
const selectedKey = menuItems.find(item => location.pathname.startsWith(item.path))?.key || ""; const selectedKey = menuItems.find(item => location.pathname.startsWith(item.path))?.key || "";

View File

@ -8,6 +8,7 @@ import Profile from "./pages/User/Profile";
import Flow from "./pages/Flow"; import Flow from "./pages/Flow";
import FlowDetail from "./pages/Flow/Detail"; import FlowDetail from "./pages/Flow/Detail";
import LayoutPage from "./pages/Layout"; import LayoutPage from "./pages/Layout";
import ClientPage from "./pages/Client";
// //
@ -29,6 +30,7 @@ const routes = [
element: <LayoutPage />, element: <LayoutPage />,
children: [ children: [
{ path: "/flow", element: <Flow /> }, { path: "/flow", element: <Flow /> },
{ path: "/client", element: <ClientPage /> },
{ path: "/flow/detail/:work_uuid", element: <FlowDetail /> }, { path: "/flow/detail/:work_uuid", element: <FlowDetail /> },
{ path: "/user", element: <User /> }, { path: "/user", element: <User /> },
{ path: "/user/detail", element: <Profile /> } { path: "/user/detail", element: <Profile /> }