| @@ -0,0 +1 @@ | |||||
| save-prefix=~ | |||||
| @@ -60,7 +60,7 @@ | |||||
| "@antv/hierarchy": "^0.6.12", | "@antv/hierarchy": "^0.6.12", | ||||
| "@types/crypto-js": "^4.2.2", | "@types/crypto-js": "^4.2.2", | ||||
| "@umijs/route-utils": "^4.0.1", | "@umijs/route-utils": "^4.0.1", | ||||
| "antd": "^5.4.4", | |||||
| "antd": "~5.21.4", | |||||
| "classnames": "^2.3.2", | "classnames": "^2.3.2", | ||||
| "crypto-js": "^4.2.0", | "crypto-js": "^4.2.0", | ||||
| "echarts": "^5.5.0", | "echarts": "^5.5.0", | ||||
| @@ -111,7 +111,7 @@ | |||||
| "umi-presets-pro": "^2.0.0" | "umi-presets-pro": "^2.0.0" | ||||
| }, | }, | ||||
| "engines": { | "engines": { | ||||
| "node": ">=12.0.0" | |||||
| "node": ">=16.14.0" | |||||
| }, | }, | ||||
| "create-umi": { | "create-umi": { | ||||
| "ignoreScript": [ | "ignoreScript": [ | ||||
| @@ -38,13 +38,8 @@ | |||||
| margin-left: 16px; | margin-left: 16px; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| line-height: 1.6; | line-height: 1.6; | ||||
| white-space: pre-line; | |||||
| word-break: break-all; | word-break: break-all; | ||||
| &--ellipsis { | |||||
| .singleLine(); | |||||
| } | |||||
| &__text { | &__text { | ||||
| color: @text-color; | color: @text-color; | ||||
| } | } | ||||
| @@ -120,13 +120,10 @@ export function BasicInfoItemValue({ | |||||
| } | } | ||||
| return ( | return ( | ||||
| <Typography.Text | |||||
| className={classNames(myClassName, { | |||||
| [`${myClassName}--ellipsis`]: ellipsis, | |||||
| })} | |||||
| ellipsis={{ tooltip: value }} | |||||
| > | |||||
| {component} | |||||
| </Typography.Text> | |||||
| <div className={myClassName}> | |||||
| <Typography.Text ellipsis={ellipsis ? { tooltip: value } : false}> | |||||
| {component} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -33,10 +33,10 @@ | |||||
| &__value { | &__value { | ||||
| flex: 1; | flex: 1; | ||||
| min-width: 0; | |||||
| margin: 0 !important; | margin: 0 !important; | ||||
| padding: 12px 20px 4px; | padding: 12px 20px 4px; | ||||
| font-size: @font-size; | font-size: @font-size; | ||||
| white-space: pre-line; | |||||
| word-break: break-all; | word-break: break-all; | ||||
| & + & { | & + & { | ||||
| @@ -47,10 +47,6 @@ | |||||
| padding-bottom: 12px; | padding-bottom: 12px; | ||||
| } | } | ||||
| &--ellipsis { | |||||
| .singleLine(); | |||||
| } | |||||
| &__text { | &__text { | ||||
| color: @text-color; | color: @text-color; | ||||
| } | } | ||||
| @@ -20,7 +20,7 @@ | |||||
| height: 40px; | height: 40px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| border-radius: 10px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| border-color: transparent; | border-color: transparent; | ||||
| @@ -1,3 +1,4 @@ | |||||
| export enum PageEnum { | export enum PageEnum { | ||||
| LOGIN = '/user/login', | LOGIN = '/user/login', | ||||
| Authorize = '/authorize', | |||||
| } | } | ||||
| @@ -162,7 +162,7 @@ export const useCheck = <T>(list: T[]) => { | |||||
| const [selected, setSelected] = useState<T[]>([]); | const [selected, setSelected] = useState<T[]>([]); | ||||
| const checked = useMemo(() => { | const checked = useMemo(() => { | ||||
| return selected.length === list.length; | |||||
| return selected.length === list.length && selected.length > 0; | |||||
| }, [selected, list]); | }, [selected, list]); | ||||
| const indeterminate = useMemo(() => { | const indeterminate = useMemo(() => { | ||||
| @@ -168,7 +168,7 @@ | |||||
| height: 40px; | height: 40px; | ||||
| padding: 0 30px; | padding: 0 30px; | ||||
| font-size: @font-size-content; | font-size: @font-size-content; | ||||
| border-radius: 10px; | |||||
| border-radius: 6px; | |||||
| } | } | ||||
| .ant-btn-default { | .ant-btn-default { | ||||
| border-color: transparent; | border-color: transparent; | ||||
| @@ -1,47 +1,46 @@ | |||||
| .code-config-list { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| .code-config { | |||||
| height: 100%; | height: 100%; | ||||
| height: 100%; | |||||
| padding: 20px 0; | |||||
| background: white; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| &__header { | |||||
| &__list { | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 32px; | |||||
| margin-bottom: 30px; | |||||
| padding: 0 30px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| flex-direction: column; | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 0; | |||||
| background: white; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__content { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| margin-bottom: 30px; | |||||
| padding: 0 30px; | |||||
| overflow-y: auto; | |||||
| } | |||||
| &__header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 32px; | |||||
| color: @text-color; | |||||
| font-size: 15px; | |||||
| } | |||||
| &__empty { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| } | |||||
| &__content { | |||||
| display: flex; | |||||
| flex: 1 1 0%; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px; | |||||
| align-content: flex-start; | |||||
| width: 100%; | |||||
| margin: 25px 0; | |||||
| overflow-y: auto; | |||||
| } | |||||
| &__empty { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| } | |||||
| :global { | |||||
| .ant-pagination { | |||||
| margin-right: 30px; | |||||
| text-align: right; | |||||
| :global { | |||||
| .ant-pagination { | |||||
| margin-bottom: 25px; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,12 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 09:55:12 | |||||
| * @Description: 代码配置 | |||||
| */ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | ||||
| import { getGitUrl } from '@/utils'; | import { getGitUrl } from '@/utils'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| @@ -127,64 +134,69 @@ function CodeConfigList() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles['code-config-list']}> | |||||
| <div className={styles['code-config-list__header']}> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | |||||
| <div className={styles['code-config']}> | |||||
| <PageTitle title="代码配置"></PageTitle> | |||||
| <div className={styles['code-config__list']}> | |||||
| <div className={styles['code-config__list__header']}> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <Input.Search | <Input.Search | ||||
| placeholder="按代码仓库名称筛选" | placeholder="按代码仓库名称筛选" | ||||
| allowClear | allowClear | ||||
| onSearch={handleSearch} | onSearch={handleSearch} | ||||
| style={{ | style={{ | ||||
| width: 300, | width: 300, | ||||
| marginRight: '20px', | |||||
| marginLeft: 'auto', | |||||
| }} | }} | ||||
| onChange={(e) => setInputText(e.target.value)} | onChange={(e) => setInputText(e.target.value)} | ||||
| value={inputText} | value={inputText} | ||||
| /> | /> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| style={{ marginLeft: '20px' }} | |||||
| style={{ marginRight: 0 }} | |||||
| onClick={createCodeConfig} | onClick={createCodeConfig} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新建代码配置 | 新建代码配置 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| </div> | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | |||||
| <div className={styles['code-config-list__content']}> | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| onRemove={handleRemove} | |||||
| onEdit={handleEdit} | |||||
| onClick={handleClick} | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | |||||
| <div className={styles['code-config__list__content']}> | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| onRemove={handleRemove} | |||||
| onEdit={handleEdit} | |||||
| onClick={handleClick} | |||||
| /> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| align="end" | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config__list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | /> | ||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | |||||
| )} | |||||
| </div> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -2,51 +2,97 @@ | |||||
| position: relative; | position: relative; | ||||
| width: calc(25% - 15px); | width: calc(25% - 15px); | ||||
| padding: 20px; | padding: 20px; | ||||
| background: white; | |||||
| border: 1px solid #eaeaea; | |||||
| background: linear-gradient(180deg, #f7faff 0%, #ffffff 100%); | |||||
| border: 2px solid white; | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| box-shadow: 0px 3px 10px rgba(164, 169, 181, 0.13); | |||||
| cursor: pointer; | cursor: pointer; | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| @media screen and (max-width: 1860px) { | @media screen and (max-width: 1860px) { | ||||
| & { | & { | ||||
| width: calc(33.33% - 13.33px); | width: calc(33.33% - 13.33px); | ||||
| } | } | ||||
| } | } | ||||
| &__name { | |||||
| &__icon { | |||||
| flex: none; | |||||
| width: 16px; | |||||
| height: 16px; | |||||
| margin-right: 10px; | margin-right: 10px; | ||||
| } | |||||
| &__name { | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| margin-bottom: 0 !important; | margin-bottom: 0 !important; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 500; | |||||
| font-size: 16px; | font-size: 16px; | ||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.4) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | } | ||||
| &__tag { | &__tag { | ||||
| padding: 2px 11px; | |||||
| font-size: 12px; | |||||
| border-radius: 1000px; | |||||
| flex: none; | |||||
| padding: 1px 10px; | |||||
| font-size: 13px; | |||||
| border-radius: 2px; | |||||
| &--public { | &--public { | ||||
| color: @primary-color; | color: @primary-color; | ||||
| background-color: .addAlpha(@primary-color, 0.08) []; | |||||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.5) []; | |||||
| } | } | ||||
| &--private { | &--private { | ||||
| color: @warning-color; | color: @warning-color; | ||||
| background-color: .addAlpha(@warning-color, 0.08) []; | |||||
| border-color: .addAlpha(@warning-color, 0.5) []; | |||||
| background-color: .addAlpha(@warning-color, 0.1) []; | |||||
| border: 1px solid .addAlpha(@warning-color, 0.5) []; | |||||
| } | } | ||||
| } | } | ||||
| :global { | |||||
| .ant-btn { | |||||
| flex: none; | |||||
| color: #808080; | |||||
| } | |||||
| } | |||||
| &__url-box { | |||||
| margin-bottom: 15px; | |||||
| padding: 14px; | |||||
| background-color: .addAlpha(@primary-color, 0.04) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| &__url { | &__url { | ||||
| margin-bottom: 10px !important; | |||||
| color: @text-color-secondary; | |||||
| margin-bottom: 15px !important; | |||||
| color: @text-color; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| } | } | ||||
| &__branch { | &__branch { | ||||
| margin-bottom: 20px; | |||||
| color: @text-color-tertiary; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| } | } | ||||
| @@ -59,13 +105,4 @@ | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | } | ||||
| @@ -19,6 +19,11 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | <div className={styles['code-config-item']} onClick={() => onClick?.(item)}> | ||||
| <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | <Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}> | ||||
| <img | |||||
| className={styles['code-config-item__icon']} | |||||
| src={require('@/assets/img/code-name-icon.png')} | |||||
| alt="" | |||||
| /> | |||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__name']} | className={styles['code-config-item__name']} | ||||
| ellipsis={{ tooltip: item.code_repo_name }} | ellipsis={{ tooltip: item.code_repo_name }} | ||||
| @@ -58,17 +63,19 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| <KFIcon type="icon-shanchu" font={17} /> | <KFIcon type="icon-shanchu" font={17} /> | ||||
| </Button> | </Button> | ||||
| </Flex> | </Flex> | ||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <div className={styles['code-config-item__url-box']}> | |||||
| <Typography.Paragraph | |||||
| className={styles['code-config-item__url']} | |||||
| ellipsis={{ tooltip: item.git_url }} | |||||
| > | |||||
| {item.git_url} | |||||
| </Typography.Paragraph> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| </div> | |||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__user']}> | <div className={styles['code-config-item__user']}> | ||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| style={{ width: '16px', marginRight: '6px' }} | |||||
| src={creatByImg} | src={creatByImg} | ||||
| alt="" | alt="" | ||||
| draggable={false} | draggable={false} | ||||
| @@ -13,13 +13,37 @@ | |||||
| } | } | ||||
| } | } | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| } | |||||
| &__name { | &__name { | ||||
| position: relative; | position: relative; | ||||
| display: inline-block; | display: inline-block; | ||||
| height: 24px; | height: 24px; | ||||
| margin: 0 10px 0 0 !important; | margin: 0 10px 0 0 !important; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 500; | |||||
| font-size: 16px; | font-size: 16px; | ||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient( | |||||
| to right, | |||||
| .addAlpha(@primary-color, 0.3) [] 0, | |||||
| .addAlpha(@primary-color, 0) [] 100% | |||||
| ); | |||||
| content: ''; | |||||
| } | |||||
| } | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | } | ||||
| &__description { | &__description { | ||||
| @@ -37,25 +61,4 @@ | |||||
| color: #808080; | color: #808080; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| } | } | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | |||||
| .resource-item__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| .resource-item__name { | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%); | |||||
| content: ''; | |||||
| } | |||||
| } | } | ||||
| @@ -33,7 +33,6 @@ | |||||
| :global { | :global { | ||||
| .ant-pagination { | .ant-pagination { | ||||
| margin-right: 30px; | margin-right: 30px; | ||||
| text-align: right; | |||||
| } | } | ||||
| } | } | ||||
| @@ -204,6 +204,7 @@ function ResourceList( | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="end" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | defaultPageSize={20} | ||||
| @@ -152,7 +152,12 @@ function EditorList() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<EditorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -18,6 +18,26 @@ | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| &__footer { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| padding-top: 20px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 12px; | |||||
| background-color: white; | |||||
| div { | |||||
| flex: 1; | |||||
| height: 1px; | |||||
| background-color: @border-color-base; | |||||
| } | |||||
| p { | |||||
| flex: none; | |||||
| margin: 0 8px; | |||||
| } | |||||
| } | |||||
| :global { | :global { | ||||
| .ant-table-container { | .ant-table-container { | ||||
| border: none !important; | border: none !important; | ||||
| @@ -34,6 +54,13 @@ | |||||
| border-left: none !important; | border-left: none !important; | ||||
| } | } | ||||
| } | } | ||||
| .ant-table-tbody-virtual::after { | |||||
| border-bottom: none !important; | |||||
| } | |||||
| .ant-table-footer { | |||||
| padding: 0; | |||||
| border: none !important; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,13 +1,19 @@ | |||||
| // import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 09:55:12 | |||||
| * @Description: 实验对比 | |||||
| */ | |||||
| import { | import { | ||||
| getExpEvaluateInfosReq, | getExpEvaluateInfosReq, | ||||
| getExpMetricsReq, | getExpMetricsReq, | ||||
| getExpTrainInfosReq, | getExpTrainInfosReq, | ||||
| } from '@/services/experiment'; | } from '@/services/experiment'; | ||||
| import { tableSorter } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| import { useSearchParams } from '@umijs/max'; | import { useSearchParams } from '@umijs/max'; | ||||
| import { App, Button, Table, /* TablePaginationConfig,*/ TableProps, Tooltip } from 'antd'; | |||||
| import { App, Button, Table, TablePaginationConfig, TableProps, Tooltip } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useMemo, useState } from 'react'; | import { useEffect, useMemo, useState } from 'react'; | ||||
| import ExperimentStatusCell from '../components/ExperimentStatusCell'; | import ExperimentStatusCell from '../components/ExperimentStatusCell'; | ||||
| @@ -20,10 +26,10 @@ type TableData = { | |||||
| dataset: string[]; | dataset: string[]; | ||||
| start_time: string; | start_time: string; | ||||
| status: string; | status: string; | ||||
| metrics_names: string[]; | |||||
| metrics: Record<string, number>; | |||||
| params_names: string[]; | |||||
| params: Record<string, number>; | |||||
| metrics_names?: string[]; | |||||
| metrics?: Record<string, number>; | |||||
| params_names?: string[]; | |||||
| params?: Record<string, number>; | |||||
| }; | }; | ||||
| function ExperimentComparison() { | function ExperimentComparison() { | ||||
| @@ -31,18 +37,15 @@ function ExperimentComparison() { | |||||
| const comparisonType = searchParams.get('type') as ComparisonType; | const comparisonType = searchParams.get('type') as ComparisonType; | ||||
| const experimentId = searchParams.get('id'); | const experimentId = searchParams.get('id'); | ||||
| const [tableData, setTableData] = useState<TableData[]>([]); | const [tableData, setTableData] = useState<TableData[]>([]); | ||||
| // const [cacheState, setCacheState] = useCacheState(); | |||||
| // const [total, setTotal] = useState(0); | |||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | ||||
| // const [loading, setLoading] = useState(false); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>({ | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }); | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | ||||
| // const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| // cacheState?.pagination ?? { | |||||
| // current: 1, | |||||
| // pageSize: 10, | |||||
| // }, | |||||
| // ); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComparisonData(); | getComparisonData(); | ||||
| @@ -50,15 +53,17 @@ function ExperimentComparison() { | |||||
| // 获取对比数据列表 | // 获取对比数据列表 | ||||
| const getComparisonData = async () => { | const getComparisonData = async () => { | ||||
| // setLoading(true); | |||||
| const request = | const request = | ||||
| comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | ||||
| const [res] = await to(request(experimentId, { offset: '', limit: 50 })); | |||||
| // setLoading(false); | |||||
| const params = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| }; | |||||
| const [res] = await to(request(experimentId, params)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| // const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(res.data); | |||||
| // setTotal(totalElements); | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -80,17 +85,10 @@ function ExperimentComparison() { | |||||
| getExpMetrics(); | getExpMetrics(); | ||||
| }; | }; | ||||
| // 分页切换 | |||||
| // const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| // if (action === 'paginate') { | |||||
| // setPagination(pagination); | |||||
| // } | |||||
| // // console.log(pagination, filters, sorter, action); | |||||
| // }; | |||||
| // 选择行 | // 选择行 | ||||
| const rowSelection: TableProps['rowSelection'] = { | |||||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | |||||
| type: 'checkbox', | type: 'checkbox', | ||||
| columnWidth: 48, | |||||
| fixed: 'left', | fixed: 'left', | ||||
| selectedRowKeys, | selectedRowKeys, | ||||
| onChange: (selectedRowKeys: React.Key[]) => { | onChange: (selectedRowKeys: React.Key[]) => { | ||||
| @@ -98,8 +96,24 @@ function ExperimentComparison() { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 分页切换 | |||||
| const handleTableChange: TableProps<TableData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<TableData>['columns'] = useMemo(() => { | const columns: TableProps<TableData>['columns'] = useMemo(() => { | ||||
| const first: TableData | undefined = tableData[0]; | |||||
| const first: TableData | undefined = tableData.find( | |||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||||
| ); | |||||
| const metricsNames = first?.metrics_names ?? []; | |||||
| const paramsNames = first?.params_names ?? []; | |||||
| return [ | return [ | ||||
| { | { | ||||
| title: '基本信息', | title: '基本信息', | ||||
| @@ -147,7 +161,7 @@ function ExperimentComparison() { | |||||
| { | { | ||||
| title: `${config.title}参数`, | title: `${config.title}参数`, | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.params_names.map((name) => ({ | |||||
| children: paramsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <Tooltip title={name}> | <Tooltip title={name}> | ||||
| <span>{name}</span> | <span>{name}</span> | ||||
| @@ -159,14 +173,14 @@ function ExperimentComparison() { | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.params[name] - b.params[name], | |||||
| sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| { | { | ||||
| title: `${config.title}指标`, | title: `${config.title}指标`, | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.metrics_names.map((name) => ({ | |||||
| children: metricsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <Tooltip title={name}> | <Tooltip title={name}> | ||||
| <span>{name}</span> | <span>{name}</span> | ||||
| @@ -178,7 +192,7 @@ function ExperimentComparison() { | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.metrics[name] - b.metrics[name], | |||||
| sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| @@ -192,30 +206,21 @@ function ExperimentComparison() { | |||||
| 可视化对比 | 可视化对比 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <div | |||||
| className={classNames( | |||||
| 'vertical-scroll-table-no-page', | |||||
| styles['experiment-comparison__table'], | |||||
| )} | |||||
| > | |||||
| <div className={classNames('vertical-scroll-table', styles['experiment-comparison__table'])}> | |||||
| <Table | <Table | ||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | ||||
| pagination={false} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| bordered={true} | bordered={true} | ||||
| virtual | |||||
| // onScroll={handleTableScroll} | |||||
| // loading={loading} | |||||
| // pagination={{ | |||||
| // ...pagination, | |||||
| // total: total, | |||||
| // showSizeChanger: true, | |||||
| // showQuickJumper: true, | |||||
| // }} | |||||
| // onChange={handleTableChange} | |||||
| rowKey="run_id" | |||||
| rowKey={(record) => record.run_id || record.experiment_ins_id} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -126,7 +126,7 @@ function AddExperimentModal({ | |||||
| <Button key="cancel" onClick={onCancel}> | <Button key="cancel" onClick={onCancel}> | ||||
| 取消 | 取消 | ||||
| </Button>, | </Button>, | ||||
| <Button key="submit" type="primary" onClick={() => handleRun(false)}> | |||||
| <Button key="submit" type={isAdd ? 'primary' : 'default'} onClick={() => handleRun(false)}> | |||||
| 确定 | 确定 | ||||
| </Button>, | </Button>, | ||||
| ]; | ]; | ||||
| @@ -4,7 +4,7 @@ | |||||
| width: 100%; | width: 100%; | ||||
| padding: 0 0 0 33px; | padding: 0 0 0 33px; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 15px; | |||||
| font-size: 14px; | |||||
| & > div { | & > div { | ||||
| padding: 0 16px; | padding: 0 16px; | ||||
| @@ -129,7 +129,8 @@ function ExperimentInstanceComponent({ | |||||
| {selectedIns.length > 0 && ( | {selectedIns.length > 0 && ( | ||||
| <Button | <Button | ||||
| style={{ position: 'absolute', right: '0' }} | style={{ position: 'absolute', right: '0' }} | ||||
| type="primary" | |||||
| color="primary" | |||||
| variant="filled" | |||||
| size="small" | size="small" | ||||
| onClick={handleDeleteAll} | onClick={handleDeleteAll} | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| @@ -5,7 +5,7 @@ | |||||
| &__label { | &__label { | ||||
| color: rgba(29, 29, 32, 0.75); | color: rgba(29, 29, 32, 0.75); | ||||
| font-size: 15px; | |||||
| font-size: 14px; | |||||
| &--running { | &--running { | ||||
| color: @success-color; | color: @success-color; | ||||
| @@ -124,7 +124,12 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| const handleTableChange: TableProps<MirrorVersionData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -155,7 +155,12 @@ function MirrorList() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||||
| const handleTableChange: TableProps<MirrorData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -1,6 +1,7 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useCheck } from '@/hooks'; | import { useCheck } from '@/hooks'; | ||||
| import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset'; | ||||
| import { tableSorter } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import tableCellRender from '@/utils/table'; | import tableCellRender from '@/utils/table'; | ||||
| import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd'; | import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd'; | ||||
| @@ -18,7 +19,7 @@ type TableData = { | |||||
| metrics_names?: string[]; | metrics_names?: string[]; | ||||
| metrics?: Record<string, number>; | metrics?: Record<string, number>; | ||||
| params_names?: string[]; | params_names?: string[]; | ||||
| params?: Record<string, string>; | |||||
| params?: Record<string, number>; | |||||
| }; | }; | ||||
| type ModelMetricsProps = { | type ModelMetricsProps = { | ||||
| @@ -113,14 +114,18 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<TableData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | }; | ||||
| const rowSelection: TableProps['rowSelection'] = { | |||||
| const rowSelection: TableProps<TableData>['rowSelection'] = { | |||||
| type: 'checkbox', | type: 'checkbox', | ||||
| fixed: 'left', | fixed: 'left', | ||||
| selectedRowKeys, | selectedRowKeys, | ||||
| @@ -142,10 +147,12 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| }, [version, tableData]); | }, [version, tableData]); | ||||
| // 表头 | // 表头 | ||||
| const columns: TableProps['columns'] = useMemo(() => { | |||||
| const columns: TableProps<TableData>['columns'] = useMemo(() => { | |||||
| const first: TableData | undefined = tableData.find( | const first: TableData | undefined = tableData.find( | ||||
| (item) => item.metrics_names && item.metrics_names.length > 0, | (item) => item.metrics_names && item.metrics_names.length > 0, | ||||
| ); | ); | ||||
| const metricsNames = first?.metrics_names ?? []; | |||||
| const paramsNames = first?.params_names ?? []; | |||||
| return [ | return [ | ||||
| { | { | ||||
| title: '基本信息', | title: '基本信息', | ||||
| @@ -165,7 +172,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| { | { | ||||
| title: `训练参数`, | title: `训练参数`, | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.params_names?.map((name) => ({ | |||||
| children: paramsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <Tooltip title={name}> | <Tooltip title={name}> | ||||
| <span>{name}</span> | <span>{name}</span> | ||||
| @@ -177,7 +184,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.params?.[name] ?? 0 - b.params?.[name] ?? 0, | |||||
| sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| @@ -188,12 +195,13 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| checked={metricsChecked} | checked={metricsChecked} | ||||
| indeterminate={metricsIndeterminate} | indeterminate={metricsIndeterminate} | ||||
| onChange={checkAllMetrics} | onChange={checkAllMetrics} | ||||
| disabled={metricsNames.length === 0} | |||||
| ></Checkbox>{' '} | ></Checkbox>{' '} | ||||
| <span>训练指标</span> | <span>训练指标</span> | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| align: 'center', | align: 'center', | ||||
| children: first?.metrics_names?.map((name) => ({ | |||||
| children: metricsNames.map((name) => ({ | |||||
| title: ( | title: ( | ||||
| <div> | <div> | ||||
| <Checkbox | <Checkbox | ||||
| @@ -215,7 +223,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||||
| align: 'center', | align: 'center', | ||||
| render: tableCellRender(true), | render: tableCellRender(true), | ||||
| ellipsis: { showTitle: false }, | ellipsis: { showTitle: false }, | ||||
| sorter: (a, b) => a.metrics?.[name] ?? 0 - b.metrics?.[name] ?? 0, | |||||
| sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]), | |||||
| showSorterTooltip: false, | showSorterTooltip: false, | ||||
| })), | })), | ||||
| }, | }, | ||||
| @@ -176,7 +176,12 @@ function ModelDeployment() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<ServiceData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| @@ -18,6 +18,7 @@ import { | |||||
| } from '@/services/modelDeployment'; | } from '@/services/modelDeployment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | import tableCellRender, { TableCellValueType } from '@/utils/table'; | ||||
| @@ -37,6 +38,7 @@ import { type SearchProps } from 'antd/es/input'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | import ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | ||||
| import VersionCompareModal from '../components/VersionCompareModal'; | |||||
| import { | import { | ||||
| CreateServiceVersionFrom, | CreateServiceVersionFrom, | ||||
| ServiceData, | ServiceData, | ||||
| @@ -56,6 +58,7 @@ function ServiceInfo() { | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | const [inputText, setInputText] = useState(cacheState?.searchText); | ||||
| const [tableData, setTableData] = useState<ServiceVersionData[]>([]); | const [tableData, setTableData] = useState<ServiceVersionData[]>([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| cacheState?.pagination ?? { | cacheState?.pagination ?? { | ||||
| current: 1, | current: 1, | ||||
| @@ -208,11 +211,39 @@ function ServiceInfo() { | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| const handleTableChange: TableProps<ServiceVersionData>['onChange'] = ( | |||||
| pagination, | |||||
| _filters, | |||||
| _sorter, | |||||
| { action }, | |||||
| ) => { | |||||
| if (action === 'paginate') { | if (action === 'paginate') { | ||||
| setPagination(pagination); | setPagination(pagination); | ||||
| } | } | ||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | |||||
| // 版本对比 | |||||
| const handleVersionCompare = () => { | |||||
| if (selectedRowKeys.length !== 2) { | |||||
| message.error('请选择两个版本进行对比'); | |||||
| return; | |||||
| } | |||||
| openAntdModal(VersionCompareModal, { | |||||
| version1: selectedRowKeys[0] as string, | |||||
| version2: selectedRowKeys[1] as string, | |||||
| }); | |||||
| }; | |||||
| // 选择行 | |||||
| const rowSelection: TableProps<ServiceVersionData>['rowSelection'] = { | |||||
| type: 'checkbox', | |||||
| columnWidth: 48, | |||||
| fixed: 'left', | |||||
| selectedRowKeys, | |||||
| onChange: (selectedRowKeys: React.Key[]) => { | |||||
| setSelectedRowKeys(selectedRowKeys); | |||||
| }, | |||||
| }; | }; | ||||
| const columns: TableProps<ServiceVersionData>['columns'] = [ | const columns: TableProps<ServiceVersionData>['columns'] = [ | ||||
| @@ -379,13 +410,16 @@ function ServiceInfo() { | |||||
| allowClear | allowClear | ||||
| ></Select> | ></Select> | ||||
| <Button | <Button | ||||
| style={{ marginRight: '20px', marginLeft: 'auto' }} | |||||
| style={{ marginRight: '15px', marginLeft: 'auto' }} | |||||
| type="default" | type="default" | ||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | onClick={() => createServiceVersion(ServiceOperationType.Create)} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 新增版本 | 新增版本 | ||||
| </Button> | </Button> | ||||
| <Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}> | |||||
| 版本对比 | |||||
| </Button> | |||||
| <Button | <Button | ||||
| style={{ marginRight: 0 }} | style={{ marginRight: 0 }} | ||||
| type="default" | type="default" | ||||
| @@ -410,6 +444,7 @@ function ServiceInfo() { | |||||
| showTotal: () => `共${total}条`, | showTotal: () => `共${total}条`, | ||||
| }} | }} | ||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowSelection={rowSelection} | |||||
| rowKey="id" | rowKey="id" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| @@ -0,0 +1,117 @@ | |||||
| @purple-color: #6516ff; | |||||
| .title(@color, @background) { | |||||
| width: 100%; | |||||
| margin-bottom: 20px; | |||||
| color: @color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size; | |||||
| line-height: 42px; | |||||
| text-align: center; | |||||
| background: @background; | |||||
| .singleLine(); | |||||
| } | |||||
| .text() { | |||||
| margin-bottom: 20px !important; | |||||
| color: @text-color-secondary; | |||||
| font-size: 13px; | |||||
| word-break: break-all; | |||||
| .singleLine(); | |||||
| } | |||||
| .version-container(@background) { | |||||
| flex: 1; | |||||
| min-width: 0; | |||||
| background: @background; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .version-compare { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| padding: 40px 40px 25px !important; | |||||
| } | |||||
| .ant-modal-header { | |||||
| margin-bottom: 20px !important; | |||||
| } | |||||
| .kf-modal-title { | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 20px; | |||||
| } | |||||
| } | |||||
| &__container { | |||||
| display: flex; | |||||
| flex-wrap: nowrap; | |||||
| gap: 0 5px; | |||||
| align-items: stretch; | |||||
| height: 100%; | |||||
| } | |||||
| &__fields { | |||||
| flex: none; | |||||
| width: 117px; | |||||
| padding: 0 15px; | |||||
| background: white; | |||||
| border: 1px solid .addAlpha(@primary-color, 0.2) []; | |||||
| border-radius: 4px; | |||||
| &__title { | |||||
| margin-bottom: 20px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| line-height: 42px; | |||||
| } | |||||
| &__text { | |||||
| .text(); | |||||
| &--different { | |||||
| color: @error-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__left { | |||||
| .version-container(.addAlpha(@primary-color, 0.04) []); | |||||
| &__title { | |||||
| .title(@primary-color, linear-gradient( | |||||
| 159.9deg,rgba(138, 177, 255, 0.5) 0%, | |||||
| rgba(22, 100, 255, 0.5) 100% | |||||
| )); | |||||
| } | |||||
| &__text { | |||||
| padding: 0 15px; | |||||
| text-align: center; | |||||
| .text(); | |||||
| &--different { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__right { | |||||
| .version-container(rgba(100, 30, 237, 0.04)); | |||||
| &__title { | |||||
| .title(@purple-color, linear-gradient( | |||||
| 159.9deg, | |||||
| rgba(193, 138, 255, 0.5) 0%, | |||||
| rgba(146, 22, 255, 0.5) 100% | |||||
| )); | |||||
| } | |||||
| &__text { | |||||
| padding: 0 15px; | |||||
| text-align: center; | |||||
| .text(); | |||||
| &--different { | |||||
| color: @purple-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,197 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { ServiceRunStatus } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Typography, type ModalProps } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect, useMemo, useState } from 'react'; | |||||
| import { statusInfo } from '../ModelDeployStatusCell'; | |||||
| import styles from './index.less'; | |||||
| type CompareData = { | |||||
| differences: Record<string, any>; | |||||
| version1: ServiceVersionData; | |||||
| version2: ServiceVersionData; | |||||
| }; | |||||
| type ServiceVersionDataKey = keyof ServiceVersionData; | |||||
| type FiledType = { | |||||
| key: ServiceVersionDataKey; | |||||
| text: string; | |||||
| format?: (data: any) => any; | |||||
| }; | |||||
| interface CreateMirrorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| version1: string; | |||||
| version2: string; | |||||
| } | |||||
| // 格式化环境变量 | |||||
| const formatEnvText = (env: Record<string, string>) => { | |||||
| if (!env || Object.keys(env).length === 0) { | |||||
| return '--'; | |||||
| } | |||||
| return Object.entries(env) | |||||
| .map(([key, value]) => `${key} = ${value}`) | |||||
| .join(','); | |||||
| }; | |||||
| function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalProps) { | |||||
| const [compareData, setCompareData] = useState<CompareData | undefined>(undefined); | |||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| const fields: FiledType[] = useMemo( | |||||
| () => [ | |||||
| { | |||||
| key: 'service_name', | |||||
| text: '服务名称', | |||||
| }, | |||||
| { | |||||
| key: 'run_state', | |||||
| text: '状态', | |||||
| format: (data: any) => { | |||||
| return data ? statusInfo[data as ServiceRunStatus].text : '--'; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'image', | |||||
| text: '镜像', | |||||
| }, | |||||
| { | |||||
| key: 'code_config', | |||||
| text: '代码配置', | |||||
| format: (data: any) => { | |||||
| return data?.show_value; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'model', | |||||
| text: '模型', | |||||
| format: (data: any) => { | |||||
| return data?.show_value; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| key: 'resource', | |||||
| text: '资源规格', | |||||
| format: getResourceDescription, | |||||
| }, | |||||
| { | |||||
| key: 'replicas', | |||||
| text: '副本数', | |||||
| }, | |||||
| { | |||||
| key: 'mount_path', | |||||
| text: '挂载路径', | |||||
| }, | |||||
| { | |||||
| key: 'url', | |||||
| text: '服务URL', | |||||
| }, | |||||
| { | |||||
| key: 'env_variables', | |||||
| text: '环境变量', | |||||
| format: formatEnvText, | |||||
| }, | |||||
| { | |||||
| key: 'description', | |||||
| text: '描述', | |||||
| }, | |||||
| ], | |||||
| [getResourceDescription], | |||||
| ); | |||||
| useEffect(() => { | |||||
| getServiceVersionCompare(); | |||||
| }, []); | |||||
| // 获取对比数据 | |||||
| const getServiceVersionCompare = async () => { | |||||
| const params = { | |||||
| id1: version1, | |||||
| id2: version2, | |||||
| }; | |||||
| const [res] = await to(getServiceVersionCompareReq(params)); | |||||
| if (res && res.data) { | |||||
| setCompareData(res.data); | |||||
| } | |||||
| }; | |||||
| const { | |||||
| version1: v1 = {} as ServiceVersionData, | |||||
| version2: v2 = {} as ServiceVersionData, | |||||
| differences = {}, | |||||
| } = compareData || {}; | |||||
| const isDifferent = (key: ServiceVersionDataKey) => { | |||||
| const keys = Object.keys(differences); | |||||
| return keys.includes(key); | |||||
| }; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="服务版本对比" | |||||
| width={825} | |||||
| footer={null} | |||||
| className={styles['version-compare']} | |||||
| > | |||||
| <div className={styles['version-compare__container']}> | |||||
| <div className={styles['version-compare__fields']}> | |||||
| <div className={styles['version-compare__fields__title']}>基础版本号</div> | |||||
| {fields.map(({ key, text }) => ( | |||||
| <div | |||||
| className={classNames(styles['version-compare__fields__text'], { | |||||
| [styles['version-compare__fields__text--different']]: isDifferent(key), | |||||
| })} | |||||
| key={key} | |||||
| > | |||||
| {text} | |||||
| </div> | |||||
| ))} | |||||
| </div> | |||||
| <div className={styles['version-compare__left']}> | |||||
| <div className={styles['version-compare__left__title']}>{v1.version}</div> | |||||
| {fields.map(({ key, format }) => { | |||||
| const text = format ? format(v1[key]) : v1[key]; | |||||
| return ( | |||||
| <div | |||||
| key={key} | |||||
| className={classNames(styles['version-compare__left__text'], { | |||||
| [styles['version-compare__left__text--different']]: isDifferent(key), | |||||
| })} | |||||
| > | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}> | |||||
| {text} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| <div className={styles['version-compare__right']}> | |||||
| <div className={styles['version-compare__right__title']}>{v2.version}</div> | |||||
| {fields.map(({ key, format }) => { | |||||
| const text = format ? format(v2[key]) : v2[key]; | |||||
| return ( | |||||
| <div | |||||
| key={key} | |||||
| className={classNames(styles['version-compare__right__text'], { | |||||
| [styles['version-compare__right__text--different']]: isDifferent(key), | |||||
| })} | |||||
| > | |||||
| <Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}> | |||||
| {text} | |||||
| </Typography.Text> | |||||
| </div> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| </div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default VersionCompareModal; | |||||
| @@ -20,10 +20,6 @@ | |||||
| } | } | ||||
| } | } | ||||
| .ant-pagination { | |||||
| text-align: center; | |||||
| } | |||||
| .ant-input-group-addon { | .ant-input-group-addon { | ||||
| display: none; | display: none; | ||||
| } | } | ||||
| @@ -97,6 +97,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| <Pagination | <Pagination | ||||
| align="center" | |||||
| total={total} | total={total} | ||||
| showSizeChanger | showSizeChanger | ||||
| defaultPageSize={20} | defaultPageSize={20} | ||||
| @@ -47,13 +47,11 @@ | |||||
| &__robot-img { | &__robot-img { | ||||
| position: fixed; | position: fixed; | ||||
| right: 30px; | |||||
| bottom: 20px; | |||||
| right: 20px; | |||||
| bottom: 90px; | |||||
| z-index: 99; | z-index: 99; | ||||
| width: 64px; | |||||
| height: 64px; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| width: 56px; | |||||
| height: 56px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| } | } | ||||
| @@ -104,3 +104,11 @@ export function getServiceVersionLogReq(params: any) { | |||||
| params, | params, | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取服务版本对比 | |||||
| export function getServiceVersionCompareReq(params: any) { | |||||
| return request(`/api/mmp/service/serviceVersionCompare`, { | |||||
| method: 'GET', | |||||
| params, | |||||
| }); | |||||
| } | |||||
| @@ -188,4 +188,227 @@ declare namespace API { | |||||
| filter?: string; | filter?: string; | ||||
| sorter?: string; | sorter?: string; | ||||
| }; | }; | ||||
| type CurrentUser = UserInfo & { | |||||
| signature?: string; | |||||
| title?: string; | |||||
| group?: string; | |||||
| tags?: { key?: string; label?: string }[]; | |||||
| notifyCount?: number; | |||||
| unreadCount?: number; | |||||
| country?: string; | |||||
| access?: string; | |||||
| geographic?: { | |||||
| province?: { label?: string; key?: string }; | |||||
| city?: { label?: string; key?: string }; | |||||
| }; | |||||
| address?: string; | |||||
| phone?: string; | |||||
| roleNames?: { | |||||
| roleName?: string; | |||||
| }[]; | |||||
| }; | |||||
| type ErrorResponse = { | |||||
| /** 业务约定的错误码 */ | |||||
| errorCode: string; | |||||
| /** 业务上的错误信息 */ | |||||
| errorMessage?: string; | |||||
| /** 业务上的请求是否成功 */ | |||||
| success?: boolean; | |||||
| }; | |||||
| type FakeCaptcha = { | |||||
| code?: number; | |||||
| status?: string; | |||||
| }; | |||||
| type getFakeCaptchaParams = { | |||||
| /** 手机号 */ | |||||
| phone?: string; | |||||
| }; | |||||
| type LoginParams = { | |||||
| username?: string; | |||||
| password?: string; | |||||
| uuid?: string; | |||||
| autoLogin?: boolean; | |||||
| type?: string; | |||||
| }; | |||||
| type LoginResult = { | |||||
| code: number; | |||||
| msg?: string; | |||||
| type?: string; | |||||
| data?: { | |||||
| access_token?: string; | |||||
| expires_in?: number; | |||||
| }; | |||||
| }; | |||||
| type NoticeIconItem = { | |||||
| id?: string; | |||||
| extra?: string; | |||||
| key?: string; | |||||
| read?: boolean; | |||||
| avatar?: string; | |||||
| title?: string; | |||||
| status?: string; | |||||
| datetime?: string; | |||||
| description?: string; | |||||
| type?: NoticeIconItemType; | |||||
| }; | |||||
| type NoticeIconItemType = 'notification' | 'message' | 'event'; | |||||
| type NoticeIconList = { | |||||
| data?: NoticeIconItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type PageParams = { | |||||
| current?: number; | |||||
| pageSize?: number; | |||||
| }; | |||||
| type RuleList = { | |||||
| data?: RuleListItem[]; | |||||
| /** 列表的内容总数 */ | |||||
| total?: number; | |||||
| success?: boolean; | |||||
| }; | |||||
| type RuleListItem = { | |||||
| key?: number; | |||||
| disabled?: boolean; | |||||
| href?: string; | |||||
| avatar?: string; | |||||
| name?: string; | |||||
| owner?: string; | |||||
| desc?: string; | |||||
| callNo?: number; | |||||
| status?: number; | |||||
| updatedAt?: string; | |||||
| createdAt?: string; | |||||
| progress?: number; | |||||
| }; | |||||
| type ruleParams = { | |||||
| /** 当前的页码 */ | |||||
| current?: number; | |||||
| /** 页面的容量 */ | |||||
| pageSize?: number; | |||||
| }; | |||||
| type ApiResponse = { | |||||
| code?: number; | |||||
| type?: string; | |||||
| message?: string; | |||||
| }; | |||||
| type Category = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type deleteOrderParams = { | |||||
| /** ID of the order that needs to be deleted */ | |||||
| orderId: number; | |||||
| }; | |||||
| type deletePetParams = { | |||||
| api_key?: string; | |||||
| /** Pet id to delete */ | |||||
| petId: number; | |||||
| }; | |||||
| type deleteUserParams = { | |||||
| /** The name that needs to be deleted */ | |||||
| username: string; | |||||
| }; | |||||
| type findPetsByStatusParams = { | |||||
| /** Status values that need to be considered for filter */ | |||||
| status: ('available' | 'pending' | 'sold')[]; | |||||
| }; | |||||
| type findPetsByTagsParams = { | |||||
| /** Tags to filter by */ | |||||
| tags: string[]; | |||||
| }; | |||||
| type getOrderByIdParams = { | |||||
| /** ID of pet that needs to be fetched */ | |||||
| orderId: number; | |||||
| }; | |||||
| type getPetByIdParams = { | |||||
| /** ID of pet to return */ | |||||
| petId: number; | |||||
| }; | |||||
| type getUserByNameParams = { | |||||
| /** The name that needs to be fetched. Use user1 for testing. */ | |||||
| username: string; | |||||
| }; | |||||
| type loginUserParams = { | |||||
| /** The user name for login */ | |||||
| username: string; | |||||
| /** The password for login in clear text */ | |||||
| password: string; | |||||
| }; | |||||
| type Order = { | |||||
| id?: number; | |||||
| petId?: number; | |||||
| quantity?: number; | |||||
| shipDate?: string; | |||||
| /** Order Status */ | |||||
| status?: 'placed' | 'approved' | 'delivered'; | |||||
| complete?: boolean; | |||||
| }; | |||||
| type Pet = { | |||||
| id?: number; | |||||
| category?: Category; | |||||
| name: string; | |||||
| photoUrls: string[]; | |||||
| tags?: Tag[]; | |||||
| /** pet status in the store */ | |||||
| status?: 'available' | 'pending' | 'sold'; | |||||
| }; | |||||
| type Tag = { | |||||
| id?: number; | |||||
| name?: string; | |||||
| }; | |||||
| type updatePetWithFormParams = { | |||||
| /** ID of pet that needs to be updated */ | |||||
| petId: number; | |||||
| }; | |||||
| type updateUserParams = { | |||||
| /** name that need to be updated */ | |||||
| username: string; | |||||
| }; | |||||
| type uploadFileParams = { | |||||
| /** ID of pet to update */ | |||||
| petId: number; | |||||
| }; | |||||
| type User = { | |||||
| id?: number; | |||||
| username?: string; | |||||
| firstName?: string; | |||||
| lastName?: string; | |||||
| email?: string; | |||||
| password?: string; | |||||
| phone?: string; | |||||
| /** User Status */ | |||||
| userStatus?: number; | |||||
| }; | |||||
| } | } | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 工具类 | * @Description: 工具类 | ||||
| */ | */ | ||||
| import { PageEnum } from '@/enums/pagesEnums'; | |||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| // 生成 8 位随机数 | // 生成 8 位随机数 | ||||
| @@ -218,3 +219,25 @@ export const getGitUrl = (url: string, branch: string): string => { | |||||
| const gitUrl = url.replace(/\.git$/, ''); | const gitUrl = url.replace(/\.git$/, ''); | ||||
| return branch ? `${gitUrl}/tree/${branch}` : gitUrl; | return branch ? `${gitUrl}/tree/${branch}` : gitUrl; | ||||
| }; | }; | ||||
| // 判断是否需要登录 | |||||
| export const needAuth = (pathname: string) => { | |||||
| return pathname !== PageEnum.LOGIN && pathname !== PageEnum.Authorize; | |||||
| }; | |||||
| // 表格排序 | |||||
| export const tableSorter = (a: any, b: any) => { | |||||
| if (b === null || b === undefined) { | |||||
| return -1; | |||||
| } | |||||
| if (a === null || a === undefined) { | |||||
| return 1; | |||||
| } | |||||
| if (typeof a === 'number' && typeof b === 'number') { | |||||
| return a - b; | |||||
| } | |||||
| if (typeof a === 'string' && typeof b === 'string') { | |||||
| return a.localeCompare(b); | |||||
| } | |||||
| return 0; | |||||
| }; | |||||