Browse Source

Merge pull request '合并dev-zw' (#188) from dev-zw into dev

dev-credits
cp3hnu 10 months ago
parent
commit
91103ed4f6
44 changed files with 705 additions and 213 deletions
  1. +2
    -0
      .gitignore
  2. +1
    -1
      react-ui/.storybook/main.ts
  3. +1
    -133
      react-ui/README.md
  4. +12
    -1
      react-ui/config/routes.ts
  5. +5
    -3
      react-ui/package.json
  6. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.ttf
  7. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff
  8. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff2
  9. BIN
      react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.otf
  10. BIN
      react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.ttf
  11. BIN
      react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.woff
  12. BIN
      react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.woff2
  13. +22
    -4
      react-ui/public/fonts/font.css
  14. BIN
      react-ui/src/assets/img/user-points-bg.png
  15. BIN
      react-ui/src/assets/img/workspace-experiment.png
  16. BIN
      react-ui/src/assets/img/workspace-pipeline.png
  17. +1
    -1
      react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
  18. +1
    -1
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  19. +2
    -2
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  20. +1
    -1
      react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx
  21. +1
    -1
      react-ui/src/pages/ModelDeployment/types.ts
  22. +31
    -0
      react-ui/src/pages/Points/components/Statistics/index.less
  23. +38
    -0
      react-ui/src/pages/Points/components/Statistics/index.tsx
  24. +19
    -0
      react-ui/src/pages/Points/index.less
  25. +256
    -0
      react-ui/src/pages/Points/index.tsx
  26. +15
    -6
      react-ui/src/pages/System/User/edit.tsx
  27. +5
    -0
      react-ui/src/pages/System/User/index.tsx
  28. +1
    -0
      react-ui/src/pages/Workspace/components/ExperimentChart/index.less
  29. +5
    -5
      react-ui/src/pages/Workspace/components/ExperimentTable/index.less
  30. +4
    -16
      react-ui/src/pages/Workspace/components/TotalStatistics/index.less
  31. +3
    -3
      react-ui/src/pages/Workspace/components/TotalStatistics/index.tsx
  32. +36
    -0
      react-ui/src/pages/Workspace/components/UserPoints/index.less
  33. +40
    -0
      react-ui/src/pages/Workspace/components/UserPoints/index.tsx
  34. +11
    -2
      react-ui/src/pages/Workspace/index.less
  35. +34
    -20
      react-ui/src/pages/Workspace/index.tsx
  36. +22
    -0
      react-ui/src/services/points/index.ts
  37. +5
    -0
      react-ui/src/stories/docs/Utils.mdx
  38. +8
    -0
      react-ui/src/styles/theme.less
  39. +1
    -0
      react-ui/src/types/system/user.d.ts
  40. +86
    -12
      react-ui/src/utils/index.ts
  41. +23
    -0
      react-ui/src/utils/statusTableCell.tsx
  42. +1
    -1
      react-ui/src/utils/table.tsx
  43. +11
    -0
      react-ui/typedoc.json
  44. +1
    -0
      react-ui/types/tsconfig.tsbuildinfo

+ 2
- 0
.gitignore View File

@@ -60,3 +60,5 @@ mvnw
**/node_modules

*storybook.log

/react-ui/docs

+ 1
- 1
react-ui/.storybook/main.ts View File

@@ -16,7 +16,7 @@ const config: StorybookConfig = {
name: '@storybook/react-webpack5',
options: {},
},
staticDirs: ['../public'],
staticDirs: ['../public', { from: '../docs', to: '/docs' }],
docs: {
defaultName: 'Documentation',
},


+ 1
- 133
react-ui/README.md View File

@@ -1,133 +1 @@
Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)

<h1 align="center">Ant Design Pro</h1>

<div align="center">

An out-of-box UI solution for enterprise applications as a React boilerplate.

[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master) ![Github Action](https://github.com/ant-design/ant-design-pro/workflows/Node%20CI/badge.svg) ![Deploy](https://github.com/ant-design/ant-design-pro/workflows/Deploy%20CI/badge.svg)

[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/) ![](https://badgen.net/badge/icon/Ant%20Design?icon=https://gw.alipayobjects.com/zos/antfincdn/Pp4WPgVDB3/KDpgvguMpGfqaHPjicRK.svg&label)

![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)

</div>

- Preview: http://preview.pro.ant.design
- Home Page: http://pro.ant.design
- Documentation: http://pro.ant.design/docs/getting-started
- ChangeLog: http://pro.ant.design/docs/changelog
- FAQ: http://pro.ant.design/docs/faq
- Mirror Site in China: http://ant-design-pro.gitee.io

## 5.0 is out! 🎉🎉🎉

[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)

## Translation Recruitment :loudspeaker:

We need your help: https://github.com/ant-design/ant-design-pro/issues/120

## Features

- :bulb: **TypeScript**: A language for application-scale JavaScript
- :scroll: **Blocks**: Build page with block template
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
- :iphone: **Responsive**: Designed for variable screen sizes
- :art: **Theming**: Customizable theme with simple config
- :globe_with_meridians: **International**: Built-in i18n solution
- :gear: **Best Practices**: Solid workflow to make your code healthy
- :1234: **Mock development**: Easy to use mock development solution
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests

## Templates

```
- Dashboard
- Analytic
- Monitor
- Workspace
- Form
- Basic Form
- Step Form
- Advanced From
- List
- Standard Table
- Standard List
- Card List
- Search List (Project/Applications/Article)
- Profile
- Simple Profile
- Advanced Profile
- Account
- Account Center
- Account Settings
- Result
- Success
- Failed
- Exception
- 403
- 404
- 500
- User
- Login
- Register
- Register Result
```

## Usage

### Use bash

We provide pro-cli to quickly initialize scaffolding.

```bash
# use npm
npm i @ant-design/pro-cli -g
pro create myapp
```

select umi version

```shell
🐂 Use umi@4 or umi@3 ? (Use arrow keys)
❯ umi@4
umi@3
```

> If the umi@4 version is selected, full blocks are not yet supported.

If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.

```shell
? 🚀 Full or a simple scaffold? (Use arrow keys)
❯ simple
complete
```

Install dependencies:

```shell
$ cd myapp && tyarn
// or
$ cd myapp && npm install
```

## Browsers support

Modern browsers.

| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --- | --- | --- | --- | --- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |

## Contributing

Any type of contribution is welcome, here are some examples of how you may contribute to this project:

- Use Ant Design Pro in your daily work.
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
# Documentation

+ 12
- 1
react-ui/config/routes.ts View File

@@ -345,7 +345,6 @@ export default [
},
],
},

{
name: '应用开发',
path: '/appsDeployment',
@@ -498,6 +497,18 @@ export default [
},
],
},
{
name: '算力积分',
path: '/points',
routes: [
{
name: '算力积分',
path: '',
key: 'points',
component: './Points/index',
},
],
},
{
path: '*',
layout: false,


+ 5
- 3
react-ui/package.json View File

@@ -1,8 +1,8 @@
{
"name": "ant-design-pro",
"version": "6.0.0",
"name": "cl-model",
"version": "1.0.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"description": "",
"scripts": {
"analyze": "cross-env ANALYZE=1 max build",
"build": "max build",
@@ -16,6 +16,7 @@
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
"docs": "typedoc --entryPointStrategy expand --entryPoints 'src/utils' --skipErrorChecking --out docs",
"gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup",
@@ -132,6 +133,7 @@
"swagger-ui-dist": "^4.18.2",
"ts-loader": "~9.5.2",
"ts-node": "^10.9.1",
"typedoc": "~0.28.1",
"typescript": "^5.0.4",
"umi-presets-pro": "^2.0.0"
},


BIN
react-ui/public/fonts/DingTalk-JinBuTi.ttf View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff2 View File


BIN
react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.otf View File


BIN
react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.ttf View File


BIN
react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.woff View File


BIN
react-ui/public/fonts/TaoBaoMaiCaiTi-Regular.woff2 View File


+ 22
- 4
react-ui/public/fonts/font.css View File

@@ -1,5 +1,23 @@
@font-face {
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}

@font-face {
font-family: 'TaoBaoMaiCaiTi';
src: url('./TaoBaoMaiCaiTi-Regular.woff2') format('woff2'), /* 最优先使用 woff2 */
url('./TaoBaoMaiCaiTi-Regular.woff') format('woff'), /* 兼容性较好的 woff */
url('./TaoBaoMaiCaiTi-Regular.ttf') format('truetype'), /* ttf 作为备选 */
url('./TaoBaoMaiCaiTi-Regular.otf') format('opentype'); /* otf 作为最后选项 */
font-display: swap; /* 优化页面加载时的字体显示 */
}


@font-face {
font-family: 'DingTalk-JinBuTi';
src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */
url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */
url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */
font-display: swap; /* 优化页面加载时的字体显示 */
}

BIN
react-ui/src/assets/img/user-points-bg.png View File

Before After
Width: 981  |  Height: 672  |  Size: 220 kB

BIN
react-ui/src/assets/img/workspace-experiment.png View File

Before After
Width: 147  |  Height: 148  |  Size: 17 kB Width: 216  |  Height: 216  |  Size: 24 kB

BIN
react-ui/src/assets/img/workspace-pipeline.png View File

Before After
Width: 169  |  Height: 159  |  Size: 21 kB Width: 219  |  Height: 216  |  Size: 31 kB

+ 1
- 1
react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx View File

@@ -137,7 +137,7 @@ function EditorCreate() {
<Col span={10}>
<Form.Item
label="资源规格"
name="standard"
name="computing_resource_id"
rules={[
{
required: true,


+ 1
- 1
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -347,7 +347,7 @@ function CreateServiceVersion() {
<Col span={10}>
<Form.Item
label="资源规格"
name="resource"
name="computing_resource_id"
rules={[
{
required: true,


+ 2
- 2
react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx View File

@@ -309,8 +309,8 @@ function ServiceInfo() {
},
{
title: '资源规格',
dataIndex: 'resource',
key: 'resource',
dataIndex: 'computing_resource_id',
key: 'computing_resource_id',
width: '20%',
render: tableCellRender(true, TableCellValueType.Custom, {
format: getResourceDescription,


+ 1
- 1
react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx View File

@@ -68,7 +68,7 @@ function VersionBasicInfo({ info }: BasicInfoProps) {
},
{
label: '资源规格',
value: info?.resource,
value: info?.computing_resource_id,
format: getResourceDescription,
},
{


+ 1
- 1
react-ui/src/pages/ModelDeployment/types.ts View File

@@ -24,7 +24,7 @@ export type ServiceVersionData = {
run_state: ServiceRunStatus; // 运行状态
image: string; // 镜像
replicas: number; // 副本数
resource: string; // 资源
computing_resource_id: number; // 资源
mount_path: string; // 挂载路径
model: {
// 模型


+ 31
- 0
react-ui/src/pages/Points/components/Statistics/index.less View File

@@ -0,0 +1,31 @@
.statistics {
display: flex;
align-items: center;
width: 100%;
padding: 20px 0;
background-color: @background-color;
border-radius: 8px;

&__item {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;

&--border {
border-right: 1px solid @border-color;
}

&__value {
margin-bottom: 8px;
color: @text-color;
font-weight: bold;
font-size: 30px;
}

&__title {
color: #999999;
font-size: @font-size-input;
}
}
}

+ 38
- 0
react-ui/src/pages/Points/components/Statistics/index.tsx View File

@@ -0,0 +1,38 @@
import classNames from 'classnames';
import styles from './index.less';

type StatisticsProps = {
remaining?: number;
consuming?: number;
};

function Statistics({ remaining, consuming }: StatisticsProps) {
const items = [
{
title: '当前可用算力积分(分)',
value: remaining ?? '-',
},
{
title: '总消耗算力积分(分)',
value: consuming ?? '-',
},
];

return (
<div className={styles['statistics']}>
{items.map((item, index) => (
<div
key={item.title}
className={classNames(styles['statistics__item'], {
[styles['statistics__item--border']]: index === 0,
})}
>
<span className={styles['statistics__item__value']}>{item.value}</span>
<span className={styles['statistics__item__title']}>{item.title}</span>
</div>
))}
</div>
);
}

export default Statistics;

+ 19
- 0
react-ui/src/pages/Points/index.less View File

@@ -0,0 +1,19 @@
.points-detail {
height: 100%;
&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;

&__top {
width: 100%;
}

&__table {
height: calc(100% - 117px - 28px);
margin-top: 28px;
}
}
}

+ 256
- 0
react-ui/src/pages/Points/index.tsx View File

@@ -0,0 +1,256 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 模型部署服务列表
*/
import PageTitle from '@/components/PageTitle';
import { getPointsConsumptionReq, getPointsStatisticsReq } from '@/services/points';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import statusTableCell from '@/utils/statusTableCell';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Link } from '@umijs/max';
import { Table, type TablePaginationConfig, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import Statistics from './components/Statistics';
import styles from './index.less';

enum TaskType {
DevEnvironment = 'dev_environment',
Workflow = 'workflow',
Ray = 'ray',
Service = 'service',
}

const taskTypeOptions = [
{
value: 'dev_environment',
label: '开发环境',
},
{
value: 'workflow',
label: '实验',
},
{
value: 'ray',
label: '超参数自动寻优',
},
{
value: 'service',
label: '服务',
},
];

export type PointsData = {
computing_resource_id: number;
credit_per_hour: number; // 每小时消耗的积分
deduce_credit: number;
deduce_last_time: string;
description: string;
id: number;
node_id: string;
start_time: string;
state: number;
workflow_id?: number; // 流水线id
task_id: number; // 实验id
task_ins_id: number; // 实例id
task_type: string;
user_id: number;
};

export type PointsStatistics = {
deduceCredit: number;
userCredit: number;
};

// 格式化任务
const formatTask = (value: string, record: PointsData) => {
let url;
switch (record.task_type) {
case TaskType.DevEnvironment:
url = `/developmentEnvironment`;
break;
case TaskType.Workflow:
if (record.workflow_id && record.task_ins_id) {
url = `/pipeline/experiment/instance/${record.workflow_id}/${record.task_ins_id}`;
}
break;
case TaskType.Ray:
if (record.task_id && record.task_ins_id) {
url = `/pipeline/hyperparameter/instance/${record.task_id}/${record.task_ins_id}`;
}
break;
case TaskType.Service:
if (record.task_id && record.task_ins_id) {
url = `/dataset/modelDeployment/serviceInfo/${record.task_id}/versionInfo/${record.task_ins_id}`;
}
break;
default:
break;
}

if (url) {
return <Link to={url}>{value}</Link>;
} else {
return <span>{value}</span>;
}
};

function PointsDetail() {
const [tableData, setTableData] = useState<PointsData[]>([]);
const [total, setTotal] = useState(0);
const [statistics, setStatistics] = useState<PointsStatistics>();
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
});

useEffect(() => {
// 获取积分统计
const getPointsStatistics = async () => {
const [res] = await to(getPointsStatisticsReq());
if (res && res.data) {
setStatistics(res.data);
}
};

getPointsStatistics();
}, []);

useEffect(() => {
// 获取积分消费明细
const getPointsConsumption = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
};
const [res] = await to(getPointsConsumptionReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

getPointsConsumption();
}, [pagination]);

// 分页切换
const handleTableChange: TableProps<PointsData>['onChange'] = (
pagination,
_filters,
_sorter,
{ action },
) => {
if (action === 'paginate') {
setPagination(pagination);
}
};

const columns: TableProps<PointsData>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render: tableCellRender(false, TableCellValueType.Index, {
page: pagination.current! - 1,
pageSize: pagination.pageSize!,
}),
},
{
title: '任务',
dataIndex: 'task_name',
key: 'task_name',
width: '15%',
render: formatTask,
},
{
title: '任务类型',
dataIndex: 'task_type',
key: 'task_type',
width: '10%',
render: statusTableCell(taskTypeOptions),
},
{
title: '积分/小时',
dataIndex: 'credit_per_hour',
key: 'credit_per_hour',
width: '10%',
render: tableCellRender(),
},
{
title: '消耗的积分(分)',
dataIndex: 'deduce_credit',
key: 'deduce_credit',
width: '12%',
render: tableCellRender(),
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
render: tableCellRender(true),
},
{
title: '开始时间',
dataIndex: 'start_time',
key: 'start_time',
width: '15%',
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
width: '8%',
render: statusTableCell([
{
value: 0,
label: '已结束',
color: themes['textColor'],
},
{
value: 1,
label: '进行中',
color: themes['primaryColor'],
},
]),
},
];

return (
<div className={styles['points-detail']}>
<PageTitle title="算力积分明细"></PageTitle>
<div className={styles['points-detail__content']}>
<div className={styles['points-detail__content__top']}>
<Statistics
remaining={statistics?.userCredit}
consuming={statistics?.deduceCredit}
></Statistics>
</div>
<div
className={classNames('vertical-scroll-table', styles['points-detail__content__table'])}
>
<Table
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `共${total}条`,
}}
onChange={handleTableChange}
rowKey="id"
/>
</div>
</div>
</div>
);
}

export default PointsDetail;

+ 15
- 6
react-ui/src/pages/System/User/edit.tsx View File

@@ -2,6 +2,7 @@ import { DictValueEnumObj } from '@/components/DictTag';
import KFModal from '@/components/KFModal';
import {
ProForm,
ProFormDigit,
ProFormRadio,
ProFormSelect,
ProFormText,
@@ -64,6 +65,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
remark: props.values.remark,
gitLinkUsername: props.values.gitLinkUsername,
gitLinkPassword: props.values.gitLinkPassword,
credit: props.values.credit,
});
}, [form, props, statusOptions]);

@@ -219,12 +221,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
autoComplete: 'new-password',
}}
allowClear
rules={[
{
required: true,
message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />,
},
]}
rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]}
/>
<ProFormSelect
valueEnum={sexOptions}
@@ -304,6 +301,18 @@ const UserForm: React.FC<UserFormProps> = (props) => {
}}
rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]}
/>
<ProFormDigit
name="credit"
label="算力积分"
placeholder="请输入算力积分"
colProps={{ xs: 24, md: 12, xl: 12 }}
rules={[
{
required: true,
message: '请输入算力积分',
},
]}
/>
<ProFormTextArea
name="remark"
label={intl.formatMessage({


+ 5
- 0
react-ui/src/pages/System/User/index.tsx View File

@@ -259,6 +259,11 @@ const UserTableList: React.FC = () => {
dataIndex: 'phonenumber',
valueType: 'text',
},
{
title: <FormattedMessage id="system.user.credit" defaultMessage="算力积分" />,
dataIndex: 'credit',
valueType: 'text',
},
{
title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
dataIndex: 'status',


+ 1
- 0
react-ui/src/pages/Workspace/components/ExperimentChart/index.less View File

@@ -1,4 +1,5 @@
.experiment-chart {
flex: none;
width: 295px;
min-width: 295px;
height: 140px;


+ 5
- 5
react-ui/src/pages/Workspace/components/ExperimentTable/index.less View File

@@ -1,8 +1,8 @@
.experiment-table {
flex: 1;
min-width: 500px;
min-width: 378px;
height: 140px;
padding: 12px 24px;
padding: 12px;
background: @workspace-background;
border-radius: 4px;

@@ -32,15 +32,15 @@
}

&__status {
width: 20%;
width: 15%;
}

&__duration {
width: 30%;
width: 25%;
}

&__date {
width: calc(50% - 60px);
width: calc(60% - 60px);
}

&__operation {


+ 4
- 16
react-ui/src/pages/Workspace/components/TotalStatistics/index.less View File

@@ -1,16 +1,12 @@
.total-statistics {
display: flex;
align-items: center;
justify-content: center;
width: 400px;
height: 140px;
background: @workspace-background;
border-radius: 4px;
padding: 0 16px;

&__icon {
width: 85px;
height: 80px;
margin-right: 40px;
width: 63px;
margin-right: 15px;
}

&__title {
@@ -20,18 +16,10 @@
font-size: @font-size-content;
}

&__title-shadow {
position: absolute;
bottom: 6px;
left: 0;
width: 79px;
height: 6px;
background: linear-gradient(87.07deg, rgba(22, 100, 255, 0.6) 0%, rgba(22, 100, 255, 0) 100%);
}

&__count {
color: @text-color;
font-weight: 700;
font-size: 25px;
font-family: DingTalk-JinBuTi;
}
}

+ 3
- 3
react-ui/src/pages/Workspace/components/TotalStatistics/index.tsx View File

@@ -1,3 +1,4 @@
import { Flex } from 'antd';
import styles from './index.less';

type TotalStatisticsProps = {
@@ -11,13 +12,12 @@ function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStati
return (
<div className={styles['total-statistics']} style={style}>
<img className={styles['total-statistics__icon']} src={icon} draggable={false} alt="" />
<div>
<Flex vertical align="center">
<div className={styles['total-statistics__title']}>
<span>{title}</span>
<div className={styles['total-statistics__title-shadow']}></div>
</div>
<div className={styles['total-statistics__count']}>{count ?? '--'}</div>
</div>
</Flex>
</div>
);
}


+ 36
- 0
react-ui/src/pages/Workspace/components/UserPoints/index.less View File

@@ -0,0 +1,36 @@
.user-points {
display: flex;
flex: none;
flex-direction: column;
align-items: center;

width: 326px;
height: 228px;
.backgroundFullImage(url(@/assets/img/user-points-bg.png));

&__label {
margin-top: 60px;
color: @text-color;
font-size: @font-size-title;
font-family: DingTalk-JinBuTi;
}

&__value {
margin-top: 8px;
margin-bottom: 12px;
color: @primary-color;
font-size: 36px;
font-family: DingTalk-JinBuTi;
line-height: 43px;
}

&__button {
padding: 8px 20px;
color: @primary-color;
font-size: @font-size-content;
background: rgba(255, 255, 255, 0.3);
border: 1px solid #ffffff;
border-radius: 8px;
cursor: pointer;
}
}

+ 40
- 0
react-ui/src/pages/Workspace/components/UserPoints/index.tsx View File

@@ -0,0 +1,40 @@
import { PointsStatistics } from '@/pages/Points/index';
import { getPointsStatisticsReq } from '@/services/points';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
import { useEffect, useState } from 'react';
import styles from './index.less';

function UserPoints() {
const [statistics, setStatistics] = useState<PointsStatistics>();
const navigate = useNavigate();

useEffect(() => {
// 获取积分统计
const getPointsStatistics = async () => {
const [res] = await to(getPointsStatisticsReq());
if (res && res.data) {
setStatistics(res.data);
}
};

getPointsStatistics();
}, []);

return (
<div className={styles['user-points']}>
<span className={styles['user-points__label']}>当前可用算力积分</span>
<span className={styles['user-points__value']}>{statistics?.userCredit ?? '--'}</span>
<div
className={styles['user-points__button']}
onClick={() => {
navigate('/points');
}}
>
查看详情
</div>
</div>
);
}

export default UserPoints;

+ 11
- 2
react-ui/src/pages/Workspace/index.less View File

@@ -6,6 +6,7 @@
background: linear-gradient(#ecf2fe, #f9fafb);

&__overview {
flex: 1;
gap: 15px;
margin-bottom: 16px;
padding: 20px 30px;
@@ -21,11 +22,19 @@

&__content {
display: flex;

flex-wrap: wrap;
gap: 15px;
align-items: center;

@media screen and (max-width: 1800px) {
flex-wrap: wrap;
&__statistics {
flex: none;
background: linear-gradient(
123.08deg,
rgba(138, 138, 138, 0.06) 1.32%,
rgba(22, 100, 255, 0.02) 58.35%
);
border-radius: 4px;
}
}
}


+ 34
- 20
react-ui/src/pages/Workspace/index.tsx View File

@@ -2,6 +2,7 @@ import { useDraggable } from '@/hooks/draggable';
import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react';
import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement';
@@ -10,6 +11,7 @@ import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart';
import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics';
import UserPoints from './components/UserPoints';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less';
@@ -28,6 +30,7 @@ function Workspace() {
setRobotFrameVisible((prev) => !prev),
);
const users: number[] = new Array(8).fill(0);

useEffect(() => {
getWorkspaceOverview();
}, []);
@@ -43,27 +46,38 @@ function Workspace() {
return (
<div className={styles.workspace}>
<WorkspaceIntro></WorkspaceIntro>
<div className={styles['workspace__overview']}>
<div className={styles['workspace__overview__title']}>运行概览</div>
<div className={styles['workspace__overview__content']}>
<TotalStatistics
icon={require('@/assets/img/workspace-pipeline.png')}
title="流水线总数"
count={overviewData?.workflowCount}
/>
<TotalStatistics
icon={require('@/assets/img/workspace-experiment.png')}
title="正在运行实例总数"
count={overviewData?.runningExperimentInsCount}
/>
<ExperitableTable
tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
<Flex gap={'0 15px'} wrap>
<div className={styles['workspace__overview']}>
<div className={styles['workspace__overview__title']}>运行概览</div>
<div className={styles['workspace__overview__content']}>
<Flex align="center" className={styles['workspace__overview__content__statistics']}>
<TotalStatistics
icon={require('@/assets/img/workspace-pipeline.png')}
title="流水线总数"
count={overviewData?.workflowCount}
/>
<Divider
type="vertical"
dashed
style={{ color: '1px dashed rgba(96, 107, 122, 0.19)', height: 68 }}
/>
<TotalStatistics
icon={require('@/assets/img/workspace-experiment.png')}
title="正在运行实例总数"
count={overviewData?.runningExperimentInsCount}
/>
</Flex>

<ExperitableTable
tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable>
{overviewData?.experimentInsStatus && (
<ExperimentChart chartData={overviewData?.experimentInsStatus}></ExperimentChart>
)}
</div>
</div>
</div>
<UserPoints />
</Flex>
<div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart>
<div className={styles['workspace__user']}>


+ 22
- 0
react-ui/src/services/points/index.ts View File

@@ -0,0 +1,22 @@
/*
* @Author: 赵伟
* @Date: 2025-03-20 13:48:53
* @Description: 积分
*/

import { request } from '@umijs/max';

// 分页查询积分消费明细
export function getPointsConsumptionReq(params: any) {
return request(`/api/mmp/computingResource/resouceOccupy`, {
method: 'GET',
params,
});
}

// 分页查询积分消费明细
export function getPointsStatisticsReq() {
return request(`/api/mmp/computingResource/credit`, {
method: 'GET',
});
}

+ 5
- 0
react-ui/src/stories/docs/Utils.mdx View File

@@ -0,0 +1,5 @@
import { Meta } from '@storybook/blocks';

<Meta title="Documentation/Utils" />

<a href="/docs/index.html" target='_blank'>工具栏文档</a>

+ 8
- 0
react-ui/src/styles/theme.less View File

@@ -69,6 +69,14 @@
-webkit-line-clamp: @line;
}

// 背景
.backgroundFullImage(@url) {
background-image: @url;
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
}

// 导出变量
:export {
primaryColor: @primary-color;


+ 1
- 0
react-ui/src/types/system/user.d.ts View File

@@ -21,6 +21,7 @@ declare namespace API.System {
remark: string;
gitLinkUsername?: string;
gitLinkPassword?: string;
credit?: number;
}

export interface UserListParams {


+ 86
- 12
react-ui/src/utils/index.ts View File

@@ -7,7 +7,10 @@
import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6';

// 生成 8 位随机数
/**
* 生成 8 位随机数
* @returns 8 位随机数
*/
export function s8() {
return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
}
@@ -20,7 +23,12 @@ export function getNameByCode(list: any[], code: any) {
return name;
}

// 解析 json 字符串
/**
* 完全解析 json 字符串,不会抛异常
*
* @param text - the string to be parsed
* @returns the parsed JSON object if the string is a valid JSON text, null otherwise
*/
export function parseJsonText(text?: string | null): any | null {
if (text === undefined || text === null || text === '') {
return null;
@@ -32,8 +40,17 @@ export function parseJsonText(text?: string | null): any | null {
}
}

// 判断是否为一般对象
function isPlainObject(value: any) {
/**
* Checks if a given value is a plain object.
*
* A plain object is an object that is created using the Object constructor.
* It does not have any special properties or methods that are not part of the
* standard Object prototype.
*
* @param {any} value - The value to be checked.
* @returns {boolean} true if the value is a plain object, false otherwise.
*/
function isPlainObject(value: any): boolean {
if (value === null || typeof value !== 'object') return false;
let proto = Object.getPrototypeOf(value);
while (proto !== null) {
@@ -43,7 +60,14 @@ function isPlainObject(value: any) {
return true;
}

// underscore to camelCase
/**
* Converts all property names in an object from underscore notation to camelCase.
*
* If the object is not a plain object, it is returned as is.
*
* @param obj - The object whose property names need to be converted.
* @returns The object with all property names converted to camelCase.
*/
export function underscoreToCamelCase(obj: Record<string, any>) {
if (!isPlainObject(obj)) {
return obj;
@@ -66,7 +90,14 @@ export function underscoreToCamelCase(obj: Record<string, any>) {
return newObj;
}

// camelCase to underscore
/**
* Converts all property names in an object from camelCase to underscore notation.
*
* If the object is not a plain object, it is returned as is.
*
* @param obj - The object whose property names need to be converted.
* @returns The object with all property names converted to underscore notation.
*/
export function camelCaseToUnderscore(obj: Record<string, any>) {
if (!isPlainObject(obj)) {
return obj;
@@ -87,7 +118,14 @@ export function camelCaseToUnderscore(obj: Record<string, any>) {
return newObj;
}

// null to undefined
/**
* Recursively converts all null values in an object to undefined.
*
* If the given value is not an object, it is returned as is.
*
* @param obj - The object whose null values need to be converted.
* @returns The object with all null values converted to undefined.
*/
export function nullToUndefined(obj: Record<string, any> | null) {
if (obj === null) {
return undefined;
@@ -113,7 +151,15 @@ export function nullToUndefined(obj: Record<string, any> | null) {
return newObj;
}

// undefined to null
/**
* Recursively converts all undefined values in an object to null.
*
* If the given value is not an object, it is returned as is.
*
* @param obj - The object whose undefined values need to be converted.
* @returns The object with all undefined values converted to null.
*/

export function undefinedToNull(obj?: Record<string, any>) {
if (obj === undefined) {
return null;
@@ -204,10 +250,20 @@ export const fittingString = (str: string, maxWidth: number, fontSize: number):
* @param {any} str - the string to be checked
* @return {boolean} true if the string is empty, undefined, or null, false otherwise
*/
export const isEmpty = (str: any): boolean => {
export const isEmpty = (str?: any | null): boolean => {
return str === '' || str === undefined || str === null;
};

/**
* Checks if a given value is undefined or null.
*
* @param {any} value - the value to be checked
* @return {boolean} true if the value is undefined or null, false otherwise
*/
export const hasNoValue = (value?: any | null): boolean => {
return value === undefined || value === null;
};

/**
* 获取 git 仓库的 url
*
@@ -226,12 +282,30 @@ export const getGitUrl = (url: string, branch: string): string => {
return branch ? `${gitUrl}/tree/${branch}` : gitUrl;
};

// 判断是否需要登录
export const needAuth = (pathname: string) => {
/**
* 判断是否需要登录
*
* @param {string} pathname - the pathname to be checked
* @return {boolean} true if the pathname needs to be authorized, false otherwise
*/
export const needAuth = (pathname: string): boolean => {
return pathname !== PageEnum.LOGIN && pathname !== PageEnum.Authorize;
};

// 表格排序
/**
* 表格排序
*
* @param {any} a - the first value to be compared
* @param {any} b - the second value to be compared
* @returns {number} -1 if a is less than b, 1 if a is greater than b, 0 if a is equal to b
*
* The sorter function compares two values and returns a number indicating their relative order.
* The function first checks if either value is null or undefined. If so, it returns -1 if b is null or undefined,
* and 1 if a is null or undefined.
* If both values are numbers, it returns the difference between the two numbers.
* If both values are strings, it returns the result of the localeCompare() method.
* Otherwise, it returns 0.
*/
export const tableSorter = (a: any, b: any) => {
if (b === null || b === undefined) {
return -1;


+ 23
- 0
react-ui/src/utils/statusTableCell.tsx View File

@@ -0,0 +1,23 @@
/*
* @Author: 赵伟
* @Date: 2025-03-20 14:33:01
* @Description: 通用的 Table 状态单元格
*/

export type StatusInfo = {
value: number | string;
label: string;
color?: string;
};

function statusTableCell(infos: StatusInfo[]) {
return function (status?: string | number | null) {
const info = infos.find((item) => item.value === status);
if (status === null || status === undefined || !info) {
return <span>--</span>;
}
return <span style={{ color: info.color }}>{info.label}</span>;
};
}

export default statusTableCell;

+ 1
- 1
react-ui/src/utils/table.tsx View File

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-06-26 10:05:52
* @Description: Table cell 自定义 render
* @Description: Table Cell 自定义 render
*/

import { isEmpty } from '@/utils';


+ 11
- 0
react-ui/typedoc.json View File

@@ -0,0 +1,11 @@
{
"entryPoints": ["./src/utils"],
"entryPointStrategy": "expand",
"out": "docs",
"excludePrivate": true,
"excludeProtected": false,
"excludeExternals": true,
"includeVersion": true,
"categorizeByGroup": true,
"name": "工具类文档"
}

+ 1
- 0
react-ui/types/tsconfig.tsbuildinfo
File diff suppressed because it is too large
View File


Loading…
Cancel
Save