Browse Source

Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev

dev-active_learn
somunslotus 10 months ago
parent
commit
2fdacab4e4
100 changed files with 1397 additions and 614 deletions
  1. +1
    -0
      .gitignore
  2. +12
    -2
      k8s/template-yaml/k8s-6system.yaml
  3. +5
    -0
      react-ui/config/routes.ts
  4. +1
    -1
      react-ui/package.json
  5. +1
    -1
      react-ui/src/app.tsx
  6. BIN
      react-ui/src/assets/img/confirm-icon.png
  7. +0
    -0
      react-ui/src/assets/img/copy-icon.png
  8. +12
    -1
      react-ui/src/components/IFramePage/index.tsx
  9. +1
    -1
      react-ui/src/components/ParameterInput/index.less
  10. +7
    -2
      react-ui/src/components/ParameterInput/index.tsx
  11. +1
    -1
      react-ui/src/components/ParameterSelect/config.tsx
  12. +14
    -3
      react-ui/src/components/ParameterSelect/index.tsx
  13. +2
    -0
      react-ui/src/components/ResourceSelectorModal/config.tsx
  14. +0
    -202
      react-ui/src/hooks/index.ts
  15. +6
    -1
      react-ui/src/hooks/useCacheState.ts
  16. +25
    -0
      react-ui/src/hooks/useCallbackState.ts
  17. +47
    -0
      react-ui/src/hooks/useCheck.ts
  18. +3
    -3
      react-ui/src/hooks/useComputingResource.ts
  19. +40
    -0
      react-ui/src/hooks/useDomSize.ts
  20. +3
    -1
      react-ui/src/hooks/useDraggable.ts
  21. +24
    -0
      react-ui/src/hooks/useEffectWhen.ts
  22. +24
    -0
      react-ui/src/hooks/useResetForm.ts
  23. +46
    -0
      react-ui/src/hooks/useSSE.ts
  24. +19
    -0
      react-ui/src/hooks/useStateRef.ts
  25. +26
    -0
      react-ui/src/hooks/useVisible.ts
  26. +16
    -3
      react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx
  27. +1
    -1
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  28. +8
    -15
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  29. +3
    -2
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  30. +2
    -1
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  31. +1
    -1
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  32. +6
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  33. +1
    -1
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  34. +12
    -0
      react-ui/src/pages/Experiment/Aim/index.tsx
  35. +6
    -2
      react-ui/src/pages/Experiment/Comparison/index.tsx
  36. +2
    -1
      react-ui/src/pages/Experiment/Info/index.jsx
  37. +5
    -3
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
  38. +1
    -1
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  39. +1
    -3
      react-ui/src/pages/Experiment/index.jsx
  40. +1
    -1
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  41. +2
    -2
      react-ui/src/pages/Mirror/Info/index.tsx
  42. +1
    -1
      react-ui/src/pages/Mirror/List/index.tsx
  43. +41
    -47
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  44. +1
    -1
      react-ui/src/pages/Model/components/ModelMetrics/index.tsx
  45. +1
    -1
      react-ui/src/pages/ModelDeployment/List/index.tsx
  46. +2
    -2
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  47. +1
    -1
      react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx
  48. +1
    -1
      react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
  49. +2
    -1
      react-ui/src/pages/Pipeline/Info/index.jsx
  50. +3
    -2
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  51. +35
    -42
      react-ui/src/pages/Pipeline/index.jsx
  52. +4
    -4
      react-ui/src/pages/Points/index.tsx
  53. +2
    -1
      react-ui/src/pages/System/User/components/ResetPwd.tsx
  54. +21
    -11
      react-ui/src/pages/System/User/edit.tsx
  55. +6
    -9
      react-ui/src/pages/System/User/index.tsx
  56. +1
    -1
      react-ui/src/pages/Workspace/index.tsx
  57. +32
    -7
      react-ui/src/requestConfig.ts
  58. +10
    -1
      react-ui/src/services/dataset/index.js
  59. +12
    -12
      react-ui/src/services/system/user.ts
  60. +39
    -0
      react-ui/src/stories/ParameterInput.stories.tsx
  61. +14
    -0
      react-ui/src/types.ts
  62. +1
    -0
      react-ui/src/types/system/user.d.ts
  63. +12
    -2
      react-ui/src/utils/constant.ts
  64. +15
    -7
      react-ui/src/utils/date.ts
  65. +30
    -17
      react-ui/src/utils/downloadfile.ts
  66. +69
    -16
      react-ui/src/utils/format.ts
  67. +2
    -3
      react-ui/src/utils/loading.tsx
  68. +15
    -0
      react-ui/src/utils/localStorage.ts
  69. +3
    -2
      react-ui/src/utils/promise.ts
  70. +21
    -4
      react-ui/src/utils/sessionStorage.ts
  71. +4
    -0
      react-ui/src/utils/statusTableCell.tsx
  72. +33
    -9
      react-ui/src/utils/table.tsx
  73. +52
    -13
      react-ui/src/utils/ui.tsx
  74. +10
    -1
      react-ui/typedoc.json
  75. +0
    -1
      react-ui/types/tsconfig.tsbuildinfo
  76. +7
    -1
      ruoyi-api/ruoyi-api-system/pom.xml
  77. +21
    -0
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteAuthService.java
  78. +30
    -0
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteMmpService.java
  79. +1
    -5
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java
  80. +8
    -4
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/Constant.java
  81. +35
    -37
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java
  82. +30
    -0
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteAuthFallbackFactory.java
  83. +51
    -0
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteMmpFallbackFactory.java
  84. +2
    -0
      ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  85. +25
    -2
      ruoyi-auth/pom.xml
  86. +2
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/RuoYiAuthApplication.java
  87. +26
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/controller/Oauth2Controller.java
  88. +37
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/domain/OauthAccount.java
  89. +10
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/mapper/Oauth2Mapper.java
  90. +45
    -0
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/Oauth2Service.java
  91. +41
    -66
      ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
  92. +31
    -0
      ruoyi-auth/src/main/resources/mapper/auth/Oauth2Mapper.xml
  93. +2
    -0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/ServiceNameConstants.java
  94. +18
    -18
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java
  95. +14
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java
  96. +61
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/git/GitLinkController.java
  97. +1
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/image/ImageController.java
  98. +1
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/image/ImageVersionController.java
  99. +13
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java
  100. +1
    -1
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java

+ 1
- 0
.gitignore View File

@@ -62,3 +62,4 @@ mvnw
*storybook.log

/react-ui/docs
/react-ui/types/tsconfig.tsbuildinfo

+ 12
- 2
k8s/template-yaml/k8s-6system.yaml View File

@@ -18,7 +18,11 @@ spec:
image: ${k8s-6system-image}
ports:
- containerPort: 9201

env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
---
apiVersion: v1
kind: Service
@@ -28,9 +32,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9201
- name: http
port: 9201
nodePort: 31207
protocol: TCP
- name: debug
nodePort: 31220
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-system


+ 5
- 0
react-ui/config/routes.ts View File

@@ -143,6 +143,11 @@ export default [
path: 'compare',
component: './Experiment/Comparison/index',
},
{
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
],
},
{


+ 1
- 1
react-ui/package.json View File

@@ -16,7 +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",
"docs": "typedoc",
"gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup",


+ 1
- 1
react-ui/src/app.tsx View File

@@ -10,7 +10,7 @@ import '../public/fonts/font.css';
import { getAccessToken } from './access';
import ErrorBoundary from './components/ErrorBoundary';
import './dayjsConfig';
import { removeAllPageCacheState } from './hooks/pageCacheState';
import { removeAllPageCacheState } from './hooks/useCacheState';
import {
getRemoteMenu,
getRoutersInfo,


BIN
react-ui/src/assets/img/confirm-icon.png View File

Before After
Width: 462  |  Height: 276  |  Size: 45 kB

react-ui/src/assets/img/comfirm-icon.png → react-ui/src/assets/img/copy-icon.png View File


+ 12
- 1
react-ui/src/components/IFramePage/index.tsx View File

@@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
@@ -13,6 +14,7 @@ export enum IframePageType {
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink', // git link
Aim = 'Aim', // 实验对比
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
@@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
});
case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
case IframePageType.Aim: // Aim
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '',
});
}
};

type IframePageProps = {
/** 子系统 */
type: IframePageType;
/** 是否可以在页签上打开 */
openInTab: boolean;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
@@ -42,7 +52,7 @@ type IframePageProps = {
};

/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, className, style }: IframePageProps) {
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);

@@ -65,6 +75,7 @@ function IframePage({ type, className, style }: IframePageProps) {
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div>
);
}


+ 1
- 1
react-ui/src/components/ParameterInput/index.less View File

@@ -22,7 +22,7 @@
border-radius: 4px;

&__value {
.singleLine();
//.singleLine();
margin-right: 8px;
font-size: @font-size-input;
line-height: 1.5714285714285714;


+ 7
- 2
react-ui/src/components/ParameterInput/index.tsx View File

@@ -6,7 +6,7 @@

import { CommonTabKeys } from '@/enums';
import { CloseOutlined } from '@ant-design/icons';
import { ConfigProvider, Form, Input } from 'antd';
import { ConfigProvider, Form, Input, Typography } from 'antd';
import { RuleObject } from 'antd/es/form';
import classNames from 'classnames';
import './index.less';
@@ -120,7 +120,12 @@ function ParameterInput({
>
{valueObj?.showValue ? (
<div className="parameter-input__content">
<span className="parameter-input__content__value">{valueObj?.showValue}</span>
<Typography.Text
className="parameter-input__content__value"
ellipsis={{ tooltip: valueObj.showValue }}
>
{valueObj.showValue}
</Typography.Text>
<CloseOutlined
className="parameter-input__content__close-icon"
onClick={handleRemove}


+ 1
- 1
react-ui/src/components/ParameterSelect/config.tsx View File

@@ -1,4 +1,4 @@
import { filterResourceStandard, resourceFieldNames } from '@/hooks/resource';
import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource';
import { ServiceData } from '@/pages/ModelDeployment/types';
import { getDatasetList, getModelList } from '@/services/dataset/index.js';
import { getServiceListReq } from '@/services/modelDeployment';


+ 14
- 3
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -4,7 +4,7 @@
* @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务
*/

import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { to } from '@/utils/promise';
import { Select, type SelectProps } from 'antd';
import { useEffect, useState } from 'react';
@@ -16,13 +16,17 @@ export type ParameterSelectObject = {
[key: string]: any;
};

export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource';

export interface ParameterSelectProps extends SelectProps {
/** 类型 */
dataType: 'dataset' | 'model' | 'service' | 'resource';
dataType: ParameterSelectDataType;
/** 是否只是展示信息 */
display?: boolean;
/** 值,支持对象,对象必须包含 value */
value?: string | ParameterSelectObject;
/** 用于流水线, 流水线资源规格要求 id 为字符串 */
isPipeline?: boolean;
/** 修改后回调 */
onChange?: (value: string | ParameterSelectObject) => void;
}
@@ -32,6 +36,7 @@ function ParameterSelect({
dataType,
display = false,
value,
isPipeline = false,
onChange,
...rest
}: ParameterSelectProps) {
@@ -39,6 +44,12 @@ function ParameterSelect({
const propsConfig = paramSelectConfig[dataType];
const valueText = typeof value === 'object' && value !== null ? value.value : value;
const [resourceStandardList] = useComputingResource();
const computingResource = isPipeline
? resourceStandardList.map((v) => ({
...v,
id: String(v.id),
}))
: resourceStandardList;

useEffect(() => {
// 获取下拉数据
@@ -56,7 +67,7 @@ function ParameterSelect({
getSelectOptions();
}, [propsConfig]);

const selectOptions = dataType === 'resource' ? resourceStandardList : options;
const selectOptions = dataType === 'resource' ? computingResource : options;

const handleChange = (text: string) => {
if (typeof value === 'object' && value !== null) {


+ 2
- 0
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey,
page: 0,
size: 2000,
status: 'available',
state: 1,
});
if (res && res.data) {
const list = res.data.content || [];


+ 0
- 202
react-ui/src/hooks/index.ts View File

@@ -1,202 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-15 10:01:29
* @Description: 自定义 hooks
*/
import { FormInstance } from 'antd';
import { debounce } from 'lodash';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
/**
* 生成具有初始值的状态引用
*
* @param initialValue - 状态的初始值
* @return 包含状态值、状态设置函数和可变引用对象的数组
*/
export function useStateRef<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);

const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);

return [value, setValue, ref] as const;
}

/**
* 生成一个自定义钩子,用于管理模态框的可见性状态。
*
* @param initialValue - 模态框的初始可见性状态。
* @return 一个数组,包含可见性状态和打开和关闭模态框的函数。
*/
export function useVisible(initialValue: boolean) {
const [visible, setVisible] = useState(initialValue);
const ref = useRef(initialValue);

const open = useCallback(() => {
setVisible(true);
}, []);

const close = useCallback(() => {
setVisible(false);
}, []);

useEffect(() => {
ref.current = visible;
}, [visible]);

return [visible, open, close, ref] as const;
}

type Callback<T> = (state: T) => void;

/**
* 生成一个具有回调机制的可变状态值和更新它的函数。
*
* @param initialValue - 初始状态值。
* @return 一个元组,包含当前状态值和用于更新状态的函数。
*/
export function useCallbackState<T>(initialValue: T) {
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<Callback<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => {
_setState(newValue);
if (callback && typeof callback === 'function') {
callbackQueue.current.push(callback);
}
};
return [state, setState] as const;
}

/**
* 用于追踪 DOM 元素尺寸的 hook。
*
* @param initialWidth - 初始宽度。
* @param initialHeight - 初始高度。
* @param deps - 依赖列表。
* @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。
*/
export function useDomSize<T extends HTMLElement>(
initialWidth: number,
initialHeight: number,
deps: React.DependencyList = [],
) {
const domRef = useRef<T>(null);
const [width, setWidth] = useState(initialWidth);
const [height, setHeight] = useState(initialHeight);

useEffect(() => {
const setDomHeight = () => {
if (domRef.current) {
setHeight(domRef.current.offsetHeight);
setWidth(domRef.current.offsetWidth);
}
};
const debounceFunc = debounce(setDomHeight, 100);

setDomHeight();
window.addEventListener('resize', debounceFunc);

return () => {
window.removeEventListener('resize', debounceFunc);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [domRef, ...deps]);

return [domRef, { width, height }] as const;
}

/**
* 用于在 modal 关闭时重置 Form 表单的 hook。
*
* @param form - Ant Design Form 表单实例
* @param open - modal 是否打开
*/
export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
const prevOpenRef = useRef<boolean>();

useEffect(() => {
prevOpenRef.current = open;
}, [open]);

const prevOpen = prevOpenRef.current;

useEffect(() => {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevOpen, open]);
};

/**
* Executes the effect function when the specified condition is true.
*
* @param effect - The effect function to execute.
* @param when - The condition to trigger the effect.
* @param deps - The dependencies for the effect.
*/
export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => {
const requestFns = useRef<(() => void)[]>([]);
useEffect(() => {
if (when) {
effect();
} else {
requestFns.current.splice(0, 1, effect);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

useEffect(() => {
if (when) {
const fn = requestFns.current.pop();
fn?.();
}
}, [when]);
};

// 选择、全选操作
export const useCheck = <T>(list: T[]) => {
const [selected, setSelected] = useState<T[]>([]);

const checked = useMemo(() => {
return selected.length === list.length && selected.length > 0;
}, [selected, list]);

const indeterminate = useMemo(() => {
return selected.length > 0 && selected.length < list.length;
}, [selected, list]);

const checkAll = useCallback(() => {
setSelected(checked ? [] : list);
}, [list, checked]);

const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]);

const checkSingle = useCallback(
(item: T) => {
setSelected((prev) => {
if (isSingleChecked(item)) {
return prev.filter((i) => i !== item);
} else {
return [...prev, item];
}
});
},
[isSingleChecked],
);

return [
selected,
setSelected,
checked,
indeterminate,
checkAll,
isSingleChecked,
checkSingle,
] as const;
};

react-ui/src/hooks/pageCacheState.ts → react-ui/src/hooks/useCacheState.ts View File

@@ -29,13 +29,18 @@ const removeCacheState = (key: string) => {
}
};

// 移除所有页面 state 缓存
/**
* 移除所有页面 state 缓存
*/
export const removeAllPageCacheState = () => {
pageKeys.forEach((key) => {
sessionStorage.removeItem(key);
});
};

/**
* 缓存页面数据
*/
export const useCacheState = () => {
const { pathname } = window.location;
const key = 'pagecache:' + pathname;

+ 25
- 0
react-ui/src/hooks/useCallbackState.ts View File

@@ -0,0 +1,25 @@
import { useEffect, useRef, useState } from 'react';

type Callback<T> = (state: T) => void;

/**
* 生成一个具有回调机制的可变状态值和更新它的函数。谨慎使用
*
* @param initialValue - 初始状态值。
* @return 一个元组,包含当前状态值和用于更新状态的函数。
*/
export function useCallbackState<T>(initialValue: T) {
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<Callback<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => {
_setState(newValue);
if (callback && typeof callback === 'function') {
callbackQueue.current.push(callback);
}
};
return [state, setState] as const;
}

+ 47
- 0
react-ui/src/hooks/useCheck.ts View File

@@ -0,0 +1,47 @@
import { useCallback, useMemo, useState } from 'react';

/**
* 选择、全选操作
* @param list - 需要进行选择的列表
* @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法]
*/
export const useCheck = <T>(list: T[]) => {
const [selected, setSelected] = useState<T[]>([]);

const checked = useMemo(() => {
return selected.length === list.length && selected.length > 0;
}, [selected, list]);

const indeterminate = useMemo(() => {
return selected.length > 0 && selected.length < list.length;
}, [selected, list]);

const checkAll = useCallback(() => {
setSelected(checked ? [] : list);
}, [list, checked]);

const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]);

const checkSingle = useCallback(
(item: T) => {
setSelected((prev) => {
if (isSingleChecked(item)) {
return prev.filter((i) => i !== item);
} else {
return [...prev, item];
}
});
},
[isSingleChecked],
);

return [
selected,
setSelected,
checked,
indeterminate,
checkAll,
isSingleChecked,
checkSingle,
] as const;
};

react-ui/src/hooks/resource.ts → react-ui/src/hooks/useComputingResource.ts View File

@@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react';

const computingResource: ComputingResource[] = [];

// 过滤资源规格
/** 过滤资源规格 */
export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = (
input: string,
option?: ComputingResource,
@@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil
);
};

// 资源规格字段
/** 资源规格字段 */
export const resourceFieldNames = {
label: 'description',
value: 'id',
};

// 获取资源规格
/** 获取资源规格 */
export function useComputingResource() {
const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]);


+ 40
- 0
react-ui/src/hooks/useDomSize.ts View File

@@ -0,0 +1,40 @@
import { debounce } from 'lodash';
import { useEffect, useRef, useState } from 'react';

/**
* 用于追踪 DOM 元素尺寸的 hook。
*
* @param initialWidth - 初始宽度。
* @param initialHeight - 初始高度。
* @param deps - 依赖列表。
* @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。
*/
export function useDomSize<T extends HTMLElement>(
initialWidth: number,
initialHeight: number,
deps: React.DependencyList = [],
) {
const domRef = useRef<T>(null);
const [width, setWidth] = useState(initialWidth);
const [height, setHeight] = useState(initialHeight);

useEffect(() => {
const setDomHeight = () => {
if (domRef.current) {
setHeight(domRef.current.offsetHeight);
setWidth(domRef.current.offsetWidth);
}
};
const debounceFunc = debounce(setDomHeight, 100);

setDomHeight();
window.addEventListener('resize', debounceFunc);

return () => {
window.removeEventListener('resize', debounceFunc);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

return [domRef, { width, height }] as const;
}

react-ui/src/hooks/draggable.ts → react-ui/src/hooks/useDraggable.ts View File

@@ -1,6 +1,8 @@
// 处理 react-draggable 组件拖动结束时,响应了点击事件的
import { useState } from 'react';

/**
* 处理 react-draggable 组件拖动结束时,响应了点击事件的
*/
export const useDraggable = (onClick: () => void) => {
const [isDragging, setIsDragging] = useState(false);


+ 24
- 0
react-ui/src/hooks/useEffectWhen.ts View File

@@ -0,0 +1,24 @@
import { useEffect, useRef } from 'react';

/**
* 当指定的条件为真时执行 Effect 函数。
*
* @param effect - The effect function to execute.
* @param when - The condition to trigger the effect.
* @param deps - The dependencies for the effect.
*/
export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => {
const requestFn = useRef<(() => void) | undefined>(effect);
useEffect(() => {
requestFn.current = effect;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, effect]);

useEffect(() => {
if (when && requestFn.current) {
requestFn.current();
requestFn.current = undefined;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, when]);
};

+ 24
- 0
react-ui/src/hooks/useResetForm.ts View File

@@ -0,0 +1,24 @@
import { FormInstance } from 'antd';
import { useEffect, useRef } from 'react';

/**
* 用于在 modal 关闭时重置 Form 表单的 hook。
*
* @param form - Ant Design Form 表单实例
* @param open - modal 是否打开
*/
export const useResetForm = (form: FormInstance, open: boolean) => {
const prevOpenRef = useRef<boolean>();

useEffect(() => {
prevOpenRef.current = open;
}, [open]);

const prevOpen = prevOpenRef.current;

useEffect(() => {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevOpen, open]);
};

+ 46
- 0
react-ui/src/hooks/useSSE.ts View File

@@ -0,0 +1,46 @@
import { parseJsonText } from '@/utils';
import { useCallback, useRef } from 'react';

export const useSSE = (onMessage: (data: any) => void) => {
const evtSourceRef = useRef<EventSource | null>(null);

const setupSSE = useCallback(
(name: string, namespace: string) => {
const { origin } = location;
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
{ withCredentials: false },
);
evtSource.onmessage = (event) => {
const data = event?.data;
if (!data) {
return;
}
const dataJson = parseJsonText(data);
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
onMessage(nodes);
}
}
};

evtSource.onerror = (error) => {
console.error('SSE error: ', error);
};

evtSourceRef.current = evtSource;
},
[onMessage],
);

const closeSSE = useCallback(() => {
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
}
}, []);

return [setupSSE, closeSSE];
};

+ 19
- 0
react-ui/src/hooks/useStateRef.ts View File

@@ -0,0 +1,19 @@
import { useEffect, useRef, useState } from 'react';

/**
* 生成具有初始值的状态引用
*
* @param initialValue - 状态的初始值
* @return 包含状态值、状态设置函数和可变引用对象的数组
*/
export function useStateRef<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);

const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);

return [value, setValue, ref] as const;
}

+ 26
- 0
react-ui/src/hooks/useVisible.ts View File

@@ -0,0 +1,26 @@
import { useCallback, useEffect, useRef, useState } from 'react';

/**
* 生成一个自定义钩子,用于管理模态框的可见性状态。
*
* @param initialValue - 模态框的初始可见性状态。
* @return 一个数组,包含 visible、打开函数、关闭函数和 visible ref。
*/
export function useVisible(initialValue: boolean) {
const [visible, setVisible] = useState(initialValue);
const ref = useRef(initialValue);

const open = useCallback(() => {
setVisible(true);
}, []);

const close = useCallback(() => {
setVisible(false);
}, []);

useEffect(() => {
ref.current = visible;
}, [visible]);

return [visible, open, close, ref] as const;
}

+ 16
- 3
react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx View File

@@ -1,6 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';
import { type ExperimentInstance } from '@/types';
@@ -58,7 +58,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentInstance(instance.id);
},
@@ -96,6 +97,18 @@ function ExperimentInstanceComponent({
}
};

// 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false,
onOk: () => {
terminateExperimentInstance(instance);
},
});
};

// 终止实验实例
const terminateExperimentInstance = async (instance: ExperimentInstance) => {
const request = config.stopInsReq;
@@ -188,7 +201,7 @@ function ExperimentInstanceComponent({
item.status === ExperimentStatus.Terminated
}
icon={<KFIcon type="icon-zhongzhi" />}
onClick={() => terminateExperimentInstance(item)}
onClick={() => handleTerminate(item)}
>
终止
</Button>


+ 1
- 1
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -7,7 +7,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';


+ 8
- 15
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addDataset } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui';
import {
getFileListFromEvent,
limitUploadFileType,
removeUploadedFile,
validateUploadFiles,
} from '@/utils/ui';
import {
Button,
Form,
@@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {

function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
// const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);

// useEffect(() => {
// getClusterOptions();
// }, []);

// 上传组件参数
const uploadProps: UploadProps = {
@@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
defaultFileList: [],
accept: '.zip,.tgz',
beforeUpload: limitUploadFileType('zip,tgz'),
onRemove: removeUploadedFile,
};

// 获取集群版本数据
// const getClusterOptions = async () => {
// const [res] = await to(getDictSelectOption('available_cluster'));
// if (res) {
// setClusterOptions(res);
// }
// };

// 上传请求
const createDataset = async (params: any) => {
const [res] = await to(addDataset(params));
@@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入数据名称" showCount allowClear maxLength={40} />
</Form.Item>
<Form.Item
label="数据集版本"


+ 3
- 2
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
@@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
Authorization: getAccessToken() || '',
},
defaultFileList: [],
onRemove: removeUploadedFile,
};

// 上传请求
@@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入模型名称" showCount allowClear maxLength={40} />
</Form.Item>
<Form.Item
label="模型版本"


+ 2
- 1
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
@@ -50,6 +50,7 @@ function AddVersionModal({
defaultFileList: [],
beforeUpload: config.beforeUpload,
accept: config.uploadAccept,
onRemove: removeUploadedFile,
};

// 上传请求


+ 1
- 1
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -1,5 +1,5 @@
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';


+ 6
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2025-03-24 15:41:42
* @Description: 版本文件列表
*/

import KFIcon from '@/components/KFIcon';
import {
ResourceData,


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

@@ -6,7 +6,7 @@

import KFIcon from '@/components/KFIcon';
import { DevEditorStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import {
deleteEditorReq,
getEditorListReq,


+ 12
- 0
react-ui/src/pages/Experiment/Aim/index.tsx View File

@@ -0,0 +1,12 @@
/*
* @Author: 赵伟
* @Date: 2025-03-31 16:38:59
* @Description: 实验对比 Aim
*/

import IframePage, { IframePageType } from '@/components/IFramePage';

function AimPage() {
return <IframePage type={IframePageType.Aim}></IframePage>;
}
export default AimPage;

+ 6
- 2
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -12,8 +12,9 @@ import {
} from '@/services/experiment';
import { tableSorter } from '@/utils';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useSearchParams } from '@umijs/max';
import { useNavigate, useSearchParams } from '@umijs/max';
import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
@@ -46,6 +47,7 @@ function ExperimentComparison() {
});

const { message } = App.useApp();
const navigate = useNavigate();
const config = comparisonConfig[comparisonType];

useEffect(() => {
@@ -73,7 +75,9 @@ function ExperimentComparison() {
const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) {
const url = res.data;
window.open(url, '_blank');
// window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('../compare-visual');
}
};



+ 2
- 1
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -1,5 +1,6 @@
import { ExperimentStatus } from '@/enums';
import { useStateRef, useVisible } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';


+ 5
- 3
react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx View File

@@ -1,6 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
deleteManyExperimentIns,
@@ -62,7 +62,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentInstance(instance.id);
},
@@ -101,7 +102,8 @@ function ExperimentInstanceComponent({
// 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定要终止此次实验运行吗?',
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false,
onOk: () => {
terminateExperimentInstance(instance);


+ 1
- 1
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -5,7 +5,7 @@
*/

import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd';


+ 1
- 3
react-ui/src/pages/Experiment/index.jsx View File

@@ -1,7 +1,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import {
deleteExperimentById,
getExperiment,
@@ -286,8 +286,6 @@ function Experiment() {
message.success('运行成功');
refreshExperimentList();
refreshExperimentIns(id);
} else {
message.error('运行失败');
}
};



+ 1
- 1
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -1,6 +1,6 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import { hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
schedulerAlgorithms,


+ 2
- 2
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -7,8 +7,8 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { useDomSize } from '@/hooks/useDomSize';
import {
deleteMirrorVersionReq,
getMirrorInfoReq,


+ 1
- 1
react-ui/src/pages/Mirror/List/index.tsx View File

@@ -5,7 +5,7 @@
*/
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';


+ 41
- 47
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File

@@ -4,12 +4,12 @@
* @Description: 模型演化
*/

import { useEffectWhen } from '@/hooks';
import { useEffectWhen } from '@/hooks/useEffectWhen';
import { getModelAtlasReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import G6, { G6GraphEvent, Graph, INode } from '@antv/g6';
import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

import NodeTooltips from '../NodeTooltips';
import styles from './index.less';
@@ -73,17 +73,45 @@ function ModelEvolution({
};
}, []);

useEffectWhen(
() => {
if (version) {
getModelAtlas();
} else {
clearGraphData();
}
},
isActive,
[resourceId, version],
);
const getModelAtlas = useCallback(async () => {
// 请求失败或者版本不存在时,清除图形
function clearGraphData() {
graph.data({
nodes: [],
edges: [],
});
graph.render();
graph.fitView();
}

if (!resourceId || !identifier || !version) {
clearGraphData();
return;
}

const params = {
id: resourceId,
identifier,
version,
};
const [res] = await to(getModelAtlasReq(params));
if (res && res.data) {
const data = normalizeTreeData(res.data);
apiData.current = data;
hierarchyNodes.current = traverseHierarchically(data);
const graphData = getGraphData(data, hierarchyNodes.current);

graph.data(graphData);
graph.render();
graph.fitView();
setShowNodeTooltip(false);
setEnterTooltip(false);
} else {
clearGraphData();
}
}, [resourceId, identifier, version]);

useEffectWhen(getModelAtlas, isActive, [resourceId, identifier, version]);

// 初始化图
const initGraph = () => {
@@ -249,40 +277,6 @@ function ModelEvolution({
}, 100);
};

// 获取模型依赖
const getModelAtlas = async () => {
const params = {
id: resourceId,
identifier,
version,
};
const [res] = await to(getModelAtlasReq(params));
if (res && res.data) {
const data = normalizeTreeData(res.data);
apiData.current = data;
hierarchyNodes.current = traverseHierarchically(data);
const graphData = getGraphData(data, hierarchyNodes.current);

graph.data(graphData);
graph.render();
graph.fitView();
setShowNodeTooltip(false);
setEnterTooltip(false);
} else {
clearGraphData();
}
};

// 请求失败或者版本不存在时,清除图形
function clearGraphData() {
graph.data({
nodes: [],
edges: [],
});
graph.render();
graph.fitView();
}

return (
<div className={styles['model-evolution']}>
<div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>


+ 1
- 1
react-ui/src/pages/Model/components/ModelMetrics/index.tsx View File

@@ -1,6 +1,6 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import TableColTitle from '@/components/TableColTitle';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset';
import { tableSorter } from '@/utils';
import { to } from '@/utils/promise';


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

@@ -6,7 +6,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { serviceTypeOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';


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

@@ -8,8 +8,8 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ServiceRunStatus, serviceStatusOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useComputingResource } from '@/hooks/resource';
import { useCacheState } from '@/hooks/useCacheState';
import { useComputingResource } from '@/hooks/useComputingResource';
import {
deleteServiceVersionReq,
getServiceInfoReq,


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

@@ -1,6 +1,6 @@
import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo';
import { ServiceRunStatus } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { ServiceVersionData } from '@/pages/ModelDeployment/types';
import { formatDate } from '@/utils/date';
import { formatCodeConfig, formatModel } from '@/utils/format';


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

@@ -1,6 +1,6 @@
import KFModal from '@/components/KFModal';
import { ServiceRunStatus } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { type ServiceVersionData } from '@/pages/ModelDeployment/types';
import { getServiceVersionCompareReq } from '@/services/modelDeployment';
import { isEmpty } from '@/utils';


+ 2
- 1
react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -1,5 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { useStateRef, useVisible } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText, s8 } from '@/utils';


+ 3
- 2
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -1,7 +1,7 @@
import CodeSelectorModal from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import ParameterInput, { requiredValidator } from '@/components/ParameterInput';
import ParameterSelect from '@/components/ParameterSelect';
import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect';
import ResourceSelectorModal, {
ResourceSelectorType,
selectorTypeConfig,
@@ -520,7 +520,8 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
{item.value.type === 'select' ? (
['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<ParameterSelect
dataType={item.value.item_type as any}
isPipeline
dataType={item.value.item_type as ParameterSelectDataType}
placeholder={item.value.placeholder}
/>
) : null


+ 35
- 42
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,7 +1,7 @@
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import PageTitle from '@/components/PageTitle';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import {
addWorkflow,
cloneWorkflow,
@@ -11,6 +11,7 @@ import {
removeWorkflow,
} from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd';
@@ -132,23 +133,38 @@ const Pipeline = () => {
modalConfirm({
title: '删除后,该流水线将不可恢复',
content: '是否确认删除?',
onOk: () => {
removeWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: pipeList.length === 1 ? Math.max(1, prev.current - 1) : prev.current,
};
});
getList();
} else {
message.error(ret.msg);
}
});
onOk: async () => {
const { id } = record;
const [res] = await to(removeWorkflow(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: pipeList.length === 1 ? Math.max(1, prev.current - 1) : prev.current,
};
});
}
},
});
};

// 处理复制
const handlePipelineCopy = (record) => {
modalConfirm({
title: '确定复制该条流水线吗?',
okText: '确认',
cancelText: '取消',
isDelete: false,
onOk: async () => {
const { id } = record;
const [res] = await to(cloneWorkflow(id));
if (res) {
message.success('复制成功');
getList();
}
},
});
};
@@ -225,30 +241,7 @@ const Pipeline = () => {
size="small"
key="clone"
icon={<KFIcon type="icon-fuzhi" />}
onClick={async () => {
modalConfirm({
title: '确定复制该条流水线吗?',
okText: '确认',
cancelText: '取消',
isDelete: false,
onOk: () => {
cloneWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('复制成功');
getList();
} else {
message.error('复制失败');
}
});

// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
}}
onClick={() => handlePipelineCopy(record)}
>
复制
</Button>


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

@@ -25,19 +25,19 @@ enum TaskType {

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


+ 2
- 1
react-ui/src/pages/System/User/components/ResetPwd.tsx View File

@@ -17,6 +17,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const [form] = Form.useForm();
const loginPassword = Form.useWatch('password', form);
const userId = props.values.userId;
const originPassword = props.values.originPassword;

const intl = useIntl();
const handleOk = () => {
@@ -26,7 +27,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
props.onCancel();
};
const handleFinish = async (values: Record<string, any>) => {
props.onSubmit({ ...values, userId } as FormValueType);
props.onSubmit({ password: values.password, userId, originPassword } as FormValueType);
};

const checkPassword = (rule: any, value: string) => {


+ 21
- 11
react-ui/src/pages/System/User/edit.tsx View File

@@ -63,8 +63,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
loginIp: props.values.loginIp,
loginDate: props.values.loginDate,
remark: props.values.remark,
gitLinkUsername: props.values.gitLinkUsername,
gitLinkPassword: props.values.gitLinkPassword,
// gitLinkUsername: props.values.gitLinkUsername,
// gitLinkPassword: props.values.gitLinkPassword,
credit: props.values.credit,
});
}, [form, props, statusOptions]);
@@ -80,6 +80,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
const params = {
...values,
userId: props.values.userId,
originPassword: props.values.originPassword,
};
props.onSubmit(params as UserFormData);
};
@@ -150,7 +151,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }}
rules={[
{
required: false,
required: true,
message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />,
},
{
@@ -174,7 +175,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }}
rules={[
{
required: false,
required: true,
message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />,
},
{
@@ -194,7 +195,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
id: 'system.user.user_name',
defaultMessage: '用户账号',
})}
hidden={userId}
disabled={!!props.values.userId}
placeholder="请输入用户账号"
colProps={{ md: 12, xl: 12 }}
rules={[
@@ -202,9 +203,9 @@ const UserForm: React.FC<UserFormProps> = (props) => {
required: true,
},
{
pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
message:
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾',
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾',
},
]}
/>
@@ -214,14 +215,23 @@ const UserForm: React.FC<UserFormProps> = (props) => {
id: 'system.user.password',
defaultMessage: '密码',
})}
hidden={userId}
placeholder="请输入密码"
colProps={{ md: 12, xl: 12 }}
fieldProps={{
autoComplete: 'new-password',
}}
allowClear
rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]}
rules={
props.values.userId
? []
: [
{ required: true, message: '请输入密码!' },
{
pattern: /^[A-Za-z0-9!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]{8,16}$/,
message: '密码长度为8 ~ 16位,只支持字母数字和符号',
},
]
}
/>
<ProFormSelect
valueEnum={sexOptions}
@@ -279,7 +289,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }}
rules={[{ required: true, message: '请选择角色!' }]}
/>
<ProFormText
{/* <ProFormText
name="gitLinkUsername"
label="Git 用户名"
placeholder="请输入 Git 用户名"
@@ -300,7 +310,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
autoComplete: 'new-password',
}}
rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]}
/>
/> */}
<ProFormDigit
name="credit"
label="算力积分"


+ 6
- 9
react-ui/src/pages/System/User/index.tsx View File

@@ -4,7 +4,6 @@ import { getRoleList } from '@/services/system/role';
import {
addUser,
changeUserStatus,
exportUser,
getDeptTree,
getUser,
getUserList,
@@ -13,6 +12,7 @@ import {
updateAuthRole,
updateUser,
} from '@/services/system/user';
import { downloadXlsx } from '@/utils/downloadfile';
import {
DeleteOutlined,
DownOutlined,
@@ -132,15 +132,12 @@ const handleRemoveOne = async (selectedRow: API.System.User) => {

/**
* 导出数据
*
*
*/
const handleExport = async () => {
const handleExport = async (deptId: string) => {
const hide = message.loading('正在导出');
try {
await exportUser();
await downloadXlsx('/api/system/user/export', 'POST', { data: { deptId: deptId } });
hide();
message.success('导出成功');
return true;
} catch (error) {
hide();
@@ -470,7 +467,7 @@ const UserTableList: React.FC = () => {
key="export"
hidden={!access.hasPerms('system:user:export')}
onClick={async () => {
handleExport();
handleExport(selectDept.id);
}}
>
<PlusOutlined />{' '}
@@ -563,7 +560,7 @@ const UserTableList: React.FC = () => {
/>
<ResetPwd
onSubmit={async (values: any) => {
const success = await resetUserPwd(values.userId, values.password);
const success = await resetUserPwd(values);
if (success) {
setResetPwdModalVisible(false);
setSelectedRows([]);
@@ -581,7 +578,7 @@ const UserTableList: React.FC = () => {
/>
<AuthRoleForm
onSubmit={async (values: any) => {
const success = await updateAuthRole(values);
const success = await updateAuthRole(currentRow!.userId, values.roleIds);
if (success) {
setAuthRoleModalVisible(false);
setSelectedRows([]);


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

@@ -1,4 +1,4 @@
import { useDraggable } from '@/hooks/draggable';
import { useDraggable } from '@/hooks/useDraggable';
import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';


+ 32
- 7
react-ui/src/requestConfig.ts View File

@@ -53,7 +53,7 @@ export const requestConfig: RequestConfig = {
],
responseInterceptors: [
[
(response: AxiosResponse) => {
async (response: AxiosResponse) => {
const { status, data, config } = response || {};
const options = config as RequestOptions;
const skipErrorHandler = options?.skipErrorHandler;
@@ -63,20 +63,45 @@ export const requestConfig: RequestConfig = {
Loading.hide();
}
if (status >= 200 && status < 300) {
if (status === 204) {
// 无内容或者无需验证
if (status === 204 || skipValidating) {
return response;
} else if (data && (skipValidating || data instanceof Blob || data.code === 200)) {
}

if (data && data.code === 200) {
return response;
}

// Blob 数据
if (data && data instanceof Blob && data.size > 0) {
// 下载文件失败时,返回的是 JSON 数据,格式为:{code: 500, msg: "xxx"}
if (data.type === 'application/json') {
try {
const text = await data.text();
const json = JSON.parse(text);

if (json.code === 500) {
popupError(json.msg || '请求失败', skipErrorHandler);
return Promise.reject(json);
}
} catch (error) {
console.error('JSON 解析失败', error);
}
}
return response;
} else if (data && data.code === 401) {
}

// Token 失效
if (data && data.code === 401) {
clearSessionToken();
setRemoteMenu(null);
gotoLoginPage(false);
popupError('请重新登录');
return Promise.reject(response);
} else {
popupError(data?.msg ?? '请求失败', skipErrorHandler);
return Promise.reject(response);
}

popupError(data?.msg ?? '请求失败', skipErrorHandler);
return Promise.reject(response);
} else {
popupError('请求失败', skipErrorHandler);
return Promise.reject(response);


+ 10
- 1
react-ui/src/services/dataset/index.js View File

@@ -181,4 +181,13 @@ export function compareModelVersion(data) {
method: 'POST',
data,
});
}
}


// 删除上传的文件
export function deleteUploadFileReq(params) {
return request(`/api/mmp/newdataset/deleteFile`, {
method: 'DELETE',
params,
});
}

+ 12
- 12
react-ui/src/services/system/user.ts View File

@@ -60,8 +60,9 @@ export async function removeUser(ids: string, options?: { [key: string]: any })
// 导出用户信息
export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) {
return request<API.Result>(`/api/system/user/export`, {
method: 'GET',
params,
method: 'POST',
data: params,
skipValidating: true,
...(options || {}),
});
}
@@ -93,11 +94,7 @@ export function updateUserProfile(data: API.CurrentUser) {
}

// 用户密码重置
export function resetUserPwd(userId: number, password: string) {
const data = {
userId,
password,
};
export function resetUserPwd(data: any) {
return request<API.Result>('/api/system/user/resetPwd', {
method: 'put',
data: data,
@@ -126,16 +123,19 @@ export function uploadAvatar(data: any) {

// 查询授权角色
export function getAuthRole(userId: number) {
return request('/system/user/authRole/' + userId, {
return request('/api/system/user/authRole/' + userId, {
method: 'get',
});
}

// 保存授权角色
export function updateAuthRole(data: Record<string, any>) {
return request('/system/user/authRole', {
method: 'put',
params: data,
export function updateAuthRole(userId: number, data: Record<string, any>) {
return request(`/api/system/user/authRole/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data: data,
});
}



+ 39
- 0
react-ui/src/stories/ParameterInput.stories.tsx View File

@@ -76,6 +76,45 @@ export const Select: Story = {
},
};

export const Ellipse: Story = {
args: {
placeholder: '请输入工作目录',
style: { width: 300 },
canInput: true,
size: 'large',
},
render: function Render(args) {
const [value, setValue] = useState<ParameterInputValue | undefined>('');

const onClick = () => {
const value = {
value: 'storybook',
showValue:
'storybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybook',
fromSelect: true,
otherValue: 'others',
};
setValue(value);
action('onChange')(value);
};
return (
<>
<ParameterInput
{...args}
value={value}
onChange={(value) => {
setValue(value);
action('onChange')(value);
}}
></ParameterInput>
<Button type="primary" style={{ display: 'block', marginTop: 10 }} onClick={onClick}>
模拟从全局参数选择
</Button>
</>
);
},
};

export const Disabled: Story = {
args: {
placeholder: '请输入工作目录',


+ 14
- 0
react-ui/src/types.ts View File

@@ -136,3 +136,17 @@ export type NodeStatus = {
startedAt: string;
finishedAt: string;
};

// 应用响应
export type AppResponse<T> = {
code: number;
msg: string;
data: T;
};

// 上传文件的响应
export type UploadFileRes = {
fileName: string;
fileSize: number;
url: string;
};

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

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

export interface UserListParams {


+ 12
- 2
react-ui/src/utils/constant.ts View File

@@ -1,3 +1,13 @@
export const xlCols = { span: 12 };
export const xllCols = { span: 10 };
/*
* @Author: 赵伟
* @Date: 2025-02-21 09:52:50
* @Description: 通用表单项输入控件宽度
*/

const xlCols = { span: 12 };
const xllCols = { span: 10 };

/**
* 输入控件宽度,xl: 12, xll: 10
*/
export const formCols = { xl: xlCols, xxl: xllCols };

+ 15
- 7
react-ui/src/utils/date.ts View File

@@ -1,7 +1,7 @@
import dayjs from 'dayjs';

/**
* Calculates the elapsed time between two dates and returns a formatted string representing the duration.
* 计算两个日期之间经过的时间,如 "3分12秒"
*
* @param {string | null | undefined} begin - The starting date.
* @param {string | null | undefined} end - The ending date.
@@ -29,22 +29,30 @@ export const elapsedTime = (begin?: string | null, end?: string | null): string
const hours = duration.hours();
const minutes = duration.minutes();
const seconds = duration.seconds();
const elspsedArray = [];
if (years !== 0) {
return `${years}年${months}个月`;
elspsedArray.push(`${years}年`);
}
if (months !== 0) {
return `${months}个月${days}天`;
elspsedArray.push(`${months}个月`);
}
if (days !== 0) {
return `${days}天${hours}小时`;
elspsedArray.push(`${days}天`);
}
if (hours !== 0) {
return `${hours}小时${minutes}分`;
elspsedArray.push(`${hours}小时`);
}
if (minutes !== 0) {
return `${minutes}分${seconds}秒`;
elspsedArray.push(`${minutes}分`);
}
return `${seconds}秒`;
if (seconds !== 0) {
elspsedArray.push(`${seconds}秒`);
}

if (elspsedArray.length === 0) {
return '0秒';
}
return elspsedArray.slice(0, 2).join('');
};

/**


+ 30
- 17
react-ui/src/utils/downloadfile.ts View File

@@ -1,19 +1,21 @@
import { request } from '@umijs/max';

const mimeMap = {
/** MimeType */
export const mimeMap = {
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
zip: 'application/zip',
};

/**
* 解析blob响应内容并下载
* @param {*} res blob响应内容
* @param {String} mimeType MIME类型
* @param res - blob响应内容
* @param mimeType - MIME类型
*/
export function resolveBlob(res: any, mimeType: string) {
const aLink = document.createElement('a');
const blob = new Blob([res.data], { type: mimeType });
// //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
// 从response的headers中获取filename,
// 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
// console.log(res);
const contentDisposition = decodeURI(res.headers['content-disposition']);
@@ -29,6 +31,11 @@ export function resolveBlob(res: any, mimeType: string) {
document.body.removeChild(aLink);
}

/**
* 下载 Zip 文件
* @param url - url 地址
* @param options - 请求参数
*/
export function downLoadZip(url: string, params?: any) {
request(url, {
method: 'GET',
@@ -40,24 +47,30 @@ export function downLoadZip(url: string, params?: any) {
});
}

export async function downLoadXlsx(url: string, params: any, fileName: string) {
/**
* 下载 XLSX 文件
* @param url - url 地址
* @param method - 请求方式
* @param options - 请求选项
*/
export async function downloadXlsx(
url: string,
method: string = 'GET',
options?: Record<string, any>,
) {
return request(url, {
...params,
method: 'POST',
method: method,
...options,
responseType: 'blob',
}).then((data) => {
const aLink = document.createElement('a');
const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
aLink.style.display = 'none';
aLink.href = URL.createObjectURL(blob);
aLink.setAttribute('download', fileName); // 设置下载文件名称
document.body.appendChild(aLink);
aLink.click();
URL.revokeObjectURL(aLink.href); // 清除引用
document.body.removeChild(aLink);
getResponse: true,
}).then((res) => {
resolveBlob(res, mimeMap.xlsx);
});
}

/**
* @deprecated 无效
*/
export function download(fileName: string) {
window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`;
}

+ 69
- 16
react-ui/src/utils/format.ts View File

@@ -1,3 +1,4 @@
import { BasicInfoLink } from '@/components/BasicInfo/types';
import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import {
@@ -18,8 +19,13 @@ type SelectedCodeConfig = {
show_value?: string; // 后端使用的
};

// 格式化数据集数组
export const formatDatasets = (datasets?: DatasetData[]) => {
/**
* 格式化数据集数组
*
* @param datasets - 数据集数组
* @return 基本信息链接对象数组
*/
export const formatDatasets = (datasets?: DatasetData[]): BasicInfoLink[] | undefined => {
if (!datasets || datasets.length === 0) {
return undefined;
}
@@ -29,8 +35,13 @@ export const formatDatasets = (datasets?: DatasetData[]) => {
}));
};

// 格式化数据集
export const formatDataset = (dataset?: DatasetData) => {
/**
* 格式化数据集
*
* @param dataset - 数据集
* @return 基本信息链接对象
*/
export const formatDataset = (dataset?: DatasetData): BasicInfoLink | undefined => {
if (!dataset) {
return undefined;
}
@@ -40,8 +51,13 @@ export const formatDataset = (dataset?: DatasetData) => {
};
};

// 格式化模型
export const formatModel = (model: ModelData) => {
/**
* 格式化模型
*
* @param model - 模型
* @return 基本信息链接对象
*/
export const formatModel = (model: ModelData): BasicInfoLink | undefined => {
if (!model) {
return undefined;
}
@@ -51,16 +67,28 @@ export const formatModel = (model: ModelData) => {
};
};

// 格式化镜像
export const formatMirror = (mirror: ResourceSelectorResponse) => {
/**
* 格式化镜像
*
* @param mirror - 选择的镜像
* @return 镜像地址
*/
export const formatMirror = (mirror: ResourceSelectorResponse): string | undefined => {
if (!mirror) {
return undefined;
}
return mirror.path;
};

// 格式化代码配置
export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfig) => {
/**
* 格式化代码配置
*
* @param project - 代码配置或者选择的代码配置
* @return 基本信息链接对象
*/
export const formatCodeConfig = (
project?: ProjectDependency | SelectedCodeConfig,
): BasicInfoLink | undefined => {
if (!project) {
return undefined;
}
@@ -81,7 +109,12 @@ export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfi
}
};

// 格式化训练任务(实验实例)
/**
* 格式化训练任务(实验实例)
*
* @param task - 训练任务
* @return 基本信息链接对象
*/
export const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
@@ -92,8 +125,13 @@ export const formatTrainTask = (task?: TrainTask) => {
};
};

// 格式化数据来源
export const formatSource = (source?: string) => {
/**
* 格式化数据来源
*
* @param source - 数据来源枚举值
* @return 数据来源中文名称
*/
export const formatSource = (source?: string): string | undefined => {
if (source === DataSource.Create) {
return '用户上传';
} else if (source === DataSource.HandExport) {
@@ -106,7 +144,12 @@ export const formatSource = (source?: string) => {
return source;
};

// 格式化字符串数组,以逗号分隔
/**
* 格式化字符串数组,以逗号分隔
*
* @param value - 字符串数组
* @return 字符串,以逗号分隔
*/
export const formatList = (value: string[] | null | undefined): string => {
if (
value === undefined ||
@@ -119,14 +162,24 @@ export const formatList = (value: string[] | null | undefined): string => {
return value.join(',');
};

// 格式化布尔值
/**
* 格式化布尔值
*
* @param value - 布尔值
* @return true 为 "是",false 为 "否"
*/
export const formatBoolean = (value: boolean): string => {
return value ? '是' : '否';
};

type FormatEnumFunc = (value: string | number) => React.ReactNode;

// 格式化枚举
/**
* 格式化枚举
*
* @param options - 枚举选项数组
* @return 一个函数,参数是枚举值,从选项数组中找到对应的项,然后返回该项的 label
*/
export const formatEnum = (
options: { value?: string | number | null; label?: React.ReactNode }[],
): FormatEnumFunc => {


+ 2
- 3
react-ui/src/utils/loading.tsx View File

@@ -28,7 +28,7 @@ export class Loading {
}
const container = document.createElement('div');
container.id = 'loading';
const rootContainer = document.body; //document.getElementsByTagName('main')[0];
const rootContainer = document.body; // document.getElementsByTagName('main')[0];
rootContainer?.appendChild(container);
const root = createRoot(container);
const global = globalConfig();
@@ -69,10 +69,9 @@ export class Loading {

static removeLoading() {
this.clearRemoveTimeout();
const rootContainer = document.body; //document.getElementsByTagName('main')[0];
const container = document.getElementById('loading');
if (container) {
rootContainer?.removeChild(container);
container.parentNode?.removeChild(container);
}
this.isShowing = false;
}


+ 15
- 0
react-ui/src/utils/localStorage.ts View File

@@ -6,6 +6,11 @@ export default class LocalStorage {
// 记住密码
static readonly rememberPasswordKey = 'login-remember-password';

/**
* 获取 LocalStorage 值
* @param key - LocalStorage key
* @param isObject - 是不是对象
*/
static getItem(key: string, isObject: boolean = false) {
const jsonStr = localStorage.getItem(key);
if (!isObject) {
@@ -17,12 +22,22 @@ export default class LocalStorage {
return null;
}

/**
* 设置 LocalStorage 值
* @param key - LocalStorage key
* @param state - SessionStorage state
* @param isObject - 是不是对象
*/
static setItem(key: string, state?: any, isObject: boolean = false) {
if (state) {
localStorage.setItem(key, isObject ? JSON.stringify(state) : state);
}
}

/**
* 移除 LocalStorage 值
* @param key - LocalStorage key
*/
static removeItem(key: string) {
localStorage.removeItem(key);
}


+ 3
- 2
react-ui/src/utils/promise.ts View File

@@ -1,6 +1,7 @@
/**
* @param { Promise } promise
* @return { Promise }
* 封装 Promise,不会抛异常,resolve 的时候返回 [data, null], reject 的时候返回 [null, error]
* @param promise
* @return resolve 的时候返回 [data, null], reject 的时候返回 [null, error]
*/
export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [null, U]> {
try {


+ 21
- 4
react-ui/src/utils/sessionStorage.ts View File

@@ -1,15 +1,22 @@
import { parseJsonText } from './index';

export default class SessionStorage {
// 用于新建镜像
/** 用于新建镜像 */
static readonly mirrorNameKey = 'mirror-name';
// 模型部署服务版本
/** 模型部署服务版本 */
static readonly serviceVersionInfoKey = 'service-version-info';
// 编辑器 url
/** 编辑器 url */
static readonly editorUrlKey = 'editor-url';
// 客户端信息
/** 客户端信息 */
static readonly clientInfoKey = 'client-info';
/** aim url */
static readonly aimUrlKey = 'aim-url';

/**
* 获取 SessionStorage 值
* @param key - SessionStorage key
* @param isObject - 是不是对象
*/
static getItem(key: string, isObject: boolean = false) {
const jsonStr = sessionStorage.getItem(key);
if (!isObject) {
@@ -21,12 +28,22 @@ export default class SessionStorage {
return null;
}

/**
* 设置 SessionStorage 值
* @param key - SessionStorage key
* @param state - SessionStorage state
* @param isObject - 是不是对象
*/
static setItem(key: string, state?: any, isObject: boolean = false) {
if (state) {
sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state);
}
}

/**
* 移除 SessionStorage 值
* @param key - SessionStorage key
*/
static removeItem(key: string) {
sessionStorage.removeItem(key);
}


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

@@ -10,6 +10,10 @@ export type StatusInfo = {
color?: string;
};

/**
* 通用的 Table 状态单元格
* @param infos - 选项数组
*/
function statusTableCell(infos: StatusInfo[]) {
return function (status?: string | number | null) {
const info = infos.find((item) => item.value === status);


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

@@ -10,27 +10,44 @@ import { Tooltip, TooltipProps, Typography } from 'antd';
import dayjs from 'dayjs';

export enum TableCellValueType {
/** 序号 */
Index = 'Index',
/** 文本 */
Text = 'Text',
/** 日期 */
Date = 'Date',
/** 数组 */
Array = 'Array',
/** 链接 */
Link = 'Link',
/** 自定义 */
Custom = 'Custom',
}

export type TableCellValueOptions<T> = {
page?: number; // 类型为 Index 时有效
pageSize?: number; // 类型为 Index 时有效
property?: string; // 类型为 Array 时有效
dateFormat?: string; // 类型为 Date 时有效
onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效
format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效
copyable?: boolean; // 省略时是否可以复制
/** 页数,类型为 Index 时有效 */
page?: number;
/** 分页大小,类型为 Index 时有效 */
pageSize?: number;
/** 取数组对象的哪个属性值,类型为 Array 时有效 */
property?: string;
/** 日期格式,类型为 Date 时有效*/
dateFormat?: string;
/** 链接点击回调,类型为 Link 时有效 */
onClick?: (record: T, e: React.MouseEvent) => void;
/** 自定义函数,类型为 Custom 时有效*/
format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null;
/** 省略时是否可以复制 */
copyable?: boolean;
};

type TableCellFormatter = (value: any | undefined | null) => string | undefined | null;

// 日期转换函数
/**
* 日期转换函数
* @param {string | undefined} dateFormat - 日期格式
* @returns {TableCellFormatter} Table cell 渲染函数
*/
function formatDateText(dateFormat?: string): TableCellFormatter {
return (value: any | undefined | null): ReturnType<TableCellFormatter> => {
if (value === undefined || value === null || value === '') {
@@ -45,7 +62,7 @@ function formatDateText(dateFormat?: string): TableCellFormatter {

/**
* 数组转换函数,将数组元素转换为字符串,用逗号分隔
* @param {string} property 如果数组元素是对象,那么取数组元素的某个属性
* @param {string} property - 如果数组元素是对象,那么取数组元素的某个属性
* @returns {TableCellFormatter} Table cell 渲染函数
*/
function formatArray(property?: string): TableCellFormatter {
@@ -65,6 +82,13 @@ function formatArray(property?: string): TableCellFormatter {
};
}

/**
* Table cell render 函数
* @param ellipsis - 是否省略
* @param type - 类型
* @param options - 选项
* @returns React 节点
*/
function tableCellRender<T>(
ellipsis: boolean | TooltipProps | 'auto' = false,
type: TableCellValueType = TableCellValueType.Text,


+ 52
- 13
react-ui/src/utils/ui.tsx View File

@@ -4,9 +4,11 @@
* @Description: UI 公共方法
*/
import { PageEnum } from '@/enums/pagesEnums';
import { removeAllPageCacheState } from '@/hooks/pageCacheState';
import { removeAllPageCacheState } from '@/hooks/useCacheState';
import { deleteUploadFileReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less';
import { type ClientInfo } from '@/types';
import { type AppResponse, type ClientInfo, type UploadFileRes } from '@/types';
import { to } from '@/utils/promise';
import { history } from '@umijs/max';
import {
Modal,
@@ -25,7 +27,9 @@ type ModalConfirmProps = ModalFuncProps & {
isDelete?: boolean;
};

// 自定义删除 Confirm 弹框
/**
* 自定义 Confirm 弹框
*/
export function modalConfirm({
title,
content,
@@ -45,7 +49,7 @@ export function modalConfirm({
src={
isDelete
? require('@/assets/img/delete-icon.png')
: require('@/assets/img/comfirm-icon.png')
: require('@/assets/img/confirm-icon.png')
}
style={{ width: '120px', marginBottom: '24px' }}
draggable={false}
@@ -63,7 +67,7 @@ export function modalConfirm({

/**
* 跳转到登录页
* @param toHome 是否跳转到首页
* @param toHome - 是否跳转到首页
*/
export const gotoLoginPage = (toHome: boolean = true) => {
const { pathname, search } = location;
@@ -80,6 +84,9 @@ export const gotoLoginPage = (toHome: boolean = true) => {
}
};

/**
* 跳转到 OAuth2 登录页
*/
export const gotoOAuth2 = () => {
const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo;
if (clientInfo) {
@@ -89,7 +96,10 @@ export const gotoOAuth2 = () => {
}
};

// 从事件中获取上传文件列表,用于 Upload + Form 中
/**
* 从事件中获取上传文件列表,用于 Upload + Form 中
* @param e - 事件,包含文件列表 fileList
*/
export const getFileListFromEvent = (e: any) => {
const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || [];
return fileList.map((item) => {
@@ -137,7 +147,10 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru
return !hasError;
};

// 限制上传文件类型
/**
* 限制上传文件类型
* @param type - 只允许上次的的文件类型
*/
export const limitUploadFileType = (type: string) => {
return (file: UploadFile): boolean | string => {
const acceptTypes = type.split(',').map((item) => item.trim());
@@ -151,14 +164,40 @@ export const limitUploadFileType = (type: string) => {
};
};

/**
* 删除已上传的文件
* @param file - 已上传的文件
*/
export const removeUploadedFile = async (file: UploadFile<AppResponse<UploadFileRes[]>>) => {
const { status, response } = file;
const { code, data } = response ?? {};
if (status === 'done' && code === 200 && Array.isArray(data) && data.length > 0) {
const uploadRes = data[0];
const { fileName, url } = uploadRes;
const [res] = await to(
deleteUploadFileReq({
fileName,
url,
}),
);
if (res) {
return true;
} else {
return false;
}
}

return true;
};

/**
* 删除 FormList 表单项,如果表单项没有值,则直接删除,否则弹出确认框
* @param form From实例
* @param listName FormList 的 name
* @param name FormList 的其中一项
* @param remove FormList 的删除方法
* @param fieldNames FormList 的子项名称数组
* @param confirmTitle 弹出确认框的标题
* @param form - From实例
* @param listName - FormList 的 name
* @param name - FormList 的其中一项
* @param remove - FormList 的删除方法
* @param fieldNames - FormList 的子项名称数组
* @param confirmTitle - 弹出确认框的标题
*/
export const removeFormListItem = (
form: FormInstance,


+ 10
- 1
react-ui/typedoc.json View File

@@ -1,5 +1,5 @@
{
"entryPoints": ["./src/utils"],
"entryPoints": ["./src/utils", "./src/hooks"],
"entryPointStrategy": "expand",
"out": "docs",
"excludePrivate": true,
@@ -7,5 +7,14 @@
"excludeExternals": true,
"includeVersion": true,
"categorizeByGroup": true,
"skipErrorChecking": true,
"exclude": [
"src/utils/formRules.ts",
"src/utils/loading.tsx",
"src/utils/menuRender.tsx",
"src/utils/IconUtil.ts",
"src/utils/permission.ts",
"src/utils/tree.ts"
],
"name": "工具类文档"
}

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


+ 7
- 1
ruoyi-api/ruoyi-api-system/pom.xml View File

@@ -22,7 +22,13 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
<version>3.3.2</version>
<scope>compile</scope>
</dependency>

</dependencies>

</project>

+ 21
- 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteAuthService.java View File

@@ -0,0 +1,21 @@
package com.ruoyi.system.api;

import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.factory.RemoteAuthFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;

@FeignClient(contextId = "remoteAuthService", value = ServiceNameConstants.AUTH_SERVICE, fallbackFactory = RemoteAuthFallbackFactory.class)
public interface RemoteAuthService {

@PostMapping("/oauth2")
public AjaxResult add(@Validated @RequestBody SysUser user);

@PutMapping("/oauth2")
public AjaxResult edit(@Validated @RequestBody SysUser user);
}

+ 30
- 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteMmpService.java View File

@@ -0,0 +1,30 @@
package com.ruoyi.system.api;

import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.factory.RemoteMmpFallbackFactory;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(contextId = "remoteMmpService", value = ServiceNameConstants.MANAGEMENT_SERVICE, fallbackFactory = RemoteMmpFallbackFactory.class)
public interface RemoteMmpService {
@GetMapping("/gitLink/login")
public GenericsAjaxResult<String> gitLinkLogin(@RequestParam("username") String username, @RequestParam("password") String password);

@PostMapping("/gitLink/createGitLinkUser")
public GenericsAjaxResult<String> createGitLinkUser(@RequestBody SysUser sysUser) throws Exception;

@PostMapping("/gitLink/resetPwd")
public GenericsAjaxResult<String> resetPwd(@RequestBody SysUser sysUser) throws Exception;

@PutMapping("/gitLink/resetEmail")
public GenericsAjaxResult<String> resetEmail(@RequestBody SysUser sysUser) throws Exception;

@PutMapping("/gitLink/resetPhoneNum")
public GenericsAjaxResult<String> resetPhoneNum(@RequestBody SysUser sysUser) throws Exception;

@DeleteMapping("/gitLink/deleteGitLinkUser")
public GenericsAjaxResult<String> deleteGitLinkUser(@RequestBody SysUser sysUser) throws Exception;
}

+ 1
- 5
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java View File

@@ -1,11 +1,7 @@
package com.ruoyi.system.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;


ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/constant/Constant.java → ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/Constant.java View File

@@ -1,7 +1,6 @@
package com.ruoyi.platform.constant;
package com.ruoyi.system.api.constant;

public class Constant {

public final static int Image_Type_Pub = 1; // 公共镜像
public final static int Image_Type_Pri = 0; // 私有镜像

@@ -22,8 +21,8 @@ public class Constant {

public final static int Git_Category_Id = 39;

public final static String Topic_Dataset = "ci4s-dataset";
public final static String Topic_model = "ci4s-model";
public final static String Topic_Dataset = "dataset";
public final static String Topic_model = "model";

public final static String Item_Public = "public";

@@ -31,6 +30,8 @@ public class Constant {
public final static String Source_Hand_Export = "hand_export";
public final static String Source_Add = "add";

public final static String Building = "building";

public final static String Running = "Running";
public final static String Failed = "Failed";
public final static String Pending = "Pending";
@@ -56,4 +57,7 @@ public class Constant {
public final static String TaskType_Ray = "ray";
public final static String TaskType_ActiveLearn = "active_learn";
public final static String TaskType_Service = "service";
public final static String DelFlag = "2";

public final static String Code = "123123";
}

+ 35
- 37
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java View File

@@ -1,7 +1,11 @@
package com.ruoyi.system.api.domain;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.alibaba.excel.enums.poi.VerticalAlignmentEnum;
import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.common.core.annotation.Excel.ColumnType;
import com.ruoyi.common.core.annotation.Excel.Type;
import com.ruoyi.common.core.annotation.Excels;
import com.ruoyi.common.core.web.domain.BaseEntity;
@@ -20,49 +24,52 @@ import java.util.List;
*
* @author ruoyi
*/
@ExcelIgnoreUnannotated
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,
verticalAlignment = VerticalAlignmentEnum.CENTER)
public class SysUser extends BaseEntity {
private static final long serialVersionUID = 1L;

/**
* 用户ID
*/
@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
@ExcelProperty(value = "用户序号", order = 1)
private Long userId;

/**
* 部门ID
*/
@Excel(name = "部门编号", type = Type.IMPORT)
@ExcelProperty(value = "部门编号", order = 2)
private Long deptId;

/**
* 用户账号
*/
@Excel(name = "登录名称")
@ExcelProperty(value = "登录名称", order = 3)
private String userName;

/**
* 用户昵称
*/
@Excel(name = "用户名称")
@ExcelProperty(value = "用户名称", order = 4)
private String nickName;

/**
* 用户邮箱
*/
@Excel(name = "用户邮箱")
@ExcelProperty(value = "用户邮箱", order = 4)
private String email;

/**
* 手机号码
*/
@Excel(name = "手机号码")
@ExcelProperty(value = "手机号码", order = 5)
private String phonenumber;

/**
* 用户性别
*/
@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
@ExcelProperty(value = "用户性别", order = 6)
private String sex;

/**
@@ -75,10 +82,12 @@ public class SysUser extends BaseEntity {
*/
private String password;

private String originPassword;

/**
* 帐号状态(0正常 1停用)
*/
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
@ExcelProperty(value = "帐号状态", order = 7)
private String status;

/**
@@ -89,22 +98,24 @@ public class SysUser extends BaseEntity {
/**
* 最后登录IP
*/
@Excel(name = "最后登录IP", type = Type.EXPORT)
@ExcelProperty(value = "最后登录IP", order = 8)
private String loginIp;

/**
* 最后登录时间
*/
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
// @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
@ExcelProperty(value = "最后登录时间", order = 9)
private Date loginDate;

/**
* 部门对象
*/
@Excels({
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
// @Excels({
// @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
// @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
// })
// @ExcelProperty(value = "部门", order = 4)
private SysDept dept;

/**
@@ -127,10 +138,6 @@ public class SysUser extends BaseEntity {
*/
private Long roleId;

private String gitLinkUsername;

private String gitLinkPassword;

private Float credit;

public SysUser() {
@@ -229,6 +236,14 @@ public class SysUser extends BaseEntity {
this.password = password;
}

public String getOriginPassword() {
return originPassword;
}

public void setOriginPassword(String originPassword) {
this.originPassword = originPassword;
}

public String getStatus() {
return status;
}
@@ -301,22 +316,6 @@ public class SysUser extends BaseEntity {
this.roleId = roleId;
}

public void setGitLinkUsername(String gitLinkUsername) {
this.gitLinkUsername = gitLinkUsername;
}

public String getGitLinkUsername() {
return gitLinkUsername;
}

public void setGitLinkPassword(String gitLinkPassword) {
this.gitLinkPassword = gitLinkPassword;
}

public String getGitLinkPassword() {
return gitLinkPassword;
}

public void setCredit(Float credit) {
this.credit = credit;
}
@@ -337,6 +336,7 @@ public class SysUser extends BaseEntity {
.append("sex", getSex())
.append("avatar", getAvatar())
.append("password", getPassword())
.append("originPassword", getOriginPassword())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())
@@ -347,8 +347,6 @@ public class SysUser extends BaseEntity {
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("dept", getDept())
.append("gitLinkUsername", getGitLinkUsername())
.append("gitLinkPassword", getGitLinkPassword())
.append("credit", getCredit())
.toString();
}


+ 30
- 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteAuthFallbackFactory.java View File

@@ -0,0 +1,30 @@
package com.ruoyi.system.api.factory;

import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.system.api.RemoteAuthService;
import com.ruoyi.system.api.domain.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class RemoteAuthFallbackFactory implements FallbackFactory<RemoteAuthService> {
private static final Logger log = LoggerFactory.getLogger(RemoteAuthFallbackFactory.class);

@Override
public RemoteAuthService create(Throwable throwable) {
log.error("Auth服务调用失败:{}", throwable.getMessage());
return new RemoteAuthService() {
@Override
public AjaxResult add(SysUser user) {
return AjaxResult.error("新增Oauth2用户失败");
}

@Override
public AjaxResult edit(SysUser user) {
return AjaxResult.error("更新Oauth2用户失败");
}
};
}
}

+ 51
- 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteMmpFallbackFactory.java View File

@@ -0,0 +1,51 @@
package com.ruoyi.system.api.factory;

import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.system.api.RemoteMmpService;
import com.ruoyi.system.api.domain.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class RemoteMmpFallbackFactory implements FallbackFactory<RemoteMmpService> {

private static final Logger log = LoggerFactory.getLogger(RemoteMmpFallbackFactory.class);

@Override
public RemoteMmpService create(Throwable throwable) {
log.error("管理平台服务调用失败:{}", throwable.getMessage());
return new RemoteMmpService() {
@Override
public GenericsAjaxResult<String> gitLinkLogin(String username, String password) {
return GenericsAjaxResult.error("刷新gitLink登录信息失败");
}

@Override
public GenericsAjaxResult<String> createGitLinkUser(SysUser sysUser) throws Exception {
throw new Exception("新增gitLink用户失败:" + throwable.getMessage());
}

@Override
public GenericsAjaxResult<String> resetPwd(SysUser sysUser) throws Exception {
throw new Exception("修改gitLink用户密码失败:" + throwable.getMessage());
}

@Override
public GenericsAjaxResult<String> resetEmail(SysUser sysUser) throws Exception {
throw new Exception("修改gitLink用户邮箱失败:" + throwable.getMessage());
}

@Override
public GenericsAjaxResult<String> resetPhoneNum(SysUser sysUser) throws Exception {
throw new Exception("修改gitLink用户手机号失败:" + throwable.getMessage());
}

@Override
public GenericsAjaxResult<String> deleteGitLinkUser(SysUser sysUser) throws Exception {
throw new Exception("删除gitLink用户失败:" + throwable.getMessage());
}
};
}
}

+ 2
- 0
ruoyi-api/ruoyi-api-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports View File

@@ -1,3 +1,5 @@
com.ruoyi.system.api.factory.RemoteUserFallbackFactory
com.ruoyi.system.api.factory.RemoteLogFallbackFactory
com.ruoyi.system.api.factory.RemoteFileFallbackFactory
com.ruoyi.system.api.factory.RemoteMmpFallbackFactory
com.ruoyi.system.api.factory.RemoteAuthFallbackFactory

+ 25
- 2
ruoyi-auth/pom.xml View File

@@ -51,9 +51,32 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>

<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>

<!-- RuoYi Common DataSource -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId>
</dependency>

<!-- RuoYi Common DataScope -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
</dependency>

<!-- RuoYi Common Log -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>


+ 2
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/RuoYiAuthApplication.java View File

@@ -1,5 +1,6 @@
package com.ruoyi.auth;

import com.ruoyi.common.security.annotation.EnableCustomConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -10,6 +11,7 @@ import com.ruoyi.common.security.annotation.EnableRyFeignClients;
*
* @author ruoyi
*/
@EnableCustomConfig
@EnableRyFeignClients
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYiAuthApplication


+ 26
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/controller/Oauth2Controller.java View File

@@ -0,0 +1,26 @@
package com.ruoyi.auth.controller;

import com.ruoyi.auth.service.Oauth2Service;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.system.api.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/oauth2")
public class Oauth2Controller extends BaseController {
@Autowired
private Oauth2Service oauth2Service;

@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user) {
return toAjax(oauth2Service.insertOauth2User(user));
}

@PutMapping
public AjaxResult edit(@Validated @RequestBody SysUser user) {
return toAjax(oauth2Service.updateOauth2User(user));
}
}

+ 37
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/domain/OauthAccount.java View File

@@ -0,0 +1,37 @@
package com.ruoyi.auth.domain;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

import java.util.Date;

@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class OauthAccount {
private Long id;

private String clientId;

private String username;

private String password;

private String mobile;

private String email;

private Integer enabled;

private Integer accountNonExpired;

private Integer credentialsNonExpired;

private Integer accountNonLocked;

private Integer accountNonDeleted;

private Date createdTime;

private Date updatedTime;
}

+ 10
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/mapper/Oauth2Mapper.java View File

@@ -0,0 +1,10 @@
package com.ruoyi.auth.mapper;

import com.ruoyi.auth.domain.OauthAccount;
import org.apache.ibatis.annotations.Param;

public interface Oauth2Mapper {
int insertOauth2User(@Param("oauthAccount")OauthAccount oauthAccount);

int updateOauth2User(@Param("oauthAccount")OauthAccount oauthAccount);
}

+ 45
- 0
ruoyi-auth/src/main/java/com/ruoyi/auth/service/Oauth2Service.java View File

@@ -0,0 +1,45 @@
package com.ruoyi.auth.service;

import com.ruoyi.auth.domain.OauthAccount;
import com.ruoyi.auth.mapper.Oauth2Mapper;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.api.constant.Constant;
import com.ruoyi.system.api.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Oauth2Service {
@Value("${oauth2.client-id}")
String clientId;

@Autowired
private Oauth2Mapper oauth2Mapper;

public int insertOauth2User(SysUser user) {
OauthAccount oauthAccount = new OauthAccount();
oauthAccount.setClientId(clientId);
oauthAccount.setUsername(user.getUserName());
oauthAccount.setPassword(user.getPassword());
oauthAccount.setMobile(user.getPhonenumber());
oauthAccount.setEmail(user.getEmail());
oauthAccount.setEnabled(Constant.State_valid);
return oauth2Mapper.insertOauth2User(oauthAccount);
}

public int updateOauth2User(SysUser user) {
OauthAccount oauthAccount = new OauthAccount();
oauthAccount.setClientId(clientId);
oauthAccount.setUsername(user.getUserName());
if (StringUtils.isNotEmpty(user.getPassword())) {
oauthAccount.setPassword(user.getPassword());
}
oauthAccount.setMobile(user.getPhonenumber());
oauthAccount.setEmail(user.getEmail());
if (Constant.DelFlag.equals(user.getDelFlag())) {
oauthAccount.setEnabled(Constant.State_invalid);
}
return oauth2Mapper.updateOauth2User(oauthAccount);
}
}

+ 41
- 66
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java View File

@@ -1,8 +1,6 @@
package com.ruoyi.auth.service;

import com.ruoyi.auth.form.AccessTokenVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.SecurityConstants;
@@ -15,18 +13,20 @@ import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.RemoteMmpService;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* 登录校验方法
*
*
* @author ruoyi
*/
@Component
public class SysLoginService
{
public class SysLoginService {
@Autowired
private RemoteUserService remoteUserService;

@@ -39,92 +39,82 @@ public class SysLoginService
@Autowired
private RedisService redisService;

@Autowired
private RemoteMmpService remoteMmpService;

/**
* 登录
*/
public LoginUser login(String username, String password)
{
public LoginUser login(String username, String password) {
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
if (StringUtils.isAnyBlank(username, password)) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new ServiceException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new ServiceException("用户密码不在指定范围");
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);

if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}

if (R.FAIL == userResult.getCode())
{
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
passwordService.validate(user, password);
remoteMmpService.gitLinkLogin(username, user.getOriginPassword());
recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
return userInfo;
}

public void logout(String loginName)
{
public void logout(String loginName) {
recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
}

/**
* 注册
*/
public void register(String username, String password)
{
public void register(String username, String password) {
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
if (StringUtils.isAnyBlank(username, password)) {
throw new ServiceException("用户/密码必须填写");
}
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
throw new ServiceException("账户长度必须在2到20个字符之间");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
throw new ServiceException("密码长度必须在5到20个字符之间");
}

@@ -135,8 +125,7 @@ public class SysLoginService
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER);

if (R.FAIL == registerResult.getCode())
{
if (R.FAIL == registerResult.getCode()) {
throw new ServiceException(registerResult.getMsg());
}
recordLogService.recordLogininfor(username, Constants.REGISTER, "注册成功");
@@ -144,52 +133,45 @@ public class SysLoginService

public LoginUser loginByKey(String username, String key) {
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, key))
{
if (StringUtils.isAnyBlank(username, key)) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/key必须填写");
throw new ServiceException("用户/key必须填写");
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);

if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}

if (R.FAIL == userResult.getCode())
{
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}

LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
if (!StringUtils.equals(key,"h1n2x3j4y5@")){
if (!StringUtils.equals(key, "h1n2x3j4y5@")) {
throw new ServiceException("对不起,您的key不正确");
}
return userInfo;
@@ -200,51 +182,44 @@ public class SysLoginService
String username = accountInfo.getUsername();

// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username))
{
if (StringUtils.isAnyBlank(username)) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new ServiceException("用户/密码必须填写");
}

// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);

if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
// register(username, "123456");
// register(username, "123456");
// userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
}

if (R.FAIL == userResult.getCode())
{
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}

LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}


+ 31
- 0
ruoyi-auth/src/main/resources/mapper/auth/Oauth2Mapper.xml View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.auth.mapper.Oauth2Mapper">
<insert id="insertOauth2User" useGeneratedKeys="true" keyProperty="id">
insert into oauth_account(client_id, username, password, mobile, email, enabled)
values (#{oauthAccount.clientId}, #{oauthAccount.username}, #{oauthAccount.password}, #{oauthAccount.mobile},
#{oauthAccount.email}, #{oauthAccount.enabled})
</insert>

<update id="updateOauth2User">
update oauth_account
<set>
<if test="oauthAccount.username != null and oauthAccount.username !=''">
username = #{oauthAccount.username},
</if>
<if test="oauthAccount.password != null and oauthAccount.password !=''">
password = #{oauthAccount.password},
</if>
<if test="oauthAccount.mobile != null and oauthAccount.mobile !=''">
mobile = #{oauthAccount.mobile},
</if>
<if test="oauthAccount.email != null and oauthAccount.email !=''">
email = #{oauthAccount.email},
</if>
<if test="oauthAccount.enabled != null">
enabled = #{oauthAccount.enabled},
</if>
</set>
where username = #{oauthAccount.username} and enabled = 1
</update>
</mapper>

+ 2
- 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/ServiceNameConstants.java View File

@@ -21,4 +21,6 @@ public class ServiceNameConstants
* 文件服务的serviceid
*/
public static final String FILE_SERVICE = "ruoyi-file";

public static final String MANAGEMENT_SERVICE = "management-platform";
}

+ 18
- 18
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/poi/ExcelUtil.java View File

@@ -257,7 +257,7 @@ public class ExcelUtil<T>

/**
* 对excel表单默认第一个索引名转换成list
*
*
* @param is 输入流
* @return 转换后集合
*/
@@ -282,7 +282,7 @@ public class ExcelUtil<T>

/**
* 对excel表单默认第一个索引名转换成list
*
*
* @param is 输入流
* @param titleNum 标题占用行数
* @return 转换后集合
@@ -294,7 +294,7 @@ public class ExcelUtil<T>

/**
* 对excel表单指定表格索引名转换成list
*
*
* @param sheetName 表格索引名
* @param titleNum 标题占用行数
* @param is 输入流
@@ -503,7 +503,7 @@ public class ExcelUtil<T>

/**
* 对list数据源将其里面的数据导入到excel表单
*
*
* @return 结果
*/
public void exportExcel(HttpServletResponse response)
@@ -565,7 +565,7 @@ public class ExcelUtil<T>

/**
* 填充excel数据
*
*
* @param index 序号
* @param row 单元格行
*/
@@ -636,7 +636,7 @@ public class ExcelUtil<T>

/**
* 创建表格样式
*
*
* @param wb 工作薄对象
* @return 样式列表
*/
@@ -689,7 +689,7 @@ public class ExcelUtil<T>

/**
* 根据Excel注解创建表格头样式
*
*
* @param wb 工作薄对象
* @return 自定义样式列表
*/
@@ -722,7 +722,7 @@ public class ExcelUtil<T>

/**
* 根据Excel注解创建表格列样式
*
*
* @param wb 工作薄对象
* @return 自定义样式列表
*/
@@ -784,7 +784,7 @@ public class ExcelUtil<T>

/**
* 设置单元格信息
*
*
* @param value 单元格值
* @param attr 注解相关
* @param cell 单元格信息
@@ -943,7 +943,7 @@ public class ExcelUtil<T>

/**
* 设置 POI XSSFSheet 单元格提示或选择框
*
*
* @param sheet 表单
* @param textlist 下拉框显示的内容
* @param promptContent 提示内容
@@ -953,7 +953,7 @@ public class ExcelUtil<T>
* @param endCol 结束列
*/
public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow,
int firstCol, int endCol)
int firstCol, int endCol)
{
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1");
@@ -980,7 +980,7 @@ public class ExcelUtil<T>

/**
* 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).
*
*
* @param sheet 要设置的sheet.
* @param textlist 下拉框显示的内容
* @param promptContent 提示内容
@@ -1069,7 +1069,7 @@ public class ExcelUtil<T>

/**
* 反向解析值 男=0,女=1,未知=2
*
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
@@ -1311,7 +1311,7 @@ public class ExcelUtil<T>

/**
* 创建工作表
*
*
* @param sheetNo sheet数量
* @param index 序号
*/
@@ -1328,7 +1328,7 @@ public class ExcelUtil<T>

/**
* 获取单元格值
*
*
* @param row 获取的行
* @param column 获取单元格列号
* @return 单元格值
@@ -1388,7 +1388,7 @@ public class ExcelUtil<T>

/**
* 判断是否是空行
*
*
* @param row 判断的行
* @return
*/
@@ -1411,7 +1411,7 @@ public class ExcelUtil<T>

/**
* 格式化不同类型的日期对象
*
*
* @param dateFormat 日期格式
* @param val 被格式化的日期对象
* @return 格式化后的日期字符
@@ -1477,7 +1477,7 @@ public class ExcelUtil<T>

/**
* 获取对象的子列表方法
*
*
* @param name 名称
* @param pojoClass 类对象
* @return 子列表方法


+ 14
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java View File

@@ -3,6 +3,7 @@ package com.ruoyi.platform.controller.dataset;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.platform.domain.Dataset;
import com.ruoyi.platform.service.NewDatasetService;
import com.ruoyi.platform.utils.DVCUtils;
import com.ruoyi.platform.vo.LabelDatasetVersionVo;
import com.ruoyi.platform.vo.NewDatasetVo;
import com.ruoyi.platform.vo.QueryModelMetricsVo;
@@ -15,6 +16,8 @@ import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -29,6 +32,8 @@ public class NewDatasetFromGitController {
@Resource
private NewDatasetService newDatasetService;

@Resource
private DVCUtils dvcUtils;

/**
* 新增数据集与版本新
@@ -172,7 +177,15 @@ public class NewDatasetFromGitController {

@PostMapping("/getVersionsCompare")
@ApiOperation(value = "获取数据集版本对比")
public AjaxResult getVersionsCompare(@RequestBody QueryModelMetricsVo querydatasetVo) throws Exception{
public AjaxResult getVersionsCompare(@RequestBody QueryModelMetricsVo querydatasetVo) throws Exception {
return AjaxResult.success(this.newDatasetService.getVersionsCompare(querydatasetVo));
}

@DeleteMapping("/deleteFile")
@ApiOperation(value = "删除文件")
public AjaxResult deleteFile(@RequestParam("url") String url, @RequestParam("fileName") String fileName) {
Path file = Paths.get(url, System.getProperty("file.separator"), fileName);
dvcUtils.deletePath(file);
return AjaxResult.success("删除成功");
}
}

+ 61
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/git/GitLinkController.java View File

@@ -0,0 +1,61 @@
package com.ruoyi.platform.controller.git;

import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.platform.service.GitService;
import com.ruoyi.system.api.domain.SysUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("gitLink")
@Api("gitLink")
public class GitLinkController extends BaseController {

@Resource
private GitService gitService;

@GetMapping("/login")
@ApiOperation("刷新giotLink用户信息")
public GenericsAjaxResult<String> gitLinkLogin(@RequestParam("username") String username, @RequestParam("password") String password) {
return genericsSuccess(gitService.login(username, password));
}

@PostMapping("/createGitLinkUser")
@ApiOperation("新增gitLink用户")
public GenericsAjaxResult<String> createGitLinkUser(@RequestBody SysUser sysUser) throws Exception {
gitService.createUser(sysUser);
return GenericsAjaxResult.success("新增成功");
}

@PostMapping("/resetPwd")
@ApiOperation("更改gitLink用户密码")
public GenericsAjaxResult<String> resetPwd(@RequestBody SysUser sysUser) throws Exception {
gitService.resetPwd(sysUser);
return GenericsAjaxResult.success("修改成功");
}

@PutMapping("/resetEmail")
@ApiOperation("更改gitLink用户邮箱")
public GenericsAjaxResult<String> resetEmail(@RequestBody SysUser sysUser) throws Exception {
gitService.resetEmail(sysUser);
return GenericsAjaxResult.success("修改成功");
}

@PutMapping("/resetPhoneNum")
@ApiOperation("更改gitLink用户手机号")
public GenericsAjaxResult<String> resetPhoneNum(@RequestBody SysUser sysUser) throws Exception {
gitService.resetPhoneNum(sysUser);
return GenericsAjaxResult.success("修改成功");
}

@DeleteMapping("/deleteGitLinkUser")
@ApiOperation("删除gitLink用户")
public GenericsAjaxResult<String> deleteGitLinkUser(@RequestBody SysUser sysUser) throws Exception {
gitService.deleteUser(sysUser);
return GenericsAjaxResult.success("删除成功");
}
}

+ 1
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/image/ImageController.java View File

@@ -92,7 +92,7 @@ public class ImageController extends BaseController {
@PostMapping("/addImageAndVersion")
@ApiOperation("添加镜像和版本")
public GenericsAjaxResult<String> addImageAndVersion(@RequestBody ImageVo imageVo) throws Exception {
return genericsSuccess(this.imageService.insertImageAndVersion(imageVo));
return this.imageService.insertImageAndVersion(imageVo);

}



+ 1
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/image/ImageVersionController.java View File

@@ -90,7 +90,7 @@ public class ImageVersionController extends BaseController {
*/
@DeleteMapping("{id}")
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.imageVersionService.removeById(id));
return this.imageVersionService.removeById(id);
}

}


+ 13
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java View File

@@ -134,7 +134,19 @@ public class NewModelFromGitController {

@PostMapping("/getVersionsCompare")
@ApiOperation(value = "获取模型版本对比")
public AjaxResult getVersionsCompare(@RequestBody QueryModelMetricsVo queryModelMetricsVo) throws Exception{
public AjaxResult getVersionsCompare(@RequestBody QueryModelMetricsVo queryModelMetricsVo) throws Exception {
return AjaxResult.success(this.modelsService.getVersionsCompare(queryModelMetricsVo));
}

@PostMapping("/praise/{id}")
@ApiOperation(value = "点赞一个项目")
public AjaxResult praise(@PathVariable("id") Integer id) throws Exception {
return AjaxResult.success(this.modelsService.praise(id));
}

@DeleteMapping("/unpraise/{id}")
@ApiOperation(value = "取消点赞一个项目")
public AjaxResult unpraise(@PathVariable("id") Integer id) throws Exception {
return AjaxResult.success(this.modelsService.unpraise(id));
}
}

+ 1
- 1
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java View File

@@ -1,6 +1,6 @@
package com.ruoyi.platform.scheduling;

import com.ruoyi.platform.constant.Constant;
import com.ruoyi.system.api.constant.Constant;
import com.ruoyi.platform.domain.AutoMl;
import com.ruoyi.platform.domain.AutoMlIns;
import com.ruoyi.platform.mapper.AutoMlDao;


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save