feat: 切换 ui

This commit is contained in:
Gavan
2025-09-16 14:50:13 +08:00
parent 26fff20e6c
commit 747f0912be
52 changed files with 1715 additions and 1073 deletions

View File

@@ -44,7 +44,7 @@
### UI框架与样式
- **Material-UI (@mui)** - Google Material Design组件库
- **Emotion** - CSS-in-JS样式库
- **@c-x/ui** - 自定义组件库
- **@ctzhian/ui** - 自定义组件库
### 状态管理与数据
- **ahooks** - React Hooks工具库

View File

@@ -1,7 +1,7 @@
<%
const { apiConfig, generateResponses, config } = it;
%>
import { message as Message } from '@c-x/ui'
import { message as Message } from '@ctzhian/ui'
import type { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType, AxiosResponse } from "axios";
import axios from "axios";

View File

@@ -11,14 +11,13 @@
"preview": "vite preview"
},
"dependencies": {
"@c-x/ui": "^1.0.9",
"@ctzhian/ui": "^7",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@hookform/resolvers": "^5.2.1",
"@monaco-editor/react": "4.7.0",
"@mui/icons-material": "^6.4.12",
"@mui/lab": "6.0.0-beta.19",
"@mui/material": "^6.4.12",
"@mui/icons-material": "^7",
"@mui/material": "^7",
"@yokowu/modelkit-ui": "2.0.6",
"@tailwindcss/vite": "^4.1.12",
"ahooks": "^3.8.4",

243
ui/pnpm-lock.yaml generated
View File

@@ -8,9 +8,9 @@ importers:
.:
dependencies:
'@c-x/ui':
specifier: ^1.0.9
version: 1.0.9(87983aa74373e7bdff176d7db35e2938)
'@ctzhian/ui':
specifier: ^7
version: 7.0.5(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@emotion/react':
specifier: ^11.14.0
version: 11.14.0(@types/react@19.1.10)(react@19.1.1)
@@ -24,20 +24,17 @@ importers:
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/icons-material':
specifier: ^6.4.12
version: 6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab':
specifier: 6.0.0-beta.19
version: 6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
specifier: ^7
version: 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/material':
specifier: ^6.4.12
version: 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
specifier: ^7
version: 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tailwindcss/vite':
specifier: ^4.1.12
version: 4.1.12(vite@6.3.5(@types/node@24.2.1)(jiti@2.5.1)(lightningcss@1.30.1))
'@yokowu/modelkit-ui':
specifier: 2.0.6
version: 2.0.6(@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/utils@7.3.1(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(i18next@25.5.2(typescript@5.8.3))(typescript@5.8.3)
version: 2.0.6(@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(i18next@25.5.2(typescript@5.8.3))(typescript@5.8.3)
ahooks:
specifier: ^3.8.4
version: 3.9.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -308,6 +305,17 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
'@ctzhian/ui@7.0.5':
resolution: {integrity: sha512-BPeEePM9K4U9vV43qFGT/zOkRMfH6GlgBjeECUIZpAyv9D1kLNn7mPC1ldCdYxIYhMqyv1dz3jb5rkkNHt+OTg==}
peerDependencies:
'@emotion/react': ^11
'@emotion/styled': ^11
'@mui/icons-material': ^7
'@mui/material': ^7
'@mui/utils': ^7
react: '>=16.9.0'
react-dom: '>=16.9.0'
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
@@ -674,16 +682,8 @@ packages:
'@mui/core-downloads-tracker@6.5.0':
resolution: {integrity: sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==}
'@mui/icons-material@6.5.0':
resolution: {integrity: sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@mui/material': ^6.5.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/core-downloads-tracker@7.3.2':
resolution: {integrity: sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==}
'@mui/icons-material@7.3.2':
resolution: {integrity: sha512-TZWazBjWXBjR6iGcNkbKklnwodcwj0SrChCNHc9BhD9rBgET22J1eFhHsEmvSvru9+opDy3umqAimQjokhfJlQ==}
@@ -737,6 +737,26 @@ packages:
'@types/react':
optional: true
'@mui/material@7.3.2':
resolution: {integrity: sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@mui/material-pigment-css': ^7.3.2
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@mui/material-pigment-css':
optional: true
'@types/react':
optional: true
'@mui/private-theming@6.4.9':
resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==}
engines: {node: '>=14.0.0'}
@@ -747,6 +767,16 @@ packages:
'@types/react':
optional: true
'@mui/private-theming@7.3.2':
resolution: {integrity: sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/styled-engine@6.5.0':
resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==}
engines: {node: '>=14.0.0'}
@@ -760,6 +790,19 @@ packages:
'@emotion/styled':
optional: true
'@mui/styled-engine@7.3.2':
resolution: {integrity: sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
'@emotion/styled': ^11.3.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@mui/system@6.5.0':
resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==}
engines: {node: '>=14.0.0'}
@@ -776,6 +819,22 @@ packages:
'@types/react':
optional: true
'@mui/system@7.3.2':
resolution: {integrity: sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@types/react':
optional: true
'@mui/types@7.2.24':
resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==}
peerDependencies:
@@ -784,8 +843,8 @@ packages:
'@types/react':
optional: true
'@mui/types@7.4.5':
resolution: {integrity: sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==}
'@mui/types@7.4.6':
resolution: {integrity: sha512-NVBbIw+4CDMMppNamVxyTccNv0WxtDb7motWDlMeSC8Oy95saj1TIZMGynPpFLePt3yOD8TskzumeqORCgRGWw==}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
@@ -802,8 +861,8 @@ packages:
'@types/react':
optional: true
'@mui/utils@7.3.1':
resolution: {integrity: sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==}
'@mui/utils@7.3.2':
resolution: {integrity: sha512-4DMWQGenOdLnM3y/SdFQFwKsCLM+mqxzvoWp9+x2XdEzXapkznauHLiXtSohHs/mc0+5/9UACt1GdugCX2te5g==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -3260,26 +3319,23 @@ snapshots:
- magicast
- typescript
'@c-x/ui@1.0.9(87983aa74373e7bdff176d7db35e2938)':
'@c-x/ui@1.0.9(2095097f83dff6549ba477ae9943cd74)':
dependencies:
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/icons-material': 6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab': 6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab': 6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-virtuoso: 4.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@c-x/ui@1.0.9(9e5bbf95f65f6bba045b6e0e314ece93)':
'@ctzhian/ui@7.0.5(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/icons-material': 7.3.2(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/lab': 6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/utils': 7.3.1(@types/react@19.1.10)(react@19.1.1)
'@mui/icons-material': 7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-virtuoso: 4.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -3611,9 +3667,9 @@ snapshots:
'@mui/base@5.0.0-beta.66(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@floating-ui/react-dom': 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@mui/types': 7.4.6(@types/react@19.1.10)
'@mui/utils': 6.4.9(@types/react@19.1.10)(react@19.1.1)
'@popperjs/core': 2.11.8
clsx: 2.1.1
@@ -3625,13 +3681,7 @@ snapshots:
'@mui/core-downloads-tracker@6.5.0': {}
'@mui/icons-material@6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.10
'@mui/core-downloads-tracker@7.3.2': {}
'@mui/icons-material@7.3.2(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
@@ -3641,13 +3691,21 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.10
'@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
'@mui/icons-material@7.3.2(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.10
'@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/base': 5.0.0-beta.66(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/material': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/types': 7.4.5(@types/react@19.1.10)
'@mui/types': 7.4.6(@types/react@19.1.10)
'@mui/utils': 6.4.9(@types/react@19.1.10)(react@19.1.1)
clsx: 2.1.1
prop-types: 15.8.1
@@ -3660,7 +3718,7 @@ snapshots:
'@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@mui/core-downloads-tracker': 6.5.0
'@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/types': 7.2.24(@types/react@19.1.10)
@@ -3679,18 +3737,61 @@ snapshots:
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/core-downloads-tracker': 7.3.2
'@mui/system': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/types': 7.4.6(@types/react@19.1.10)
'@mui/utils': 7.3.2(@types/react@19.1.10)(react@19.1.1)
'@popperjs/core': 2.11.8
'@types/react-transition-group': 4.4.12(@types/react@19.1.10)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/private-theming@6.4.9(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@mui/utils': 6.4.9(@types/react@19.1.10)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.10
'@mui/private-theming@7.3.2(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/utils': 7.3.2(@types/react@19.1.10)(react@19.1.1)
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@types/react': 19.1.10
'@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/styled-engine@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
@@ -3703,7 +3804,7 @@ snapshots:
'@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@mui/private-theming': 6.4.9(@types/react@19.1.10)(react@19.1.1)
'@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)
'@mui/types': 7.2.24(@types/react@19.1.10)
@@ -3717,19 +3818,35 @@ snapshots:
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/system@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/private-theming': 7.3.2(@types/react@19.1.10)(react@19.1.1)
'@mui/styled-engine': 7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(react@19.1.1)
'@mui/types': 7.4.6(@types/react@19.1.10)
'@mui/utils': 7.3.2(@types/react@19.1.10)(react@19.1.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 19.1.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@types/react': 19.1.10
'@mui/types@7.2.24(@types/react@19.1.10)':
optionalDependencies:
'@types/react': 19.1.10
'@mui/types@7.4.5(@types/react@19.1.10)':
'@mui/types@7.4.6(@types/react@19.1.10)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
optionalDependencies:
'@types/react': 19.1.10
'@mui/utils@6.4.9(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
'@mui/types': 7.2.24(@types/react@19.1.10)
'@types/prop-types': 15.7.15
clsx: 2.1.1
@@ -3739,10 +3856,10 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.10
'@mui/utils@7.3.1(@types/react@19.1.10)(react@19.1.1)':
'@mui/utils@7.3.2(@types/react@19.1.10)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.2
'@mui/types': 7.4.5(@types/react@19.1.10)
'@babel/runtime': 7.28.4
'@mui/types': 7.4.6(@types/react@19.1.10)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
@@ -4212,9 +4329,9 @@ snapshots:
'@vue/shared@3.5.18': {}
'@yokowu/modelkit-ui@2.0.6(@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/utils@7.3.1(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(i18next@25.5.2(typescript@5.8.3))(typescript@5.8.3)':
'@yokowu/modelkit-ui@2.0.6(@mui/lab@6.0.0-beta.19(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@mui/material@7.3.2(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(i18next@25.5.2(typescript@5.8.3))(typescript@5.8.3)':
dependencies:
'@c-x/ui': 1.0.9(9e5bbf95f65f6bba045b6e0e314ece93)
'@c-x/ui': 1.0.9(2095097f83dff6549ba477ae9943cd74)
'@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
'@mui/icons-material': 7.3.2(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)
@@ -4525,7 +4642,7 @@ snapshots:
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
csstype: 3.1.3
dotenv@17.2.1: {}
@@ -4956,7 +5073,7 @@ snapshots:
i18next@25.5.2(typescript@5.8.3):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
optionalDependencies:
typescript: 5.8.3
@@ -5788,7 +5905,7 @@ snapshots:
react-i18next@15.7.3(i18next@25.5.2(typescript@5.8.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
html-parse-stringify: 3.0.1
i18next: 25.5.2(typescript@5.8.3)
react: 19.1.1
@@ -5846,7 +5963,7 @@ snapshots:
react-transition-group@4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.28.4
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1

View File

@@ -10,19 +10,19 @@
* ---------------------------------------------------------------
*/
import { message as Message } from "@c-x/ui";
import { message as Message } from '@ctzhian/ui';
import type {
AxiosInstance,
AxiosRequestConfig,
HeadersDefaults,
ResponseType,
} from "axios";
import axios from "axios";
} from 'axios';
import axios from 'axios';
export type QueryParamsType = Record<string | number, any>;
export interface FullRequestParams
extends Omit<AxiosRequestConfig, "data" | "params" | "url" | "responseType"> {
extends Omit<AxiosRequestConfig, 'data' | 'params' | 'url' | 'responseType'> {
/** set parameter to `true` for call `securityWorker` for this request */
secure?: boolean;
/** request path */
@@ -39,34 +39,34 @@ export interface FullRequestParams
export type RequestParams = Omit<
FullRequestParams,
"body" | "method" | "query" | "path"
'body' | 'method' | 'query' | 'path'
>;
export interface ApiConfig<SecurityDataType = unknown>
extends Omit<AxiosRequestConfig, "data" | "cancelToken"> {
extends Omit<AxiosRequestConfig, 'data' | 'cancelToken'> {
securityWorker?: (
securityData: SecurityDataType | null,
securityData: SecurityDataType | null
) => Promise<AxiosRequestConfig | void> | AxiosRequestConfig | void;
secure?: boolean;
format?: ResponseType;
}
export enum ContentType {
Json = "application/json",
FormData = "multipart/form-data",
UrlEncoded = "application/x-www-form-urlencoded",
Text = "text/plain",
Json = 'application/json',
FormData = 'multipart/form-data',
UrlEncoded = 'application/x-www-form-urlencoded',
Text = 'text/plain',
}
const whitePathnameList = ["/user/login", "/login", "/auth", "/invite"];
const whiteApiList = ["/api/v1/user/profile", "/api/v1/admin/profile"];
const whitePathnameList = ['/user/login', '/login', '/auth', '/invite'];
const whiteApiList = ['/api/v1/user/profile', '/api/v1/admin/profile'];
const redirectToLogin = () => {
const redirectAfterLogin = encodeURIComponent(location.href);
const search = `redirect=${redirectAfterLogin}`;
const pathname = location.pathname.startsWith("/user")
? "/login"
: "/login/admin";
const pathname = location.pathname.startsWith('/user')
? '/login'
: '/login/admin';
window.location.href = `${pathname}`;
};
@@ -75,7 +75,7 @@ type ExtractDataProp<T> = T extends { data?: infer U } ? U : never;
export class HttpClient<SecurityDataType = unknown> {
public instance: AxiosInstance;
private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private securityWorker?: ApiConfig<SecurityDataType>['securityWorker'];
private secure?: boolean;
private format?: ResponseType;
@@ -88,7 +88,7 @@ export class HttpClient<SecurityDataType = unknown> {
this.instance = axios.create({
withCredentials: true,
...axiosConfig,
baseURL: axiosConfig.baseURL || "",
baseURL: axiosConfig.baseURL || '',
});
this.secure = secure;
this.format = format;
@@ -107,20 +107,20 @@ export class HttpClient<SecurityDataType = unknown> {
if (
whitePathnameList.find((item) => location.pathname.startsWith(item))
) {
return Promise.reject("尚未登录");
return Promise.reject('尚未登录');
}
Message.error("尚未登录");
Message.error('尚未登录');
redirectToLogin();
return Promise.reject("尚未登录");
return Promise.reject('尚未登录');
}
// 手动取消请求
if (err.code === "ERR_CANCELED") {
if (err.code === 'ERR_CANCELED') {
return;
}
const msg = err?.response?.data?.message || err?.message;
Message.error(msg);
return Promise.reject(msg);
},
}
);
}
@@ -130,7 +130,7 @@ export class HttpClient<SecurityDataType = unknown> {
protected mergeRequestParams(
params1: AxiosRequestConfig,
params2?: AxiosRequestConfig,
params2?: AxiosRequestConfig
): AxiosRequestConfig {
const method = params1.method || (params2 && params2.method);
@@ -151,7 +151,7 @@ export class HttpClient<SecurityDataType = unknown> {
}
protected stringifyFormItem(formItem: unknown) {
if (typeof formItem === "object" && formItem !== null) {
if (typeof formItem === 'object' && formItem !== null) {
return JSON.stringify(formItem);
} else {
return `${formItem}`;
@@ -168,7 +168,7 @@ export class HttpClient<SecurityDataType = unknown> {
const isFileType = formItem instanceof Blob || formItem instanceof File;
formData.append(
key,
isFileType ? formItem : this.stringifyFormItem(formItem),
isFileType ? formItem : this.stringifyFormItem(formItem)
);
}
@@ -186,7 +186,7 @@ export class HttpClient<SecurityDataType = unknown> {
...params
}: FullRequestParams): Promise<ExtractDataProp<T>> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.secure) &&
((typeof secure === 'boolean' ? secure : this.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
{};
@@ -197,7 +197,7 @@ export class HttpClient<SecurityDataType = unknown> {
type === ContentType.FormData &&
body &&
body !== null &&
typeof body === "object"
typeof body === 'object'
) {
body = this.createFormData(body as Record<string, unknown>);
}
@@ -206,7 +206,7 @@ export class HttpClient<SecurityDataType = unknown> {
type === ContentType.Text &&
body &&
body !== null &&
typeof body !== "string"
typeof body !== 'string'
) {
body = JSON.stringify(body);
}
@@ -216,7 +216,7 @@ export class HttpClient<SecurityDataType = unknown> {
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData
? { "Content-Type": type }
? { 'Content-Type': type }
: {}),
},
params: query,
@@ -226,4 +226,4 @@ export class HttpClient<SecurityDataType = unknown> {
});
};
}
export default new HttpClient({ format: "json" }).request;
export default new HttpClient({ format: 'json' }).request;

View File

@@ -2,7 +2,7 @@ import { styled } from '@mui/material';
const StyledCard = styled('div')(({ theme }) => ({
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius * 2.5,
borderRadius: (theme.shape.borderRadius as number) * 2.5,
backgroundColor: theme.palette.background.default,
}));

View File

@@ -1,11 +1,27 @@
import Card from '@/components/card';
import { Ellipsis, Modal } from '@c-x/ui';
import { Ellipsis, Modal } from '@ctzhian/ui';
import { useEffect, useRef, useState } from 'react';
import { DomainSecurityScanningResult, DomainSecurityScanningRiskDetail } from '@/api/types';
import { getSecurityScanningDetail, getUserSecurityScanningDetail } from '@/api';
import { Box, CircularProgress, Grid2 as Grid, List, ListItem, ListItemButton, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material';
import {
DomainSecurityScanningResult,
DomainSecurityScanningRiskDetail,
} from '@/api/types';
import {
getSecurityScanningDetail,
getUserSecurityScanningDetail,
} from '@/api';
import {
Box,
CircularProgress,
Grid,
List,
ListItem,
ListItemButton,
Stack,
ToggleButton,
ToggleButtonGroup,
} from '@mui/material';
import ListAltIcon from '@mui/icons-material/ListAlt';
import ViewSidebarOutlinedIcon from '@mui/icons-material/ViewSidebarOutlined';
import MonacoEditor from '@monaco-editor/react';
@@ -36,16 +52,18 @@ const RiskLevelBox = ({ level }: RiskLevelBoxProps) => {
if (!config) return null;
return (
<Box sx={{
backgroundColor: config.color,
color: '#fff',
borderRadius: '4px',
textAlign: 'center',
width: '80px',
minWidth: '80px',
fontSize: '12px',
lineHeight: '20px'
}}>
<Box
sx={{
backgroundColor: config.color,
color: '#fff',
borderRadius: '4px',
textAlign: 'center',
width: '80px',
minWidth: '80px',
fontSize: '12px',
lineHeight: '20px',
}}
>
{config.text}
</Box>
);
@@ -111,17 +129,21 @@ const TaskDetail = ({
task?: DomainSecurityScanningResult;
open: boolean;
onClose: () => void;
admin: boolean
admin: boolean;
}) => {
const [loading, setLoading] = useState(true);
const [vulns, setVulns] = useState<DomainSecurityScanningRiskDetail[]>([]);
const [vulDetail, setVulDetail] = useState<DomainSecurityScanningRiskDetail | undefined>(undefined);
const [vulDetail, setVulDetail] = useState<
DomainSecurityScanningRiskDetail | undefined
>(undefined);
const [viewMode, setViewMode] = useState<'list' | 'detail'>('detail');
const fetchData = async () => {
setLoading(true);
const resp = await (admin ? getSecurityScanningDetail : getUserSecurityScanningDetail)({
id: task?.id as string
const resp = await (admin
? getSecurityScanningDetail
: getUserSecurityScanningDetail)({
id: task?.id as string,
});
setVulns(resp);
setLoading(false);
@@ -131,10 +153,10 @@ const TaskDetail = ({
setVulns([]);
setVulDetail(undefined);
setViewMode('detail');
console.log(!!vulDetail)
console.log(!!vulDetail);
if (open) {
fetchData();
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task, open]);
@@ -149,86 +171,103 @@ const TaskDetail = ({
}, [vulDetail]);
// 高亮显示漏洞代码
const highlightVulnerability = (editor: Monaco.editor.IStandaloneCodeEditor, vulDetail: DomainSecurityScanningRiskDetail | undefined) => {
const highlightVulnerability = (
editor: Monaco.editor.IStandaloneCodeEditor,
vulDetail: DomainSecurityScanningRiskDetail | undefined
) => {
// 清除之前的装饰器
editor.deltaDecorations(editor.getModel()?.getAllDecorations().map(d => d.id) || [], []);
editor.deltaDecorations(
editor
.getModel()
?.getAllDecorations()
.map((d) => d.id) || [],
[]
);
// 如果有 start 和 end 位置信息,则设置选区
if (vulDetail?.start && vulDetail?.end) {
const startLine = vulDetail.start.line ?? 1;
const startColumn = vulDetail.start.col ?? 1;
const endLine = vulDetail.end.line ?? startLine;
const endColumn = vulDetail.end.col ?? startColumn;
// 设置选区
const selection = {
startLineNumber: startLine,
startColumn: startColumn,
endLineNumber: endLine,
endColumn: endColumn
endColumn: endColumn,
};
editor.setSelection(selection);
// 添加装饰器以增强高亮效果
editor.deltaDecorations([], [
{
range: selection,
options: {
isWholeLine: false,
className: 'highlighted-code',
inlineClassName: 'highlighted-code-inline',
overviewRuler: {
color: 'rgba(255, 255, 0, 0.5)',
position: 1 // Monaco.Editor.OverviewRulerLane.Center
}
}
}
]);
editor.deltaDecorations(
[],
[
{
range: selection,
options: {
isWholeLine: false,
className: 'highlighted-code',
inlineClassName: 'highlighted-code-inline',
overviewRuler: {
color: 'rgba(255, 255, 0, 0.5)',
position: 1, // Monaco.Editor.OverviewRulerLane.Center
},
},
},
]
);
// 滚动到选区
editor.revealLineInCenter(startLine);
}
};
const handleEditorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor) => {
const handleEditorDidMount = (
editor: Monaco.editor.IStandaloneCodeEditor
) => {
// 保存编辑器实例
editorRef.current = editor;
// 初始高亮
highlightVulnerability(editor, vulDetail);
};
return (
<Modal title={
<Stack direction={'row'} >
{vulDetail && <ToggleButtonGroup
exclusive
value={viewMode}
size="small"
onChange={(e, value) => {
setViewMode(value)
}}
>
<ToggleButton value="list">
<ListAltIcon />
</ToggleButton>
<ToggleButton value="detail" disabled={!vulDetail}>
<ViewSidebarOutlinedIcon sx={{ transform: 'rotate(180deg)' }} />
</ToggleButton>
</ToggleButtonGroup>}
<Ellipsis
sx={{
fontWeight: 'bold',
fontSize: 20,
lineHeight: '40px',
width: 700,
ml: '20px'
}}
>
{task?.name} / {task?.project_name}
</Ellipsis>
</Stack>
<Modal
title={
<Stack direction={'row'}>
{vulDetail && (
<ToggleButtonGroup
exclusive
value={viewMode}
size='small'
onChange={(e, value) => {
setViewMode(value);
}}
>
<ToggleButton value='list'>
<ListAltIcon />
</ToggleButton>
<ToggleButton value='detail' disabled={!vulDetail}>
<ViewSidebarOutlinedIcon sx={{ transform: 'rotate(180deg)' }} />
</ToggleButton>
</ToggleButtonGroup>
)}
<Ellipsis
sx={{
fontWeight: 'bold',
fontSize: 20,
lineHeight: '40px',
width: 700,
ml: '20px',
}}
>
{task?.name} / {task?.project_name}
</Ellipsis>
</Stack>
}
width={1200}
open={open}
@@ -237,23 +276,34 @@ const TaskDetail = ({
>
<Card sx={{ p: 0, background: 'transparent', boxShadow: 'none' }}>
<Grid container sx={{ height: '70vh' }}>
<Grid size={viewMode === 'detail' && !!vulDetail ? 5 : 12} sx={{
height: '100%',
overflow: 'auto'
}}>
<Grid
size={viewMode === 'detail' && !!vulDetail ? 5 : 12}
sx={{
height: '100%',
overflow: 'auto',
}}
>
<List>
{loading ? (
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
<div
style={{
display: 'flex',
justifyContent: 'center',
padding: '20px',
}}
>
<CircularProgress />
</div>
) : (
vulns.map((vuln) => (
<ListItem key={vuln.id}
<ListItem
key={vuln.id}
sx={{
padding: 0,
width: '100%'
}}>
<ListItemButton
width: '100%',
}}
>
<ListItemButton
selected={vulDetail?.id === vuln.id}
onClick={() => {
setVulDetail(vuln);
@@ -263,54 +313,90 @@ const TaskDetail = ({
borderBottomStyle: 'solid',
borderBottomColor: 'background.paper',
fontSize: '14px',
width: '100%'
}}>
<Stack direction={"column"} sx={{
width: '100%'
}}>
<Stack direction={"row"}>
<RiskLevelBox level={vuln.level as 'severe' | 'critical' | 'suggest'} />
<Box sx={{
width: '100%',
}}
>
<Stack
direction={'column'}
sx={{
width: '100%',
}}
>
<Stack direction={'row'}>
<RiskLevelBox
level={
vuln.level as 'severe' | 'critical' | 'suggest'
}
/>
<Box
sx={{
fontSize: '14px',
ml: '20px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
lineHeight: '20px',
}}
>
{vuln.desc}
</Box>
</Stack>
<Box
sx={{
color: 'text.tertiary',
fontSize: '14px',
ml: '20px',
mt: '6px',
width: '100%',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
lineHeight: '20px'
}}>{vuln.desc}</Box>
</Stack>
<Box sx={{
color: 'text.tertiary',
fontSize: '14px',
mt: '6px',
width: '100%',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
}}
>
{vuln.filename}:{vuln?.start?.line}
</Box>
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
fontSize: '12px',
mt: '6px',
}}> {vulDetail?.start?.line} {vulDetail?.start?.line !== vulDetail?.end?.line && `至第 ${vulDetail?.end?.line}`}</Box>}
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
fontSize: '12px',
mt: '6px',
}}>
<pre style={{
backgroundColor: '#fff',
padding: '10px',
borderRadius: '4px',
border: '1px solid #ccc',
overflow: 'hidden',
}}>{vulDetail?.lines}</pre>
</Box>}
{!!vulDetail && vulDetail?.id === vuln.id && <Box sx={{
fontSize: '12px',
mt: '6px',
}}>{vulDetail?.fix}</Box>}
{!!vulDetail && vulDetail?.id === vuln.id && (
<Box
sx={{
fontSize: '12px',
mt: '6px',
}}
>
{vulDetail?.start?.line}
{vulDetail?.start?.line !== vulDetail?.end?.line &&
`至第 ${vulDetail?.end?.line}`}
</Box>
)}
{!!vulDetail && vulDetail?.id === vuln.id && (
<Box
sx={{
fontSize: '12px',
mt: '6px',
}}
>
<pre
style={{
backgroundColor: '#fff',
padding: '10px',
borderRadius: '4px',
border: '1px solid #ccc',
overflow: 'hidden',
}}
>
{vulDetail?.lines}
</pre>
</Box>
)}
{!!vulDetail && vulDetail?.id === vuln.id && (
<Box
sx={{
fontSize: '12px',
mt: '6px',
}}
>
{vulDetail?.fix}
</Box>
)}
</Stack>
</ListItemButton>
</ListItem>
@@ -318,30 +404,40 @@ const TaskDetail = ({
)}
</List>
</Grid>
{viewMode === 'detail' && !!vulDetail && <Grid size={7} sx={{
height: '100%',
overflow: 'auto'
}}>
<Box sx={{
width: '100%',
height: '100%',
}}>
<style> {`.monaco-editor.vs-dark .highlighted-code { background-color: rgba(255, 0, 0, 0.3) !important; }`} </style>
<MonacoEditor
height="100%"
value={vulDetail?.content || ''}
theme='vs-dark'
language={getLanguageByFilename(vulDetail?.filename)}
options={{
readOnly: true,
minimap: {
enabled: false
}
{viewMode === 'detail' && !!vulDetail && (
<Grid
size={7}
sx={{
height: '100%',
overflow: 'auto',
}}
>
<Box
sx={{
width: '100%',
height: '100%',
}}
onMount={handleEditorDidMount}
></MonacoEditor>
</Box>
</Grid>}
>
<style>
{' '}
{`.monaco-editor.vs-dark .highlighted-code { background-color: rgba(255, 0, 0, 0.3) !important; }`}{' '}
</style>
<MonacoEditor
height='100%'
value={vulDetail?.content || ''}
theme='vs-dark'
language={getLanguageByFilename(vulDetail?.filename)}
options={{
readOnly: true,
minimap: {
enabled: false,
},
}}
onMount={handleEditorDidMount}
></MonacoEditor>
</Box>
</Grid>
)}
</Grid>
</Card>
</Modal>

View File

@@ -1,19 +1,17 @@
import { useState, useEffect } from 'react';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import { getSecurityScanningList } from '@/api/SecurityScanning';
import dayjs from 'dayjs';
import Card from '@/components/card';
import {
Autocomplete,
Box,
Stack,
TextField,
Tooltip,
} from '@mui/material';
import { Autocomplete, Box, Stack, TextField, Tooltip } from '@mui/material';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { DomainSecurityScanningResult, DomainSecurityScanningRiskResult, DomainUser } from '@/api/types';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import {
DomainSecurityScanningResult,
DomainSecurityScanningRiskResult,
DomainUser,
} from '@/api/types';
import User from '@/components/user';
import TaskDetail from './taskDetail';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
@@ -23,17 +21,21 @@ import { getUserSecurityScanningList } from '@/api';
const CodeScanTaskList = ({
admin,
users
users,
}: {
admin: boolean,
users: DomainUser[]
admin: boolean;
users: DomainUser[];
}) => {
const [page, setPage] = useState(1);
const [size, setSize] = useState(20);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<DomainSecurityScanningResult[]>([]);
const [detail, setDetail] = useState<DomainSecurityScanningResult | undefined>();
const [dataSource, setDataSource] = useState<DomainSecurityScanningResult[]>(
[]
);
const [detail, setDetail] = useState<
DomainSecurityScanningResult | undefined
>();
const [filterUser, setFilterUser] = useState('');
const fetchData = async (params: {
@@ -43,7 +45,9 @@ const CodeScanTaskList = ({
author?: string;
}) => {
setLoading(true);
const res = await (admin ? getSecurityScanningList : getUserSecurityScanningList)({
const res = await (admin
? getSecurityScanningList
: getUserSecurityScanningList)({
page: params.page || page,
size: params.size || size,
author: params.author || filterUser,
@@ -57,7 +61,7 @@ const CodeScanTaskList = ({
setPage(1);
fetchData({
page: 1,
author: filterUser
author: filterUser,
});
}, [filterUser]);
@@ -69,71 +73,111 @@ const CodeScanTaskList = ({
render: (project_name, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.name}
</Box>
<Box sx={{
color: 'text.tertiary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
mt: '4px'
}}>
{(record.status === 'pending') && <Stack direction={'row'}>
<AutoModeIcon sx={{
width: '16px',
height: '16px',
color: 'info.main'
}} />
<Box sx={{
lineHeight: '16px',
ml: '4px'
}}></Box>
</Stack>}
{(record.status === 'running') && <Stack direction={'row'}>
<AutoModeIcon sx={{
width: '16px',
height: '16px',
color: 'info.main',
animation: 'spin 1s linear infinite',
'@keyframes spin': {
'0%': {
transform: 'rotate(0deg)',
},
'100%': {
transform: 'rotate(360deg)',
},
},
}} />
<Box sx={{
lineHeight: '16px',
ml: '4px'
}}></Box>
</Stack>}
{(record.status === 'success') && <Stack direction={'row'}>
<CheckCircleOutlineIcon sx={{
width: '16px',
height: '16px',
color: 'success.main'
}} />
<Box sx={{
lineHeight: '16px',
ml: '4px'
}}></Box>
</Stack>}
{(record.status === 'failed') && <Tooltip title={record.error}>
<Box
sx={{
color: 'text.tertiary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
mt: '4px',
}}
>
{record.status === 'pending' && (
<Stack direction={'row'}>
<ErrorOutlineIcon sx={{
width: '16px',
height: '16px',
color: 'error.main'
}} />
<Box sx={{
lineHeight: '16px',
ml: '4px'
}}></Box>
<AutoModeIcon
sx={{
width: '16px',
height: '16px',
color: 'info.main',
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
</Tooltip>}
)}
{record.status === 'running' && (
<Stack direction={'row'}>
<AutoModeIcon
sx={{
width: '16px',
height: '16px',
color: 'info.main',
animation: 'spin 1s linear infinite',
'@keyframes spin': {
'0%': {
transform: 'rotate(0deg)',
},
'100%': {
transform: 'rotate(360deg)',
},
},
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
)}
{record.status === 'success' && (
<Stack direction={'row'}>
<CheckCircleOutlineIcon
sx={{
width: '16px',
height: '16px',
color: 'success.main',
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
)}
{record.status === 'failed' && (
<Tooltip title={record.error}>
<Stack direction={'row'}>
<ErrorOutlineIcon
sx={{
width: '16px',
height: '16px',
color: 'error.main',
}}
/>
<Box
sx={{
lineHeight: '16px',
ml: '4px',
}}
>
</Box>
</Stack>
</Tooltip>
)}
</Box>
</Stack>
);
@@ -145,10 +189,23 @@ const CodeScanTaskList = ({
render: (project_name, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.project_name}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.path}
</Box>
</Stack>
@@ -160,28 +217,30 @@ const CodeScanTaskList = ({
title: '扫描结果',
width: 260,
render(risk: DomainSecurityScanningRiskResult, record) {
const hasNoRisk = record.status !== 'pending' &&
const hasNoRisk =
record.status !== 'pending' &&
(!risk.severe_count || risk.severe_count <= 0) &&
(!risk.critical_count || risk.critical_count <= 0) &&
(!risk.suggest_count || risk.suggest_count <= 0);
const tip = []
const tip = [];
if (risk.severe_count && risk.severe_count > 0) {
tip.push(`严重安全告警 ${risk.severe_count}`)
}
tip.push(`严重安全告警 ${risk.severe_count}`);
}
if (risk.critical_count && risk.critical_count > 0) {
tip.push(`高风险安全提醒 ${risk.critical_count}`)
tip.push(`高风险安全提醒 ${risk.critical_count}`);
}
if (risk.suggest_count && risk.suggest_count > 0) {
tip.push(`低风险安全提醒 ${risk.suggest_count}`)
tip.push(`低风险安全提醒 ${risk.suggest_count}`);
}
return (
<Tooltip title={ hasNoRisk ? '暂无风险' : tip.join(', ')}>
<Stack direction='row'
<Tooltip title={hasNoRisk ? '暂无风险' : tip.join(', ')}>
<Stack
direction='row'
onClick={() => {
if (!hasNoRisk) {
setDetail(record)
setDetail(record);
}
}}
sx={{
@@ -190,7 +249,10 @@ const CodeScanTaskList = ({
width: '200px',
height: '24px',
lineHeight: '24px',
background: (record.status === 'pending' || record.status === 'running') ? 'repeating-linear-gradient(45deg, #f0f0f0, #f0f0f0 10px, #e0e0e0 10px, #e0e0e0 20px)' : '#F1F2F8',
background:
record.status === 'pending' || record.status === 'running'
? 'repeating-linear-gradient(45deg, #f0f0f0, #f0f0f0 10px, #e0e0e0 10px, #e0e0e0 20px)'
: '#F1F2F8',
backgroundSize: '30px 30px',
animation: 'stripes 1s linear infinite',
borderRadius: '4px',
@@ -198,7 +260,7 @@ const CodeScanTaskList = ({
transition: 'box-shadow 0.3s ease',
userSelect: 'none',
'&:hover': {
cursor: "pointer",
cursor: 'pointer',
boxShadow: hasNoRisk ? '' : '0 0px 8px #FFCF62',
},
'@keyframes stripes': {
@@ -209,45 +271,82 @@ const CodeScanTaskList = ({
backgroundPosition: '30px 0',
},
},
}}>
{((record.status === 'success' || record.status === 'failed') && hasNoRisk) ? (
}}
>
{(record.status === 'success' || record.status === 'failed') &&
hasNoRisk ? (
// 如果没有风险,显示"无风险"
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
color: 'disabled.main',
}}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
color: 'disabled.main',
}}
>
</Box>
) : (
// 否则,显示原有的风险条
<>
{!!risk.severe_count && risk.severe_count > 0 && <Box sx={{
backgroundColor: 'risk.severe',
minWidth: '30px',
width: (risk.severe_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
textAlign: 'center'
}}>{risk.severe_count}</Box>}
{!!risk.critical_count && risk.critical_count > 0 && <Box sx={{
backgroundColor: 'risk.critical',
minWidth: '30px',
width: (risk.critical_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
textAlign: 'center'
}}>{risk.critical_count}</Box>}
{!!risk.suggest_count && risk.suggest_count > 0 && <Box sx={{
backgroundColor: 'risk.suggest',
minWidth: '30px',
width: (risk.suggest_count || 0) * 100 / ((risk.critical_count || 0) + (risk.severe_count || 0) + (risk.suggest_count || 0)) + '%',
textAlign: 'center'
}}>{risk.suggest_count}</Box>}
{!!risk.severe_count && risk.severe_count > 0 && (
<Box
sx={{
backgroundColor: 'risk.severe',
minWidth: '30px',
width:
((risk.severe_count || 0) * 100) /
((risk.critical_count || 0) +
(risk.severe_count || 0) +
(risk.suggest_count || 0)) +
'%',
textAlign: 'center',
}}
>
{risk.severe_count}
</Box>
)}
{!!risk.critical_count && risk.critical_count > 0 && (
<Box
sx={{
backgroundColor: 'risk.critical',
minWidth: '30px',
width:
((risk.critical_count || 0) * 100) /
((risk.critical_count || 0) +
(risk.severe_count || 0) +
(risk.suggest_count || 0)) +
'%',
textAlign: 'center',
}}
>
{risk.critical_count}
</Box>
)}
{!!risk.suggest_count && risk.suggest_count > 0 && (
<Box
sx={{
backgroundColor: 'risk.suggest',
minWidth: '30px',
width:
((risk.suggest_count || 0) * 100) /
((risk.critical_count || 0) +
(risk.severe_count || 0) +
(risk.suggest_count || 0)) +
'%',
textAlign: 'center',
}}
>
{risk.suggest_count}
</Box>
)}
</>
)}
</Stack>
</Tooltip>
)
);
},
},
{
@@ -273,39 +372,50 @@ const CodeScanTaskList = ({
render: (text) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(text).format('YYYY-MM-DD')}
</Box>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(text).format('HH:mm:ss')}
</Box>
</Stack>
)
);
},
},
];
return (
<Card sx={{ flex: 1, height: '100%' }}>
{admin && <Stack direction='row' spacing={2} sx={{ mb: 2 }}>
<Autocomplete
size='small'
sx={{ minWidth: 220 }}
options={users || []}
getOptionLabel={(option) => option.username || ''}
value={
users?.find((item) => item.username === filterUser) ||
null
}
onChange={(_, newValue) =>
setFilterUser(newValue ? newValue.username! : '')
}
isOptionEqualToValue={(option, value) =>
option.username === value.username
}
renderInput={(params) => <TextField {...params} label='成员' />}
clearOnEscape
/>
</Stack>}
{admin && (
<Stack direction='row' spacing={2} sx={{ mb: 2 }}>
<Autocomplete
size='small'
sx={{ minWidth: 220 }}
options={users || []}
getOptionLabel={(option) => option.username || ''}
value={users?.find((item) => item.username === filterUser) || null}
onChange={(_, newValue) =>
setFilterUser(newValue ? newValue.username! : '')
}
isOptionEqualToValue={(option, value) =>
option.username === value.username
}
renderInput={(params) => <TextField {...params} label='成员' />}
clearOnEscape
/>
</Stack>
)}
<Table
height={admin ? 'calc(100% - 52px)' : '100%'}
sx={{ mx: -2 }}

View File

@@ -1,5 +1,5 @@
import { Button, IconButton, Stack, Tooltip } from '@mui/material';
import { message } from '@c-x/ui';
import { message } from '@ctzhian/ui';
import { postLogout } from '@/api/User';
import { postAdminLogout } from '@/api/Admin';
import { useNavigate, useLocation } from 'react-router-dom';

View File

@@ -9,7 +9,9 @@ const StyledLabel = styled('div')<StyledLabelProps>(
// 获取颜色值
const getColor = (colorProp: string) => {
// 如果是主题预设颜色
if (['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)) {
if (
['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)
) {
return theme.palette[
colorProp as 'success' | 'warning' | 'error' | 'info' | 'disabled'
].main;
@@ -26,10 +28,14 @@ const StyledLabel = styled('div')<StyledLabelProps>(
// 获取背景颜色(淡化版本)
const getBackgroundColor = (colorProp: string) => {
if (['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)) {
if (
['success', 'warning', 'error', 'info', 'disabled'].includes(colorProp)
) {
// 使用主题的 light 版本,如果没有则使用 alpha 透明度
const palette =
theme.palette[colorProp as 'success' | 'warning' | 'error' | 'info' | 'disabled'];
theme.palette[
colorProp as 'success' | 'warning' | 'error' | 'info' | 'disabled'
];
return alpha(palette.main, 0.15);
}
// 如果是 default使用淡灰色背景
@@ -42,7 +48,7 @@ const StyledLabel = styled('div')<StyledLabelProps>(
return {
padding: theme.spacing(0.5, 1),
borderRadius: theme.shape.borderRadius * 4,
borderRadius: (theme.shape.borderRadius as number) * 4,
color: textColor,
backgroundColor: getBackgroundColor(color),
border: `1px solid ${alpha(textColor, 0.3)}`,

View File

@@ -1,4 +1,4 @@
import { message } from '@c-x/ui';
import { message } from '@ctzhian/ui';
import { Box, useTheme } from '@mui/material';
import React from 'react';
import ReactMarkdown, { Components } from 'react-markdown';

View File

@@ -1,6 +1,6 @@
import dayjs from 'dayjs';
import { useState } from 'react';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
import HelpCenter from '@/assets/json/help-center.json';
import Takeoff from '@/assets/json/takeoff.json';
import IconUpgrade from '@/assets/json/upgrade.json';
@@ -22,22 +22,21 @@ const AboutModal = ({
onClose,
curVersion,
latestVersion,
license
license,
}: LicenseModalProps) => {
const [openChangeLicense, setOpenChangeLicense] = useState(false);
const editionText = (edition: any) => {
if (edition === 0) {
return '开源版'
return '开源版';
} else if (edition === 1) {
return '联创版'
return '联创版';
} else if (edition === 2) {
return '企业版'
return '企业版';
} else {
return '未知'
return '未知';
}
}
};
return (
<Modal
@@ -45,18 +44,31 @@ const AboutModal = ({
width={600}
open={open}
onCancel={onClose}
footer={null}>
<Stack direction={'column'} gap={2} sx={{
fontSize: '14px'
}}>
footer={null}
>
<Stack
direction={'column'}
gap={2}
sx={{
fontSize: '14px',
}}
>
<Stack direction={'row'} gap={2} alignItems={'center'}>
<Box sx={{
width: '120px'
}}></Box>
<Box sx={{
width: '120px',
fontWeight: 700
}}>{curVersion}</Box>
<Box
sx={{
width: '120px',
}}
>
</Box>
<Box
sx={{
width: '120px',
fontWeight: 700,
}}
>
{curVersion}
</Box>
{latestVersion === `v${curVersion}` ? (
<Box sx={{ color: 'text.auxiliary', fontSize: 12 }}>
@@ -76,7 +88,7 @@ const AboutModal = ({
}
onClick={() => {
window.open(
'https://monkeycode.docs.baizhi.cloud/node/01980d22-db84-73b4-ae13-6a188e318048',
'https://monkeycode.docs.baizhi.cloud/node/01980d22-db84-73b4-ae13-6a188e318048'
);
}}
>
@@ -86,9 +98,13 @@ const AboutModal = ({
</Stack>
<Stack direction={'row'} gap={2} alignItems={'center'}>
<Box sx={{
width: '120px',
}}></Box>
<Box
sx={{
width: '120px',
}}
>
</Box>
<Box>{editionText(license?.edition)}</Box>
<Button
@@ -125,19 +141,30 @@ const AboutModal = ({
</Button>
</Stack>
{license && license?.edition !== 0 && <Stack direction={'row'} gap={2}>
<Box sx={{
width: '120px'
}}></Box>
<Box sx={{
}}>{dayjs.unix(license.started_at!).format('YYYY-MM-DD')} ~ {dayjs.unix(license.expired_at!).format('YYYY-MM-DD')}</Box>
</Stack>}
{license && license?.edition !== 0 && (
<Stack direction={'row'} gap={2}>
<Box
sx={{
width: '120px',
}}
>
</Box>
<Box sx={{}}>
{dayjs.unix(license.started_at!).format('YYYY-MM-DD')} ~{' '}
{dayjs.unix(license.expired_at!).format('YYYY-MM-DD')}
</Box>
</Stack>
)}
</Stack>
<ChangeLicense
open={openChangeLicense}
onClose={() => { setOpenChangeLicense(false) }} />
onClose={() => {
setOpenChangeLicense(false);
}}
/>
</Modal>
);
};
export default AboutModal;
export default AboutModal;

View File

@@ -1,6 +1,6 @@
import dayjs from 'dayjs';
import { useState } from 'react';
import { Ellipsis, message, Modal } from '@c-x/ui';
import { Ellipsis, message, Modal } from '@ctzhian/ui';
import { Box, Button, Link, Stack, TextField } from '@mui/material';
import { DomainLicenseResp } from '@/api/types';
import { v1LicenseCreate } from '@/api';
@@ -10,73 +10,92 @@ interface LicenseModalProps {
onClose: () => void;
}
const ChangeLicense = ({
open,
onClose
}: LicenseModalProps) => {
const ChangeLicense = ({ open, onClose }: LicenseModalProps) => {
const [code, setCode] = useState('');
const [verifing, setVerifing] = useState(false);
const updateLicense = () => {
if (code.length === 0) {
message.error("授权码不能为空");
message.error('授权码不能为空');
return;
}
setVerifing(true);
v1LicenseCreate({
license_type: 'code',
license_code: code,
license_file: '' as any
}).then((resp: DomainLicenseResp) => {
message.success("切换授权成功");
console.log(resp)
setVerifing(false);
onClose();
setTimeout(() => {
location.reload();
}, 1000)
}).catch(() => {
message.error("遇到一点意外,无法激活");
setVerifing(false);
license_file: '' as any,
})
}
.then((resp: DomainLicenseResp) => {
message.success('切换授权成功');
console.log(resp);
setVerifing(false);
onClose();
setTimeout(() => {
location.reload();
}, 1000);
})
.catch(() => {
message.error('遇到一点意外,无法激活');
setVerifing(false);
});
};
return (
<Modal
<Modal
title='切换 MonkeyCode 授权'
width={400}
open={open}
onCancel={onClose}
footer={null}>
<Stack direction={'column'} gap={2} sx={{
fontSize: '14px'
}}>
<Stack direction={'row'} gap={2} sx={{
fontSize: '14px'
}}>
<TextField label="授权码" variant="outlined" sx={{
width: '100%'
footer={null}
>
<Stack
direction={'column'}
gap={2}
sx={{
fontSize: '14px',
}}
>
<Stack
direction={'row'}
gap={2}
sx={{
fontSize: '14px',
}}
onChange={(e) => {
setCode(e.target.value);
}} />
>
<TextField
label='授权码'
variant='outlined'
sx={{
width: '100%',
}}
onChange={(e) => {
setCode(e.target.value);
}}
/>
</Stack>
<Stack direction={'row'} gap={2} sx={{
fontSize: '14px'
}}>
<Button variant="contained"
<Stack
direction={'row'}
gap={2}
sx={{
fontSize: '14px',
}}
>
<Button
variant='contained'
loading={verifing}
sx={{
width: '100%'
width: '100%',
}}
onClick={() => {
updateLicense()
}}>线</Button>
updateLicense();
}}
>
线
</Button>
</Stack>
</Stack>
</Modal>
);
};
export default ChangeLicense;
export default ChangeLicense;

View File

@@ -1,8 +1,8 @@
import Logo from '@/assets/images/logo.png';
import { alpha, Box, Button, Stack, useTheme, styled } from '@mui/material';
import { Icon } from '@c-x/ui';
import { Icon } from '@ctzhian/ui';
import { NavLink, useLocation } from 'react-router-dom';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
import { useMemo, useState } from 'react';
import Qrcode from '@/assets/images/qrcode.png';
import Version from './version';
@@ -145,7 +145,10 @@ const Sidebar = () => {
}
return isConfigModel
? ADMIN_MENUS.map((item) => ({ ...item, disabled: false }))
: ADMIN_MENUS.map((item) => ({ ...item, disabled: item.pathname !== 'general-setting' }));
: ADMIN_MENUS.map((item) => ({
...item,
disabled: item.pathname !== 'general-setting',
}));
}, [pathname, isConfigModel]);
return (

View File

@@ -6,7 +6,7 @@ import '@/assets/fonts/font.css';
import '@/assets/fonts/iconfont';
import './index.css';
import '@/assets/styles/markdown.css';
import { ThemeProvider } from '@c-x/ui';
import { ThemeProvider } from '@ctzhian/ui';
import { zhCN } from 'date-fns/locale/zh-CN';
import { setDefaultOptions } from 'date-fns';

View File

@@ -9,13 +9,13 @@ import {
Paper,
Alert,
CircularProgress,
Grid2 as Grid,
Grid,
InputAdornment,
IconButton,
Divider,
Stack,
} from '@mui/material';
import { CusTabs, Icon, message } from '@c-x/ui';
import { CusTabs, Icon, message } from '@ctzhian/ui';
// @ts-ignore
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
@@ -354,9 +354,9 @@ const AuthPage = () => {
<CusTabs
list={[
{ label: '研发成员', value: 'user' },
{ label: '管理员', value: 'admin', disabled: true},
{ label: '管理员', value: 'admin', disabled: true },
]}
value={"user"}
value={'user'}
sx={{
width: '100%',
mb: 4,

View File

@@ -2,7 +2,7 @@ import Avatar from '@/components/avatar';
import Card from '@/components/card';
import { getChatInfo } from '@/api/Billing';
import MarkDown from '@/components/markDown';
import { Ellipsis, Modal } from '@c-x/ui';
import { Ellipsis, Modal } from '@ctzhian/ui';
import { styled } from '@mui/material';
import logo from '@/assets/images/logo.png';

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import { getListChatRecord } from '@/api/Billing';
import dayjs from 'dayjs';
@@ -17,7 +17,7 @@ import {
import StyledLabel from '@/components/label';
import ChatDetailModal from './chatDetailModal';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { DomainChatRecord, DomainUser } from '@/api/types';
import { addCommasToNumber } from '@/utils';
import User from '@/components/user';

View File

@@ -1,6 +1,6 @@
import Card from '@/components/card';
import { getCompletionInfo } from '@/api/Billing';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
import MonacoEditor from '@monaco-editor/react';
import { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';

View File

@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { DomainCompletionRecord, DomainUser } from '@/api/types';
import { getListCompletionRecord } from '@/api/Billing';
import { useRequest } from 'ahooks';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import Card from '@/components/card';
import {
Box,
@@ -17,7 +17,7 @@ import {
import { getListUser } from '@/api/User';
import dayjs from 'dayjs';
import { useDebounceFn } from 'ahooks';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { addCommasToNumber } from '@/utils';
import CompletionDetailModal from './completionDetailModal';
import StyledLabel from '@/components/label';
@@ -154,14 +154,26 @@ const Completion = () => {
render(value: number) {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(value).format('YYYY-MM-DD')}
</Box>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(value).format('HH:mm:ss')}
</Box>
</Stack>
)
);
},
},
];

View File

@@ -1,6 +1,6 @@
import { Box, Stack } from '@mui/material';
import { DomainUserCodeRank } from '@/api/types';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
import { StyledItem, StyledSerialNumber, StyledText } from './statisticCard';
import User from '@/components/user';
import Avatar from '@/components/avatar';
@@ -49,13 +49,13 @@ const ContributionModal = ({
minWidth: 0,
}}
>
<Avatar
name={item.user?.username}
src={item.user?.avatar_url}
sx={{ width: 20, height: 20, fontSize: 12 }}
/>
<StyledText className='active-user-name'>{item.username}
<StyledText className='active-user-name'>
{item.username}
</StyledText>
</Stack>
</StyledItem>

View File

@@ -5,12 +5,8 @@ import {
getUserCodeRankDashboard,
} from '@/api/Dashboard';
import { SecondTimeRange } from '@/components/ui/calendar';
import {
getRecent60MinutesData,
getTimeRange,
getRangeData,
} from '@/utils';
import { Grid2 as Grid, styled } from '@mui/material';
import { getRecent60MinutesData, getTimeRange, getRangeData } from '@/utils';
import { Grid, styled } from '@mui/material';
import { useRequest } from 'ahooks';
import { useMemo } from 'react';
import BarCharts from './barCharts';
@@ -73,12 +69,32 @@ const GlobalStatistic = ({
} = timeStatData || {};
const label = { valueLabel: 'value' };
return {
userActiveChartData: getRangeData(timeDuration, active_users, precision, label),
userActiveChartData: getRangeData(
timeDuration,
active_users,
precision,
label
),
chatChartData: getRangeData(timeDuration, chats, precision, label),
codeCompletionChartData: getRangeData(timeDuration, code_completions, precision, label),
codeLineChartData: getRangeData(timeDuration, lines_of_code, precision, label),
codeCompletionChartData: getRangeData(
timeDuration,
code_completions,
precision,
label
),
codeLineChartData: getRangeData(
timeDuration,
lines_of_code,
precision,
label
),
realTimeTokenChartData: getRecent60MinutesData(real_time_tokens, label),
acceptedPerChartData: getRangeData(timeDuration, accepted_per, precision, label),
acceptedPerChartData: getRangeData(
timeDuration,
accepted_per,
precision,
label
),
};
}, [timeStatData, precision]);

View File

@@ -5,7 +5,7 @@ import {
} from '@/api/types';
import Avatar from '@/components/avatar';
import Card from '@/components/card';
import { Icon } from '@c-x/ui';
import { Icon } from '@ctzhian/ui';
import {
Box,
IconButton,

View File

@@ -5,8 +5,13 @@ import {
} from '@/api/Dashboard';
import { DomainUser } from '@/api/types';
import { SecondTimeRange } from '@/components/ui/calendar';
import { getRangeData, getRecent24HoursData, getRecentDaysData, getTimeRange } from '@/utils';
import { Grid2 as Grid } from '@mui/material';
import {
getRangeData,
getRecent24HoursData,
getRecentDaysData,
getTimeRange,
} from '@/utils';
import { Grid } from '@mui/material';
import { useRequest } from 'ahooks';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
@@ -85,8 +90,18 @@ const MemberStatistic = ({
precision,
label
);
const codeLineChartData = getRangeData(timeDuration, lines_of_code, precision, label);
const acceptedPerChartData = getRangeData(timeDuration, accepted_per, precision, label);
const codeLineChartData = getRangeData(
timeDuration,
lines_of_code,
precision,
label
);
const acceptedPerChartData = getRangeData(
timeDuration,
accepted_per,
precision,
label
);
return {
chatChartData,
codeCompletionChartData,

View File

@@ -1,4 +1,4 @@
import { Empty } from '@c-x/ui';
import { Empty } from '@ctzhian/ui';
import { Box, Stack, styled } from '@mui/material';
import dayjs from 'dayjs';
import { useState } from 'react';
@@ -117,7 +117,7 @@ export const ContributionCard = ({
gap={1.5}
sx={{
flex: 1,
minWidth: 0
minWidth: 0,
}}
>
<Avatar
@@ -125,7 +125,8 @@ export const ContributionCard = ({
src={item.user?.avatar_url}
sx={{ width: 20, height: 20, fontSize: 12 }}
/>
<StyledText className='active-user-name'>{item.username}
<StyledText className='active-user-name'>
{item.username}
</StyledText>
</Stack>
</StyledItem>

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import { getListUser } from '@/api/User';
import { Stack, Box } from '@mui/material';
import { CusTabs } from '@c-x/ui';
import { CusTabs } from '@ctzhian/ui';
import GlobalStatistic from './components/globalStatistic';
import { useRequest } from 'ahooks';
import MemberStatistic from './components/memberStatistic';
@@ -112,7 +112,7 @@ const Dashboard = () => {
};
const handleTimeRangeChange = (value: any) => {
if (value) {
console.log(value)
console.log(value);
setTimeRange(value);
} else {
setTimeRange(get24HoursRange());
@@ -136,7 +136,12 @@ const Dashboard = () => {
return (
<Stack gap={2} sx={{ height: '100%' }}>
<Stack direction='row' gap={2} justifyContent='space-between' alignItems='center'>
<Stack
direction='row'
gap={2}
justifyContent='space-between'
alignItems='center'
>
<CusTabs
value={tabValue}
onChange={onTabChange}

View File

@@ -7,10 +7,10 @@ import {
putAiemployeeUpdate,
postUserAiemployeeCreate,
putUserAiemployeeUpdate,
} from "@/api";
import { Ellipsis, message, Modal } from "@c-x/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
} from '@/api';
import { Ellipsis, message, Modal } from '@ctzhian/ui';
import { zodResolver } from '@hookform/resolvers/zod';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import {
Box,
Checkbox,
@@ -22,13 +22,13 @@ import {
Radio,
RadioGroup,
Stack,
TextField
} from "@mui/material";
import { useEffect, useState } from "react";
import CopyToClipboard from "react-copy-to-clipboard";
import { Controller, useForm } from "react-hook-form";
import { useLocation } from "react-router-dom";
import { z } from "zod";
TextField,
} from '@mui/material';
import { useEffect, useState } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { Controller, useForm } from 'react-hook-form';
import { useLocation } from 'react-router-dom';
import { z } from 'zod';
const formSchema = z.object({
issue_at_comment: z.boolean().default(false),
@@ -38,15 +38,15 @@ const formSchema = z.object({
mr_pr_at_comment: z.boolean().default(false),
/** 是否处理全部新增PR/MR */
mr_pr_open: z.boolean().default(false),
name: z.string().min(1, "必填项").default(""),
name: z.string().min(1, '必填项').default(''),
platform: z
.enum(ConstsRepoPlatform)
.default(ConstsRepoPlatform.RepoPlatformGitLab),
position: z
.enum(ConstsAIEmployeePosition)
.default(ConstsAIEmployeePosition.AIEmployeePositionEngineer),
repo_url: z.string().min(1, "必填项").default(""),
token: z.string().min(1, "必填项").default(""),
repo_url: z.string().min(1, '必填项').default(''),
token: z.string().min(1, '必填项').default(''),
});
const EmloyeeModal = ({
@@ -62,10 +62,10 @@ const EmloyeeModal = ({
}) => {
const [webhookOpen, setWebhookOpen] = useState(false);
const [webhookUrl, setWebhookUrl] = useState<
Pick<DomainAIEmployee, "webhook_url" | "webhook_secret"> | undefined
Pick<DomainAIEmployee, 'webhook_url' | 'webhook_secret'> | undefined
>();
const { pathname } = useLocation();
const isUser = pathname.startsWith("/user/");
const isUser = pathname.startsWith('/user/');
const {
reset,
register,
@@ -79,7 +79,10 @@ const EmloyeeModal = ({
const handleChange = handleSubmit(
async (data) => {
const res = await (record
? (isUser ? putUserAiemployeeUpdate : putAiemployeeUpdate)({ ...data, id: record.id })
? (isUser ? putUserAiemployeeUpdate : putAiemployeeUpdate)({
...data,
id: record.id,
})
: (isUser ? postUserAiemployeeCreate : postAiemployeeCreate)(data));
onChanged?.(); // 调用回调函数,刷新列表
setWebhookUrl(res);
@@ -90,10 +93,10 @@ const EmloyeeModal = ({
}
);
const checkitems = [
{ key: "issue_open", label: "自动跟进所有的 Issue" },
{ key: "mr_pr_open", label: "自动跟进所有的 Merge/Pull Request" },
{ key: "issue_at_comment", label: "允许在 Issue 中被 @" },
{ key: "mr_pr_at_comment", label: "允许在 Merge/Pull Request 中被 @" },
{ key: 'issue_open', label: '自动跟进所有的 Issue' },
{ key: 'mr_pr_open', label: '自动跟进所有的 Merge/Pull Request' },
{ key: 'issue_at_comment', label: '允许在 Issue 中被 @' },
{ key: 'mr_pr_at_comment', label: '允许在 Merge/Pull Request 中被 @' },
] as const;
useEffect(() => {
if (open) reset(record || formSchema.parse({}));
@@ -105,36 +108,36 @@ const EmloyeeModal = ({
return (
<>
<Modal
title={record ? "编辑 AI 员工" : "创建 AI 员工"}
title={record ? '编辑 AI 员工' : '创建 AI 员工'}
width={600}
open={open}
// onOk={() => setOpenWebhook(true)}
onOk={handleChange}
onCancel={onClose}
okText={record ? "更新" : "创建"}
cancelText="取消"
okText={record ? '更新' : '创建'}
cancelText='取消'
>
<Stack spacing={2} sx={{ fontSize: "13px" }}>
<Stack spacing={2} sx={{ fontSize: '13px' }}>
<TextField
label="AI 员工名称"
label='AI 员工名称'
fullWidth
size="small"
{...register("name")}
size='small'
{...register('name')}
error={!!errors.name}
helperText={errors.name?.message}
/>
<Stack
direction={"row"}
direction={'row'}
component={FormControl}
alignItems="center"
alignItems='center'
spacing={3}
>
<FormLabel id="demo-row-radio-buttons-group-label">
<FormLabel id='demo-row-radio-buttons-group-label'>
AI
</FormLabel>
<Controller
control={control}
name="position"
name='position'
render={({ field }) => (
<RadioGroup
row
@@ -180,25 +183,25 @@ const EmloyeeModal = ({
))}
</FormGroup>
<TextField
label="工作项目的 Git 仓库"
label='工作项目的 Git 仓库'
fullWidth
size="small"
{...register("repo_url")}
size='small'
{...register('repo_url')}
error={!!errors.repo_url}
helperText={errors.repo_url?.message}
/>
<Stack
direction={"row"}
direction={'row'}
component={FormControl}
alignItems="center"
alignItems='center'
spacing={3}
>
<FormLabel id="demo-row-radio-buttons-group-label">
<FormLabel id='demo-row-radio-buttons-group-label'>
Git
</FormLabel>
<Controller
control={control}
name="platform"
name='platform'
render={({ field }) => (
<RadioGroup
row
@@ -226,72 +229,72 @@ const EmloyeeModal = ({
/>
</Stack>
<TextField
label="Git 仓库的访问令牌"
label='Git 仓库的访问令牌'
fullWidth
size="small"
{...register("token")}
size='small'
{...register('token')}
error={!!errors.token}
helperText={errors.token?.message}
/>
</Stack>
</Modal>
<Modal
title="Webhook 配置信息"
title='Webhook 配置信息'
width={830}
open={webhookOpen}
onOk={onCloseWebhook}
onCancel={onCloseWebhook}
showCancel={false}
okText="确定"
okText='确定'
>
{webhookUrl?.webhook_secret && (
<Stack
spacing={2}
sx={{
mt: 2,
fontSize: "14px",
"& > .MuiStack-root > div:nth-child(2)": {
fontSize: '14px',
'& > .MuiStack-root > div:nth-child(2)': {
fontWeight: 600,
bgcolor: "background.paper",
bgcolor: 'background.paper',
px: 1,
py: 0.5,
borderRadius: "4px",
borderRadius: '4px',
},
}}
>
<Stack direction="row" alignItems={"center"} spacing={2}>
<Box sx={{ flexShrink: 0, minWidth: "130px" }}>Webhook URL: </Box>
<Stack direction='row' alignItems={'center'} spacing={2}>
<Box sx={{ flexShrink: 0, minWidth: '130px' }}>Webhook URL: </Box>
<Ellipsis>{webhookUrl?.webhook_url}</Ellipsis>
<CopyToClipboard
text={webhookUrl?.webhook_url || ""}
text={webhookUrl?.webhook_url || ''}
onCopy={() => {
message.success("复制成功");
message.success('复制成功');
}}
>
<IconButton
color="primary"
size="small"
sx={{ alignSelf: "flex-end" }}
color='primary'
size='small'
sx={{ alignSelf: 'flex-end' }}
>
<ContentCopyIcon />
</IconButton>
</CopyToClipboard>
</Stack>
<Stack direction="row" alignItems={"center"} spacing={2}>
<Box sx={{ flexShrink: 0, minWidth: "130px" }}>
Webhook Secret:{" "}
<Stack direction='row' alignItems={'center'} spacing={2}>
<Box sx={{ flexShrink: 0, minWidth: '130px' }}>
Webhook Secret:{' '}
</Box>
<Ellipsis>{webhookUrl?.webhook_secret}</Ellipsis>
<CopyToClipboard
text={webhookUrl?.webhook_secret || ""}
text={webhookUrl?.webhook_secret || ''}
onCopy={() => {
message.success("复制成功");
message.success('复制成功');
}}
>
<IconButton
color="primary"
size="small"
sx={{ alignSelf: "flex-end" }}
color='primary'
size='small'
sx={{ alignSelf: 'flex-end' }}
>
<ContentCopyIcon />
</IconButton>

View File

@@ -1,29 +1,29 @@
import { Ellipsis, Icon, message, Modal, Table } from "@c-x/ui";
import { useEffect, useState } from "react";
import { Ellipsis, Icon, message, Modal, Table } from '@ctzhian/ui';
import { useEffect, useState } from 'react';
import Card from "@/components/card";
import { Box, Button, Stack } from "@mui/material";
import Card from '@/components/card';
import { Box, Button, Stack } from '@mui/material';
import { deleteAiemployeeDelete, getAiemployeeList } from "@/api/AiEmployee";
import { deleteAiemployeeDelete, getAiemployeeList } from '@/api/AiEmployee';
import {
deleteUserAiemployeeDelete,
getUserAiemployeeList,
} from "@/api/UserAiEmployee";
} from '@/api/UserAiEmployee';
import {
ConstsRepoPlatform,
DomainAIEmployee,
DomainUpdateAIEmployeeReq,
} from "@/api/types";
import { ColumnsType } from "@c-x/ui/dist/Table";
import dayjs from "dayjs";
import EmloyeeModal from "./emloyeeModal";
import { useLocation } from "react-router-dom";
} from '@/api/types';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import dayjs from 'dayjs';
import EmloyeeModal from './emloyeeModal';
import { useLocation } from 'react-router-dom';
const gitPlatformIcons = {
[ConstsRepoPlatform.RepoPlatformGitHub]: "icon-github",
[ConstsRepoPlatform.RepoPlatformGitLab]: "icon-gitlab",
[ConstsRepoPlatform.RepoPlatformGitee]: "icon-gitee",
[ConstsRepoPlatform.RepoPlatformGitea]: "icon-gitea",
[ConstsRepoPlatform.RepoPlatformGitHub]: 'icon-github',
[ConstsRepoPlatform.RepoPlatformGitLab]: 'icon-gitlab',
[ConstsRepoPlatform.RepoPlatformGitee]: 'icon-gitee',
[ConstsRepoPlatform.RepoPlatformGitea]: 'icon-gitea',
} as const;
const EmployeeTaskList = () => {
const [page, setPage] = useState(1);
@@ -33,7 +33,7 @@ const EmployeeTaskList = () => {
const [dataSource, setDataSource] = useState<DomainAIEmployee[]>([]);
const [detail, setDetail] = useState<DomainUpdateAIEmployeeReq | undefined>();
const { pathname } = useLocation();
const isUser = pathname.startsWith("/user/");
const isUser = pathname.startsWith('/user/');
const [open, setOpen] = useState(false);
const onClose = () => {
setOpen(false);
@@ -71,17 +71,17 @@ const EmployeeTaskList = () => {
};
const handleDelete = (record: DomainAIEmployee) => {
Modal.confirm({
title: "提示",
okText: "删除",
title: '提示',
okText: '删除',
okButtonProps: {
color: "error",
color: 'error',
},
content: (
<>
AI {" "}
<Box component="span" sx={{ fontWeight: 700, color: "text.primary" }}>
AI {' '}
<Box component='span' sx={{ fontWeight: 700, color: 'text.primary' }}>
{record!.name}
</Box>{" "}
</Box>{' '}
</>
),
@@ -89,7 +89,7 @@ const EmployeeTaskList = () => {
(isUser ? deleteUserAiemployeeDelete : deleteAiemployeeDelete)({
id: record.id!,
}).then(() => {
message.success("删除成功");
message.success('删除成功');
fetchData({});
});
},
@@ -97,22 +97,22 @@ const EmployeeTaskList = () => {
};
const columns: ColumnsType<DomainAIEmployee> = [
{
dataIndex: "name",
title: "AI 员工",
dataIndex: 'name',
title: 'AI 员工',
width: 240,
render: (name, record) => {
return (
<Stack direction="column">
<Stack direction="row" alignItems="center" spacing={1}>
<Stack direction='column'>
<Stack direction='row' alignItems='center' spacing={1}>
<Icon
type="icon-jiqiren"
sx={{ fontSize: 20, color: "text.main" }}
type='icon-jiqiren'
sx={{ fontSize: 20, color: 'text.main' }}
/>
<Box
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.name}
@@ -120,17 +120,17 @@ const EmployeeTaskList = () => {
</Stack>
<Box
sx={{
color: "text.tertiary",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontSize: "0.8rem",
mt: "4px",
color: 'text.tertiary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '0.8rem',
mt: '4px',
}}
>
<Box
sx={{
lineHeight: "16px",
lineHeight: '16px',
}}
>
{record?.position}
@@ -141,36 +141,36 @@ const EmployeeTaskList = () => {
},
},
{
title: "工作项目",
dataIndex: "platform",
title: '工作项目',
dataIndex: 'platform',
width: 300,
render: (platform, record) => {
return (
<Stack direction="column">
<Stack direction="row" alignItems="center" spacing={1}>
<Stack direction='column'>
<Stack direction='row' alignItems='center' spacing={1}>
<Icon
type={gitPlatformIcons[record.platform as ConstsRepoPlatform]}
sx={{ fontSize: 16, color: "text.main" }}
sx={{ fontSize: 16, color: 'text.main' }}
/>
<Box
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.repository_url
? record?.repository_url.split("/").pop()
? record?.repository_url.split('/').pop()
: record?.platform}
</Box>
</Stack>
<Ellipsis
sx={{
color: "text.secondary",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontSize: "0.8rem",
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '0.8rem',
}}
>
{record?.repository_url}
@@ -180,27 +180,27 @@ const EmployeeTaskList = () => {
},
},
{
title: "创建者",
dataIndex: "creater",
title: '创建者',
dataIndex: 'creater',
render: (creater, record) => {
return (
<Stack direction="column">
<Stack direction='column'>
<Box
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.admin?.username}
</Box>
<Box
sx={{
color: "text.secondary",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontSize: "0.8rem",
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '0.8rem',
}}
>
{dayjs(Number(record?.created_at) * 1000).fromNow()}
@@ -210,27 +210,27 @@ const EmployeeTaskList = () => {
},
},
{
title: "工作状态",
dataIndex: "status",
title: '工作状态',
dataIndex: 'status',
render: (status, record) => {
return (
<Stack direction="column">
<Stack direction='column'>
<Box
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.completed_count || 0}
</Box>
<Box
sx={{
color: "text.secondary",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
fontSize: "0.8rem",
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '0.8rem',
}}
>
{dayjs(Number(record?.last_active_at) * 1000).fromNow()}
@@ -240,17 +240,17 @@ const EmployeeTaskList = () => {
},
},
{
title: "操作",
dataIndex: "opt",
title: '操作',
dataIndex: 'opt',
render: (status, record) => {
return (
<Stack direction="row" spacing={1}>
<Button variant="text" onClick={() => handleEdit(record)}>
<Stack direction='row' spacing={1}>
<Button variant='text' onClick={() => handleEdit(record)}>
</Button>
<Button
variant="text"
color="error"
variant='text'
color='error'
onClick={() => handleDelete(record)}
>
@@ -261,18 +261,18 @@ const EmployeeTaskList = () => {
},
];
return (
<Card sx={{ flex: 1, height: "100%" }}>
<Stack height="100%">
<Card sx={{ flex: 1, height: '100%' }}>
<Stack height='100%'>
<Button
variant="contained"
size="small"
sx={{ mb: 2, alignSelf: "flex-end" }}
variant='contained'
size='small'
sx={{ mb: 2, alignSelf: 'flex-end' }}
onClick={() => setOpen(true)}
>
AI
</Button>
<Table
sx={{ mx: -2, flexGrow: 1, overflow: "auto" }}
sx={{ mx: -2, flexGrow: 1, overflow: 'auto' }}
PaginationProps={{
sx: {
pt: 2,
@@ -282,7 +282,7 @@ const EmployeeTaskList = () => {
loading={loading}
columns={columns}
dataSource={dataSource}
rowKey="id"
rowKey='id'
pagination={{
page,
pageSize: size,

View File

@@ -1,10 +1,10 @@
import Card from '@/components/card';
import { Stack, Box } from '@mui/material';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import dayjs from 'dayjs';
import { useRequest } from 'ahooks';
import { getAdminLoginHistory } from '@/api/Admin';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { DomainListAdminLoginHistoryResp } from '@/api/types';
import User from '@/components/user';
@@ -13,7 +13,9 @@ type LoginHistory = NonNullable<
>[number];
const AdminLoginHistory = () => {
const { data, loading } = useRequest(() => getAdminLoginHistory({page: 1, size: 50}));
const { data, loading } = useRequest(() =>
getAdminLoginHistory({ page: 1, size: 50 })
);
const columns: ColumnsType<LoginHistory> = [
{
title: '账号',
@@ -30,7 +32,9 @@ const AdminLoginHistory = () => {
<Stack direction='column'>
<Box>{record?.ip_info?.ip}</Box>
<Box sx={{ color: 'text.secondary' }}>
{record?.ip_info?.country === '中国' ? ('' + record?.ip_info?.province + '-' + record?.ip_info?.city) : (record?.ip_info?.country || '未知')}
{record?.ip_info?.country === '中国'
? '' + record?.ip_info?.province + '-' + record?.ip_info?.city
: record?.ip_info?.country || '未知'}
</Box>
</Stack>
);
@@ -45,7 +49,7 @@ const AdminLoginHistory = () => {
<Box>{dayjs.unix(text).format('YYYY-MM-DD')}</Box>
<Box>{dayjs.unix(text).format('HH:mm:ss')}</Box>
</Stack>
)
);
},
},
];
@@ -55,23 +59,27 @@ const AdminLoginHistory = () => {
direction='row'
justifyContent='space-between'
alignItems='center'
sx={{
sx={{
mb: 2,
height: 32,
fontWeight: 'bold',
}}
}}
>
<Box sx={{
'&::before': {
content: '""',
display: 'inline-block',
width: 4,
height: 12,
bgcolor: 'common.black',
borderRadius: '2px',
mr: 1,
},
}}></Box>
<Box
sx={{
'&::before': {
content: '""',
display: 'inline-block',
width: 4,
height: 12,
bgcolor: 'common.black',
borderRadius: '2px',
mr: 1,
},
}}
>
</Box>
</Stack>
<Table
columns={columns}

View File

@@ -14,9 +14,14 @@ import {
import { AdminPanelSettings, Person } from '@mui/icons-material';
import { postCreateAdmin } from '@/api/Admin';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { deleteDeleteAdmin, getAdminProfile, getListAdminUser, postGrantRole } from '@/api/Admin';
import { Table, Modal, message } from '@c-x/ui';
import { ColumnsType } from '@c-x/ui/dist/Table';
import {
deleteDeleteAdmin,
getAdminProfile,
getListAdminUser,
postGrantRole,
} from '@/api/Admin';
import { Table, Modal, message } from '@ctzhian/ui';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { useRequest } from 'ahooks';
import dayjs from 'dayjs';
import { DomainAdminUser } from '@/api/types';
@@ -109,7 +114,9 @@ const AddAdminModal = ({
<MenuItem value={2}></MenuItem>
</Select>
{errors.roleId && (
<Box sx={{ color: 'error.main', fontSize: '0.75rem', mt: 0.5 }}>
<Box
sx={{ color: 'error.main', fontSize: '0.75rem', mt: 0.5 }}
>
{errors.roleId?.message as string}
</Box>
)}
@@ -186,17 +193,19 @@ const AdminUser = () => {
const [open, setOpen] = useState(false);
const { data, loading, refresh } = useRequest(() => getListAdminUser({}));
const { data: currentAdmin } = useRequest(() => getAdminProfile({}));
const handleRoleChange = (adminId: string, newRoleId: number) => {
postGrantRole({
admin_id: adminId,
role_ids: [newRoleId],
}).then(() => {
message.success('角色更新成功');
refresh();
}).catch(() => {
message.error('角色更新失败');
});
})
.then(() => {
message.success('角色更新成功');
refresh();
})
.catch(() => {
message.error('角色更新失败');
});
};
const onDeleteAdmin = (data: DomainAdminUser) => {
@@ -240,40 +249,42 @@ const AdminUser = () => {
<Select
size='small'
value={record.role?.id || ''}
onChange={(e) => handleRoleChange(record.id!, Number(e.target.value))}
onChange={(e) =>
handleRoleChange(record.id!, Number(e.target.value))
}
displayEmpty
disabled={record.id === currentAdmin?.id}
renderValue={(value) => {
if (value === 1) {
return (
<Stack direction="row" alignItems="center" gap={1}>
<AdminPanelSettings fontSize="small" />
<Stack direction='row' alignItems='center' gap={1}>
<AdminPanelSettings fontSize='small' />
<span></span>
</Stack>
);
} else if (value === 2) {
return (
<Stack direction="row" alignItems="center" gap={1}>
<Person fontSize="small" />
<Stack direction='row' alignItems='center' gap={1}>
<Person fontSize='small' />
<span></span>
</Stack>
);
}
return "请选择角色";
return '请选择角色';
}}
sx={{
mr: 1
mr: 1,
}}
>
<MenuItem value={1}>
<Stack direction="row" alignItems="center" gap={1}>
<AdminPanelSettings fontSize="small" />
<Stack direction='row' alignItems='center' gap={1}>
<AdminPanelSettings fontSize='small' />
<span></span>
</Stack>
</MenuItem>
<MenuItem value={2}>
<Stack direction="row" alignItems="center" gap={1}>
<Person fontSize="small" />
<Stack direction='row' alignItems='center' gap={1}>
<Person fontSize='small' />
<span></span>
</Stack>
</MenuItem>
@@ -287,10 +298,16 @@ const AdminUser = () => {
dataIndex: 'created_at',
width: 120,
render: (text, record) => {
return <Stack>
<Box>{dayjs.unix(text).fromNow()}</Box>
<Box>{record.last_active_at === 0 ? '从未使用' : dayjs.unix(text).fromNow()}</Box>
</Stack>
return (
<Stack>
<Box>{dayjs.unix(text).fromNow()}</Box>
<Box>
{record.last_active_at === 0
? '从未使用'
: dayjs.unix(text).fromNow()}
</Box>
</Stack>
);
},
},
{
@@ -317,23 +334,27 @@ const AdminUser = () => {
direction='row'
justifyContent='space-between'
alignItems='center'
sx={{
sx={{
mb: 2,
height: 32,
fontWeight: 'bold',
}}
}}
>
<Box sx={{
'&::before': {
content: '""',
display: 'inline-block',
width: 4,
height: 12,
bgcolor: 'common.black',
borderRadius: '2px',
mr: 1,
},
}}></Box>
<Box
sx={{
'&::before': {
content: '""',
display: 'inline-block',
width: 4,
height: 12,
bgcolor: 'common.black',
borderRadius: '2px',
mr: 1,
},
}}
>
</Box>
<Button
variant='contained'
color='primary'

View File

@@ -1,26 +1,26 @@
import Card from "@/components/card";
import { Box, Button, Stack, TextField } from "@mui/material";
import { message } from "@c-x/ui";
import { useEffect, useState } from "react";
import { getGetSetting, putUpdateSetting } from "@/api/Admin";
import { DomainUpdateSettingReq } from "@/api/types";
import Card from '@/components/card';
import { Box, Button, Stack, TextField } from '@mui/material';
import { message } from '@ctzhian/ui';
import { useEffect, useState } from 'react';
import { getGetSetting, putUpdateSetting } from '@/api/Admin';
import { DomainUpdateSettingReq } from '@/api/types';
const CardServiceSettings = () => {
const [baseURL, setBaseURL] = useState("");
const [initialBaseURL, setInitialBaseURL] = useState("");
const [baseURL, setBaseURL] = useState('');
const [initialBaseURL, setInitialBaseURL] = useState('');
useEffect(() => {
const fetchInitialBaseURL = async () => {
try {
const response = await getGetSetting();
const initialValue = response.base_url || "";
const initialValue = response.base_url || '';
setBaseURL(initialValue);
setInitialBaseURL(initialValue);
} catch (err: any) {
message.error("Failed to fetch initial base URL:", err);
message.error('Failed to fetch initial base URL:', err);
// 如果获取失败,可以设置一个默认值或者保持空字符串
setBaseURL("");
setInitialBaseURL("");
setBaseURL('');
setInitialBaseURL('');
}
};
@@ -35,11 +35,11 @@ const CardServiceSettings = () => {
try {
const parsedURL = new URL(url);
// Check if the protocol is either http or https
if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") {
if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
return false;
}
// Check if the URL is a base URL (no path or only root path)
if (parsedURL.pathname !== "/" && parsedURL.pathname !== "") {
if (parsedURL.pathname !== '/' && parsedURL.pathname !== '') {
return false;
}
return true;
@@ -51,7 +51,7 @@ const CardServiceSettings = () => {
const handleSave = async () => {
// Check if the baseURL is valid before saving
if (baseURL && !isValidURL(baseURL)) {
message.error("请输入一个有效的 URL 地址");
message.error('请输入一个有效的 URL 地址');
return;
}
@@ -60,50 +60,50 @@ const CardServiceSettings = () => {
base_url: baseURL,
};
await putUpdateSetting(setting);
message.success("保存成功");
message.success('保存成功');
setInitialBaseURL(baseURL);
} catch (err: any) {
message.error("保存失败:", err);
message.error('保存失败:', err);
}
};
const hasValueChanged = baseURL !== initialBaseURL;
return (
<Card sx={{ p: 0, mb: 2, borderBottom: "1px solid #e0e0e0" }}>
<Card sx={{ p: 0, mb: 2, borderBottom: '1px solid #e0e0e0' }}>
<Box
sx={{
fontWeight: "bold",
fontWeight: 'bold',
px: 2,
py: 1.5,
bgcolor: "rgb(248, 249, 250)",
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
bgcolor: 'rgb(248, 249, 250)',
borderTopLeftRadius: '10px',
borderTopRightRadius: '10px',
}}
>
MonkeyCode
</Box>
<Stack direction="column">
<Box sx={{ width: "100%" }}>
<Stack direction='column'>
<Box sx={{ width: '100%' }}>
<Stack
direction="row"
alignItems={"center"}
justifyContent={"space-between"}
direction='row'
alignItems={'center'}
justifyContent={'space-between'}
sx={{
m: 2,
height: 32,
fontWeight: "bold",
fontWeight: 'bold',
}}
>
<Box
sx={{
"&::before": {
'&::before': {
content: '""',
display: "inline-block",
display: 'inline-block',
width: 4,
height: 12,
bgcolor: "common.black",
borderRadius: "2px",
bgcolor: 'common.black',
borderRadius: '2px',
mr: 1,
},
}}
@@ -111,7 +111,7 @@ const CardServiceSettings = () => {
MonkeyCode
</Box>
{hasValueChanged && (
<Button variant="contained" size="small" onClick={handleSave}>
<Button variant='contained' size='small' onClick={handleSave}>
</Button>
)}
@@ -121,14 +121,14 @@ const CardServiceSettings = () => {
fullWidth
value={baseURL}
onChange={handleBaseURLChange}
placeholder={baseURL ? "" : window.location.origin}
placeholder={baseURL ? '' : window.location.origin}
/>
<Box
sx={{
mt: 1,
fontSize: "0.75rem",
color: "warning.main",
fontWeight: "normal",
fontSize: '0.75rem',
color: 'warning.main',
fontWeight: 'normal',
}}
>
VSCode MonkeyCode

View File

@@ -1,11 +1,11 @@
import CardServiceSettings from "./components/cardServiceSettings";
import { Grid2 as Grid } from "@mui/material";
import CardAdminUser from "./components/cardAdminUser";
import Model from "@/pages/model";
import CardServiceSettings from './components/cardServiceSettings';
import { Grid } from '@mui/material';
import CardAdminUser from './components/cardAdminUser';
import Model from '@/pages/model';
const GeneralSetting = () => {
return (
<Grid container spacing={2} sx={{ height: "100%" }}>
<Grid container spacing={2} sx={{ height: '100%' }}>
<Grid size={6}>
<CardServiceSettings />
<Model />

View File

@@ -15,7 +15,7 @@ import {
Typography,
Container,
Paper,
Grid2 as Grid,
Grid,
InputAdornment,
IconButton,
CircularProgress,
@@ -25,7 +25,7 @@ import {
import { useRequest } from 'ahooks';
import { postRegister, getUserOauthSignupOrIn } from '@/api/User';
import { getGetSetting } from '@/api/Admin';
import { Icon } from '@c-x/ui';
import { Icon } from '@ctzhian/ui';
import { ConstsLoginSource, DomainSetting } from '@/api/types';
import DownloadIcon from '@mui/icons-material/Download';

View File

@@ -7,7 +7,7 @@ import {
Container,
Paper,
CircularProgress,
Grid2 as Grid,
Grid,
InputAdornment,
IconButton,
Stack,
@@ -18,7 +18,7 @@ import { postAdminLogin } from '@/api/Admin';
import { useForm, Controller } from 'react-hook-form';
import { styled } from '@mui/material/styles';
import { ConstsLoginSource, DomainSetting } from '@/api/types';
import { Icon, CusTabs } from '@c-x/ui';
import { Icon, CusTabs } from '@ctzhian/ui';
import Logo from '@/assets/images/logo.png';
import { getRedirectUrl } from '@/utils';
import { getGetSetting } from '@/api/Admin';

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Modal, message } from '@c-x/ui';
import { Modal, message } from '@ctzhian/ui';
import { Box, TextField } from '@mui/material';
import { postCreateGroup } from '@/api/UserGroup';

View File

@@ -17,23 +17,40 @@ import {
SelectChangeEvent,
TextField,
} from '@mui/material';
import { Table, Modal, message } from '@c-x/ui';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { DomainAdminUser, DomainLicenseEdition, DomainUser, DomainUserGroup } from '@/api/types';
import { deleteDeleteGroup, getListAdminUser, getListUserGroup, v1LicenseList } from '@/api';
import { deleteRemoveAdminFromGroup, postGrantGroup, postAddUserToGroup, deleteRemoveUserFromGroup, putUpdateUserGroup } from '@/api/UserGroup';
import { Table, Modal, message } from '@ctzhian/ui';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import {
DomainAdminUser,
DomainLicenseEdition,
DomainUser,
DomainUserGroup,
} from '@/api/types';
import {
deleteDeleteGroup,
getListAdminUser,
getListUserGroup,
v1LicenseList,
} from '@/api';
import {
deleteRemoveAdminFromGroup,
postGrantGroup,
postAddUserToGroup,
deleteRemoveUserFromGroup,
putUpdateUserGroup,
} from '@/api/UserGroup';
import CreateGroupModal from './createGroupModal';
import UpdateGroupModal from './updateGroupModal';
import { Check } from '@mui/icons-material';
const GroupList = () => {
const [openCreateGroupModal, setOpenCreateGroupModal] = useState(false);
const [openUpdateGroupModal, setOpenUpdateGroupModal] = useState(false);
const [currentGroup, setCurrentGroup] = useState<DomainUserGroup | null>(null);
const groupData = useRequest(() => getListUserGroup({page: 1, size: 9999}));
const userData = useRequest(() => getListUser({page: 1, size: 9999}));
const adminData = useRequest(() => getListAdminUser({page: 1, size: 9999}));
const [currentGroup, setCurrentGroup] = useState<DomainUserGroup | null>(
null
);
const groupData = useRequest(() => getListUserGroup({ page: 1, size: 9999 }));
const userData = useRequest(() => getListUser({ page: 1, size: 9999 }));
const adminData = useRequest(() => getListAdminUser({ page: 1, size: 9999 }));
const [searchUser, setSearchUser] = useState('');
const [memberSearchText, setMemberSearchText] = useState('');
const [adminSearchText, setAdminSearchText] = useState('');
@@ -41,8 +58,8 @@ const GroupList = () => {
const memberInputRef = useRef<HTMLInputElement>(null);
const adminInputRef = useRef<HTMLInputElement>(null);
const license = useRequest(() => {
return v1LicenseList({})
}).data
return v1LicenseList({});
}).data;
const filteredData = useMemo(() => {
if (!groupData?.data?.groups) return [];
@@ -56,12 +73,20 @@ const GroupList = () => {
const filteredSelectUsers = useMemo(() => {
if (!userData.data?.users) return [];
return userData.data.users.filter(user => (user.username || '').toLowerCase().includes(memberSearchText.toLowerCase()));
return userData.data.users.filter((user) =>
(user.username || '')
.toLowerCase()
.includes(memberSearchText.toLowerCase())
);
}, [userData.data?.users, memberSearchText]);
const filteredSelectAdmins = useMemo(() => {
if (!adminData.data?.users) return [];
return adminData.data.users.filter(user => (user.username || '').toLowerCase().includes(adminSearchText.toLowerCase()));
return adminData.data.users.filter((user) =>
(user.username || '')
.toLowerCase()
.includes(adminSearchText.toLowerCase())
);
}, [adminData.data?.users, adminSearchText]);
const handleClick = (
@@ -120,196 +145,239 @@ const GroupList = () => {
title: '成员',
dataIndex: 'users',
render: (users, record) => {
return <FormControl sx={{ width: '100%' }}>
<InputLabel size='small'></InputLabel>
<Select
multiple
value={(users || []).map((u: DomainUser) => u.id)}
label='成员'
size='small'
onClose={() => setMemberSearchText('')}
onOpen={() => {
setTimeout(() => {
memberInputRef.current?.focus();
}, 100);
}}
MenuProps={{
autoFocus: false,
}}
renderValue={(selectedIds: string[]) => {
if (!Array.isArray(selectedIds)) return null;
const selectedUsers = (userData.data?.users || []).filter((user: DomainUser) =>
user.id && selectedIds.includes(user.id)
);
return selectedUsers.map((u: DomainUser) => (
<Chip size='small' key={u.id} label={u.username}></Chip>
));
}}
onChange={(event: SelectChangeEvent<string[]>) => {
console.log(event.target)
// 获取当前分组的成员ID列表
const currentUserIds = (users || []).map((user: DomainUser) => user.id!);
// 获取选择的成员ID列表
const selectedUserIds = event.target.value as string[];
// 计算新增的成员ID
const addedUserIds = selectedUserIds.filter(id => !currentUserIds.includes(id));
// 计算移除的成员ID
const removedUserIds = currentUserIds.filter((id: string) => !selectedUserIds.includes(id));
// 调用API添加成员
if (addedUserIds.length > 0) {
postAddUserToGroup({
user_ids: addedUserIds,
group_ids: [record.id!]
}).then(() => {
message.success('成员添加成功');
groupData.refresh();
}).catch(() => {
message.error('成员添加失败');
});
}
// 调用API移除成员
if (removedUserIds.length > 0) {
deleteRemoveUserFromGroup({
user_ids: removedUserIds,
group_id: record.id!
}).then(() => {
message.success('成员移除成功');
groupData.refresh();
}).catch(() => {
message.error('成员移除失败');
});
}
setMemberSearchText('');
}}
>
<Box
onKeyDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
sx={{ p: 1, position: 'sticky', top: -8, zIndex: 1, backgroundColor: 'background.paper' }}
return (
<FormControl sx={{ width: '100%' }}>
<InputLabel size='small'></InputLabel>
<Select
multiple
value={(users || []).map((u: DomainUser) => u.id)}
label='成员'
size='small'
onClose={() => setMemberSearchText('')}
onOpen={() => {
setTimeout(() => {
memberInputRef.current?.focus();
}, 100);
}}
MenuProps={{
autoFocus: false,
}}
renderValue={(selectedIds: string[]) => {
if (!Array.isArray(selectedIds)) return null;
const selectedUsers = (userData.data?.users || []).filter(
(user: DomainUser) => user.id && selectedIds.includes(user.id)
);
return selectedUsers.map((u: DomainUser) => (
<Chip size='small' key={u.id} label={u.username}></Chip>
));
}}
onChange={(event: SelectChangeEvent<string[]>) => {
console.log(event.target);
// 获取当前分组的成员ID列表
const currentUserIds = (users || []).map(
(user: DomainUser) => user.id!
);
// 获取选择的成员ID列表
const selectedUserIds = event.target.value as string[];
// 计算新增的成员ID
const addedUserIds = selectedUserIds.filter(
(id) => !currentUserIds.includes(id)
);
// 计算移除的成员ID
const removedUserIds = currentUserIds.filter(
(id: string) => !selectedUserIds.includes(id)
);
// 调用API添加成员
if (addedUserIds.length > 0) {
postAddUserToGroup({
user_ids: addedUserIds,
group_ids: [record.id!],
})
.then(() => {
message.success('成员添加成功');
groupData.refresh();
})
.catch(() => {
message.error('成员添加失败');
});
}
// 调用API移除成员
if (removedUserIds.length > 0) {
deleteRemoveUserFromGroup({
user_ids: removedUserIds,
group_id: record.id!,
})
.then(() => {
message.success('成员移除成功');
groupData.refresh();
})
.catch(() => {
message.error('成员移除失败');
});
}
setMemberSearchText('');
}}
>
<TextField
size="small"
fullWidth
inputRef={memberInputRef}
placeholder="搜索成员"
onChange={(e) => setMemberSearchText(e.target.value)}
/>
</Box>
{filteredSelectUsers?.map((user) => (
<MenuItem key={user.username} value={user.id}>
{(users.some((u: DomainUser) => {
return u.id === user.id
})) ? <>
{user.username}<Check sx={{ ml: 2, width: '16px' }} />
</> : <>
{user.username}
</>}
</MenuItem>)
)}
</Select>
</FormControl>;
<Box
onKeyDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
sx={{
p: 1,
position: 'sticky',
top: -8,
zIndex: 1,
backgroundColor: 'background.paper',
}}
>
<TextField
size='small'
fullWidth
inputRef={memberInputRef}
placeholder='搜索成员'
onChange={(e) => setMemberSearchText(e.target.value)}
/>
</Box>
{filteredSelectUsers?.map((user) => (
<MenuItem key={user.username} value={user.id}>
{users.some((u: DomainUser) => {
return u.id === user.id;
}) ? (
<>
{user.username}
<Check sx={{ ml: 2, width: '16px' }} />
</>
) : (
<>{user.username}</>
)}
</MenuItem>
))}
</Select>
</FormControl>
);
},
},
{
title: '管理员',
dataIndex: 'admins',
render: (admins, record) => {
return <FormControl sx={{ width: '100%' }}>
<InputLabel size='small'></InputLabel>
<Select
multiple
value={(admins || []).map((u: DomainAdminUser) => u.id)}
label='管理员'
size='small'
onClose={() => setAdminSearchText('')}
onOpen={() => {
setTimeout(() => {
adminInputRef.current?.focus();
}, 100);
}}
MenuProps={{
autoFocus: false,
}}
renderValue={(selectedIds: string[]) => {
if (!Array.isArray(selectedIds)) return null;
const selectedAdmins = (adminData.data?.users || []).filter((user: DomainAdminUser) =>
user.id && selectedIds.includes(user.id)
);
return selectedAdmins.map((u: DomainAdminUser) => (
<Chip size='small' key={u.id} label={u.username}></Chip>
));
}}
onChange={(event: SelectChangeEvent<string[]>) => {
console.log(event.target)
// 获取当前分组的管理员ID列表
const currentAdminIds = (admins || []).map((user: DomainAdminUser) => user.id!);
// 获取选择的管理员ID列表
const selectedAdminIds = event.target.value as string[];
// 计算新增的管理员ID
const addedAdminIds = selectedAdminIds.filter(id => !currentAdminIds.includes(id));
// 计算移除的管理员ID
const removedAdminIds = currentAdminIds.filter((id: string) => !selectedAdminIds.includes(id));
// 调用API添加管理员
if (addedAdminIds.length > 0) {
postGrantGroup({
admin_ids: addedAdminIds,
group_ids: [record.id!]
}).then(() => {
message.success('管理员添加成功');
groupData.refresh();
}).catch(() => {
message.error('管理员添加失败');
});
}
// 调用API移除管理员
if (removedAdminIds.length > 0) {
deleteRemoveAdminFromGroup({
admin_ids: removedAdminIds,
group_id: record.id!
}).then(() => {
message.success('管理员移除成功');
groupData.refresh();
}).catch(() => {
message.error('管理员移除失败');
});
}
setAdminSearchText('');
}}
>
<Box
onKeyDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
sx={{ p: 1, position: 'sticky', top: -8, zIndex: 1, backgroundColor: 'background.paper' }}
return (
<FormControl sx={{ width: '100%' }}>
<InputLabel size='small'></InputLabel>
<Select
multiple
value={(admins || []).map((u: DomainAdminUser) => u.id)}
label='管理员'
size='small'
onClose={() => setAdminSearchText('')}
onOpen={() => {
setTimeout(() => {
adminInputRef.current?.focus();
}, 100);
}}
MenuProps={{
autoFocus: false,
}}
renderValue={(selectedIds: string[]) => {
if (!Array.isArray(selectedIds)) return null;
const selectedAdmins = (adminData.data?.users || []).filter(
(user: DomainAdminUser) =>
user.id && selectedIds.includes(user.id)
);
return selectedAdmins.map((u: DomainAdminUser) => (
<Chip size='small' key={u.id} label={u.username}></Chip>
));
}}
onChange={(event: SelectChangeEvent<string[]>) => {
console.log(event.target);
// 获取当前分组的管理员ID列表
const currentAdminIds = (admins || []).map(
(user: DomainAdminUser) => user.id!
);
// 获取选择的管理员ID列表
const selectedAdminIds = event.target.value as string[];
// 计算新增的管理员ID
const addedAdminIds = selectedAdminIds.filter(
(id) => !currentAdminIds.includes(id)
);
// 计算移除的管理员ID
const removedAdminIds = currentAdminIds.filter(
(id: string) => !selectedAdminIds.includes(id)
);
// 调用API添加管理员
if (addedAdminIds.length > 0) {
postGrantGroup({
admin_ids: addedAdminIds,
group_ids: [record.id!],
})
.then(() => {
message.success('管理员添加成功');
groupData.refresh();
})
.catch(() => {
message.error('管理员添加失败');
});
}
// 调用API移除管理员
if (removedAdminIds.length > 0) {
deleteRemoveAdminFromGroup({
admin_ids: removedAdminIds,
group_id: record.id!,
})
.then(() => {
message.success('管理员移除成功');
groupData.refresh();
})
.catch(() => {
message.error('管理员移除失败');
});
}
setAdminSearchText('');
}}
>
<TextField
size="small"
fullWidth
inputRef={adminInputRef}
placeholder="搜索管理员"
onChange={(e) => setAdminSearchText(e.target.value)}
/>
</Box>
{filteredSelectAdmins?.map((user) => (
<MenuItem key={user.username} value={user.id}>
{(admins.some((u: DomainAdminUser) => {
return u.id === user.id
})) ? <>
{user.username}<Check sx={{ ml: 2, width: '16px' }} />
</> : <>
{user.username}
</>}
</MenuItem>)
)}
</Select>
</FormControl>;
<Box
onKeyDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
sx={{
p: 1,
position: 'sticky',
top: -8,
zIndex: 1,
backgroundColor: 'background.paper',
}}
>
<TextField
size='small'
fullWidth
inputRef={adminInputRef}
placeholder='搜索管理员'
onChange={(e) => setAdminSearchText(e.target.value)}
/>
</Box>
{filteredSelectAdmins?.map((user) => (
<MenuItem key={user.username} value={user.id}>
{admins.some((u: DomainAdminUser) => {
return u.id === user.id;
}) ? (
<>
{user.username}
<Check sx={{ ml: 2, width: '16px' }} />
</>
) : (
<>{user.username}</>
)}
</MenuItem>
))}
</Select>
</FormControl>
);
},
},
{
@@ -335,9 +403,7 @@ const GroupList = () => {
return (
<Card sx={{ maxHeight: '500px' }}>
<Menu anchorEl={anchorEl} open={!!anchorEl} onClose={handleClose}>
<MenuItem onClick={onUpdateGroup}>
</MenuItem>
<MenuItem onClick={onUpdateGroup}></MenuItem>
<MenuItem sx={{ color: 'error.main' }} onClick={onDeleteGroup}>
</MenuItem>
@@ -351,13 +417,21 @@ const GroupList = () => {
>
<Box sx={{ fontWeight: 700 }}></Box>
<Stack direction='row' gap={1}>
<TextField label='搜索' size='small' onChange={(e)=>setSearchUser(e.target.value)} />
<TextField
label='搜索'
size='small'
onChange={(e) => setSearchUser(e.target.value)}
/>
<Button
variant='contained'
color='primary'
onClick={() => setOpenCreateGroupModal(true)}
disabled={license?.edition !== DomainLicenseEdition.LicenseEditionEnterprise}
></Button>
disabled={
license?.edition !== DomainLicenseEdition.LicenseEditionEnterprise
}
>
</Button>
</Stack>
</Stack>
<Table

View File

@@ -1,19 +1,12 @@
import React, { useState, useMemo } from 'react';
import Card from '@/components/card';
import {
Grid2 as Grid,
Stack,
styled,
Switch,
Button,
Box,
} from '@mui/material';
import { Grid, Stack, styled, Switch, Button, Box } from '@mui/material';
import { useRequest } from 'ahooks';
import { getGetSetting, putUpdateSetting } from '@/api/Admin';
import MemberManage from './memberManage';
import LoginHistory from './loginHistory';
import { DomainLicenseEdition, v1LicenseList } from '@/api';
import { message } from '@c-x/ui';
import { message } from '@ctzhian/ui';
import ThirdPartyLoginSettingModal from './thirdPartyLoginSettingModal';
import GroupList from './groupList';
@@ -124,7 +117,10 @@ const User = () => {
color='info'
sx={{ gap: 2 }}
onClick={() => setThirdPartyLoginSettingModalOpen(true)}
disabled={license?.edition !== DomainLicenseEdition.LicenseEditionEnterprise}
disabled={
license?.edition !==
DomainLicenseEdition.LicenseEditionEnterprise
}
>
</Button>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Modal, message, Loading } from '@c-x/ui';
import { Modal, message, Loading } from '@ctzhian/ui';
import { Box, Typography, IconButton, Paper } from '@mui/material';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { CopyToClipboard } from 'react-copy-to-clipboard';

View File

@@ -2,9 +2,9 @@ import Card from '@/components/card';
import { Stack, Box } from '@mui/material';
import { useRequest } from 'ahooks';
import { getLoginHistory } from '@/api/User';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import dayjs from 'dayjs';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { DomainListLoginHistoryResp } from '@/api/types';
import User from '@/components/user';
@@ -20,7 +20,13 @@ const LoginHistory = () => {
dataIndex: 'user',
render: (user, record) => {
return (
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
<User
username={record.user!.username!}
id={record.user!.id!}
@@ -38,10 +44,23 @@ const LoginHistory = () => {
render: (ip, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.device}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.hostname}
</Box>
</Stack>
@@ -54,10 +73,23 @@ const LoginHistory = () => {
render: (ip, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.client_id}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.client_version}
</Box>
</Stack>
@@ -70,11 +102,26 @@ const LoginHistory = () => {
render: (ip, record) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.ip_info?.ip}
</Box>
<Box sx={{ color: 'text.secondary', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{record?.ip_info?.country === '中国' ? ('' + record?.ip_info?.province + '-' + record?.ip_info?.city) : (record?.ip_info?.country || '未知')}
<Box
sx={{
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{record?.ip_info?.country === '中国'
? '' + record?.ip_info?.province + '-' + record?.ip_info?.city
: record?.ip_info?.country || '未知'}
</Box>
</Stack>
);
@@ -86,14 +133,26 @@ const LoginHistory = () => {
render: (text) => {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(text).format('YYYY-MM-DD')}
</Box>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(text).format('HH:mm:ss')}
</Box>
</Stack>
)
);
},
},
];

View File

@@ -17,9 +17,9 @@ import {
MenuItem,
Typography,
} from '@mui/material';
import { Table, MenuSelect, Modal, message } from '@c-x/ui';
import { Table, MenuSelect, Modal, message } from '@ctzhian/ui';
import InviteUserModal from './inviteUserModal';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { ConstsUserStatus, DomainUser } from '@/api/types';
import dayjs from 'dayjs';
import { CopyToClipboard } from 'react-copy-to-clipboard';
@@ -150,22 +150,28 @@ const MemberManage = () => {
const [open, setOpen] = useState(false);
const [resetPasswordOpen, setResetPasswordOpen] = useState(false);
const [currentUser, setCurrentUser] = useState<DomainUser | null>(null);
const { data: originData, loading, refresh } = useRequest(() => getListUser({page: 1, size: 999}));
const {
data: originData,
loading,
refresh,
} = useRequest(() => getListUser({ page: 1, size: 999 }));
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [searchUser, setSearchUser] = useState('');
const [data, setData] = useState<DomainUser[]>([]);
useEffect(()=>{
if(searchUser){
setData(originData?.users?.filter((item) => {
const searchTerm = searchUser.toLowerCase();
const username = (item.username || '').toLowerCase();
const email = (item.email || '').toLowerCase();
return username.includes(searchTerm) || email.includes(searchTerm);
}) || []);
}else {
useEffect(() => {
if (searchUser) {
setData(
originData?.users?.filter((item) => {
const searchTerm = searchUser.toLowerCase();
const username = (item.username || '').toLowerCase();
const email = (item.email || '').toLowerCase();
return username.includes(searchTerm) || email.includes(searchTerm);
}) || []
);
} else {
setData(originData?.users || []);
}
},[searchUser, originData])
}, [searchUser, originData]);
const handleClick = (
event: React.MouseEvent<HTMLButtonElement>,
record: DomainUser
@@ -344,7 +350,11 @@ const MemberManage = () => {
>
<Box sx={{ fontWeight: 700 }}></Box>
<Stack direction='row' gap={1}>
<TextField label='搜索' size='small' onChange={(e)=>setSearchUser(e.target.value)} />
<TextField
label='搜索'
size='small'
onChange={(e) => setSearchUser(e.target.value)}
/>
<Button
variant='contained'
color='primary'

View File

@@ -6,7 +6,7 @@ import {
Autocomplete,
Chip,
} from '@mui/material';
import { Modal, message } from '@c-x/ui';
import { Modal, message } from '@ctzhian/ui';
import { useState, useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { FormItem } from '@/components/form';

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Modal, message } from '@c-x/ui';
import { Modal, message } from '@ctzhian/ui';
import { Box, TextField } from '@mui/material';
import { putUpdateUserGroup } from '@/api/UserGroup';
import { DomainUserGroup } from '@/api/types';
@@ -11,7 +11,12 @@ interface UpdateGroupModalProps {
group: DomainUserGroup | null;
}
const UpdateGroupModal = ({ open, onClose, onUpdated, group }: UpdateGroupModalProps) => {
const UpdateGroupModal = ({
open,
onClose,
onUpdated,
group,
}: UpdateGroupModalProps) => {
const [groupName, setGroupName] = useState('');
// 当group变化时更新表单中的组名
@@ -67,4 +72,4 @@ const UpdateGroupModal = ({ open, onClose, onUpdated, group }: UpdateGroupModalP
);
};
export default UpdateGroupModal;
export default UpdateGroupModal;

View File

@@ -1,16 +1,20 @@
import React, { useState } from 'react';
import Card from '@/components/card';
import { deleteDeleteModel, putUpdateModel } from '@/api/Model';
import {
deleteDeleteModel,
putUpdateModel,
} from '@/api/Model';
import { DomainModel, GithubComChaitinMonkeyCodeBackendConstsModelStatus, GithubComChaitinMonkeyCodeBackendConstsModelType, } from '@/api/types';
import { Stack, Box, Button, Grid2 as Grid, ButtonBase } from '@mui/material';
import { Icon, Modal, message } from '@c-x/ui';
DomainModel,
GithubComChaitinMonkeyCodeBackendConstsModelStatus,
GithubComChaitinMonkeyCodeBackendConstsModelType,
} from '@/api/types';
import { Stack, Box, Button, Grid, ButtonBase } from '@mui/material';
import { Icon, Modal, message } from '@ctzhian/ui';
import { addCommasToNumber } from '@/utils';
import NoData from '@/assets/images/nodata.png';
import { ModelModal, DEFAULT_MODEL_PROVIDERS} from '@yokowu/modelkit-ui';
import { localModelToModelKitModel, modelService } from '@/pages/model/components/services/modelService';
import { ModelModal, DEFAULT_MODEL_PROVIDERS } from '@yokowu/modelkit-ui';
import {
localModelToModelKitModel,
modelService,
} from '@/pages/model/components/services/modelService';
const ModelItem = ({
data,
@@ -40,7 +44,8 @@ const ModelItem = ({
onOk: () => {
putUpdateModel({
id: data.id,
status: GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive,
status:
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive,
provider: data.provider!,
}).then(() => {
message.success('停用成功');
@@ -90,7 +95,8 @@ const ModelItem = ({
onOk: () => {
putUpdateModel({
id: data.id,
status: GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusDefault,
status:
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusDefault,
provider: data.provider!,
}).then(() => {
message.success('设为默认模型成功');
@@ -115,7 +121,8 @@ const ModelItem = ({
onOk: () => {
putUpdateModel({
id: data.id,
status: GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive,
status:
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive,
provider: data.provider!,
}).then(() => {
message.success('激活成功');
@@ -128,7 +135,11 @@ const ModelItem = ({
// 添加状态标签渲染函数
const renderStatusLabel = () => {
// 根据 is_active 和 status 字段判断状态
if (data.is_active && data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive) {
if (
data.is_active &&
data.status ===
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive
) {
return (
<Box
sx={{
@@ -151,13 +162,16 @@ const ModelItem = ({
borderLeft: '6px solid transparent',
borderTop: '6px solid',
borderTopColor: 'success.dark',
}
},
}}
>
</Box>
);
} else if (data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive) {
} else if (
data.status ===
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive
) {
return (
<Box
sx={{
@@ -180,7 +194,7 @@ const ModelItem = ({
borderLeft: '6px solid transparent',
borderTop: '6px solid',
borderTopColor: 'grey.600',
}
},
}}
>
@@ -210,7 +224,7 @@ const ModelItem = ({
borderLeft: '6px solid transparent',
borderTop: '6px solid',
borderTopColor: 'primary.dark',
}
},
}}
>
@@ -257,9 +271,14 @@ const ModelItem = ({
<Stack direction='row' alignItems='center' gap={1}>
<Icon
type={
DEFAULT_MODEL_PROVIDERS[data.provider as keyof typeof DEFAULT_MODEL_PROVIDERS]?.icon
DEFAULT_MODEL_PROVIDERS[
data.provider as keyof typeof DEFAULT_MODEL_PROVIDERS
]?.icon
}
sx={{ fontSize: 24, color: data.is_active ? 'inherit' : 'grey.400' }}
sx={{
fontSize: 24,
color: data.is_active ? 'inherit' : 'grey.400',
}}
/>
<Stack
direction='row'
@@ -326,16 +345,20 @@ const ModelItem = ({
gap={2}
sx={{ mt: 2 }}
>
<Stack direction='row' alignItems='center'> </Stack>
<Stack direction='row' alignItems='center'>
{' '}
</Stack>
<Stack direction='row' sx={{ button: { minWidth: 0 } }} gap={2}>
{(data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive ||
data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive) && (
{(data.status ===
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusActive ||
data.status ===
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive) && (
<ButtonBase
disableRipple
sx={{
color: 'text.primary',
'&:hover': {
fontWeight: 700
fontWeight: 700,
},
}}
onClick={onSetDefaultModel}
@@ -344,22 +367,23 @@ const ModelItem = ({
</ButtonBase>
)}
{data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive &&
data.model_type !== GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeCoder &&
(
<ButtonBase
disableRipple
sx={{
color: 'success.main',
'&:hover': {
fontWeight: 700
},
}}
onClick={onActiveModel}
>
</ButtonBase>
)}
{data.status ===
GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive &&
data.model_type !==
GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeCoder && (
<ButtonBase
disableRipple
sx={{
color: 'success.main',
'&:hover': {
fontWeight: 700,
},
}}
onClick={onActiveModel}
>
</ButtonBase>
)}
{!data.is_internal && (
<ButtonBase
@@ -367,7 +391,7 @@ const ModelItem = ({
sx={{
color: 'info.main',
'&:hover': {
fontWeight: 700
fontWeight: 700,
},
}}
onClick={() => onEdit(data)}
@@ -382,7 +406,7 @@ const ModelItem = ({
sx={{
color: 'error.main',
'&:hover': {
fontWeight: 700
fontWeight: 700,
},
}}
onClick={onInactiveModel}
@@ -397,7 +421,7 @@ const ModelItem = ({
sx={{
color: 'error.main',
'&:hover': {
fontWeight: 700
fontWeight: 700,
},
}}
onClick={onRemoveModel}
@@ -471,7 +495,7 @@ const ModelCard: React.FC<IModelCardProps> = ({
data={editData ? localModelToModelKitModel(editData) : null}
model_type={modelType}
modelService={modelService}
language="zh-CN"
language='zh-CN'
messageComponent={message}
/>
</Card>

View File

@@ -4,7 +4,7 @@ import ModelCard from './components/modelCard';
import { Stack } from '@mui/material';
import { GithubComChaitinMonkeyCodeBackendConstsModelType } from '@/api/types';
import { useCommonContext } from '@/hooks/context';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
const Model = () => {
const { coderModel, llmModel, refreshModel, isConfigModel, modelLoading } =
@@ -43,13 +43,17 @@ const Model = () => {
title='对话模型'
data={llmModel}
refreshModel={refreshModel}
modelType={GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeLLM}
modelType={
GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeLLM
}
/>
<ModelCard
title='代码补全模型'
data={coderModel}
refreshModel={refreshModel}
modelType={GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeCoder}
modelType={
GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeCoder
}
/>
</Stack>
);

View File

@@ -2,7 +2,7 @@ import Avatar from '@/components/avatar';
import Card from '@/components/card';
import { getUserChatInfo } from '@/api/UserRecord';
import MarkDown from '@/components/markDown';
import { Ellipsis, Modal } from '@c-x/ui';
import { Ellipsis, Modal } from '@ctzhian/ui';
import { styled } from '@mui/material';
import logo from '@/assets/images/logo.png';

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import { getUserListChatRecord } from '@/api/UserRecord';
import dayjs from 'dayjs';
@@ -15,7 +15,7 @@ import {
import StyledLabel from '@/components/label';
import ChatDetailModal from './chatDetailModal';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { DomainChatRecord, DomainUser } from '@/api/types';
import { addCommasToNumber } from '@/utils';
import User from '@/components/user';
@@ -153,14 +153,26 @@ const Chat = () => {
render(value: number) {
return (
<Stack direction='column'>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(value).format('YYYY-MM-DD')}
</Box>
<Box sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<Box
sx={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{dayjs.unix(value).format('HH:mm:ss')}
</Box>
</Stack>
)
);
},
},
];

View File

@@ -1,6 +1,6 @@
import Card from '@/components/card';
import { getUserCompletionInfo } from '@/api/UserRecord';
import { Modal } from '@c-x/ui';
import { Modal } from '@ctzhian/ui';
import MonacoEditor from '@monaco-editor/react';
import { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { DomainCompletionRecord, DomainUser } from '@/api/types';
import { getUserListCompletionRecord } from '@/api/UserRecord';
import { Table } from '@c-x/ui';
import { Table } from '@ctzhian/ui';
import Card from '@/components/card';
import {
Box,
@@ -15,7 +15,7 @@ import {
} from '@mui/material';
import dayjs from 'dayjs';
import { useDebounceFn } from 'ahooks';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { ColumnsType } from '@ctzhian/ui/dist/Table';
import { addCommasToNumber } from '@/utils';
import CompletionDetailModal from './completionDetailModal';
import StyledLabel from '@/components/label';

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Grid2 as Grid } from '@mui/material';
import { Grid } from '@mui/material';
import MemberInfo from '@/pages/dashboard/components/memberInfo';
import PieCharts from '@/pages/dashboard/components/pieCharts';
import LineCharts from '@/pages/dashboard/components/lineCharts';

View File

@@ -8,13 +8,13 @@ import {
Container,
Paper,
CircularProgress,
Grid2 as Grid,
Grid,
InputAdornment,
IconButton,
Divider,
Stack,
} from '@mui/material';
import { Icon, message } from '@c-x/ui';
import { Icon, message } from '@ctzhian/ui';
import { getRedirectUrl } from '@/utils';

View File

@@ -14,7 +14,7 @@ import {
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
import { message, Modal } from '@c-x/ui';
import { message, Modal } from '@ctzhian/ui';
import { useForm, Controller } from 'react-hook-form';
import { useRequest } from 'ahooks';
import Card from '@/components/card';

View File

@@ -2,7 +2,7 @@
import { createTheme, Paper } from '@mui/material';
import type { Shadows } from '@mui/material';
import { zhCN } from '@mui/material/locale';
import { zhCN as CuiZhCN } from '@c-x/ui/dist/local';
import { zhCN as CuiZhCN } from '@ctzhian/ui/dist/local';
import onData from '@/assets/images/nodata.png';
import { common } from '@mui/material/colors';
@@ -34,7 +34,7 @@ const lightTheme = createTheme(
risk: {
severe: '#FF6262',
critical: '#FFA762',
suggest: '#FFCF62'
suggest: '#FFCF62',
},
disabled: {
main: '#666',
@@ -179,7 +179,7 @@ const lightTheme = createTheme(
styleOverrides: {
root: {
color: 'unset',
fontSize: '0.8rem',
fontSize: '0.8rem',
fontFamily: 'var(--font-gilory), var(--font-HarmonyOS)',
},
asterisk: {
@@ -197,14 +197,14 @@ const lightTheme = createTheme(
MuiRadio: {
styleOverrides: {
root: {
fontSize: '0.8rem',
fontSize: '0.8rem',
},
},
},
MuiFormControlLabel: {
styleOverrides: {
label: {
fontSize: '0.8rem',
fontSize: '0.8rem',
},
},
},