| @@ -271,7 +271,18 @@ export default [ | |||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: 'info/:id', | path: 'info/:id', | ||||
| component: './Mirror/Info', | |||||
| routes: [ | |||||
| { | |||||
| name: '镜像详情', | |||||
| path: '', | |||||
| component: './Mirror/Info', | |||||
| }, | |||||
| { | |||||
| name: '新增镜像版本', | |||||
| path: 'add-version', | |||||
| component: './Mirror/Create', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| @@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [ | |||||
| function MirrorCreate() { | function MirrorCreate() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [nameDisabled, setNameDisabled] = useState(false); | |||||
| const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本 | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| @@ -60,7 +60,7 @@ function MirrorCreate() { | |||||
| const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | const name = SessionStorage.getItem(SessionStorage.mirrorNameKey); | ||||
| if (name) { | if (name) { | ||||
| form.setFieldValue('name', name); | form.setFieldValue('name', name); | ||||
| setNameDisabled(true); | |||||
| setIsAddVersion(true); | |||||
| } | } | ||||
| return () => { | return () => { | ||||
| SessionStorage.removeItem(SessionStorage.mirrorNameKey); | SessionStorage.removeItem(SessionStorage.mirrorNameKey); | ||||
| @@ -120,7 +120,7 @@ function MirrorCreate() { | |||||
| return ( | return ( | ||||
| <div className={styles['mirror-create']}> | <div className={styles['mirror-create']}> | ||||
| <PageTitle title="创建镜像"></PageTitle> | |||||
| <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> | |||||
| <div className={styles['mirror-create__content']}> | <div className={styles['mirror-create__content']}> | ||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| @@ -142,7 +142,7 @@ function MirrorCreate() { | |||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像名称及Tag" | |||||
| label="镜像名称和版本" | |||||
| name="name" | name="name" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -158,7 +158,7 @@ function MirrorCreate() { | |||||
| <Input | <Input | ||||
| placeholder="请输入镜像名称" | placeholder="请输入镜像名称" | ||||
| maxLength={64} | maxLength={64} | ||||
| disabled={nameDisabled} | |||||
| disabled={isAddVersion} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -174,22 +174,22 @@ function MirrorCreate() { | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入镜像Tag', | |||||
| message: '请输入镜像版本', | |||||
| }, | }, | ||||
| { | { | ||||
| pattern: /^[a-zA-Z0-9._-]+$/, | pattern: /^[a-zA-Z0-9._-]+$/, | ||||
| message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={10}> | <Row gutter={10}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label="镜像描述" | |||||
| label={isAddVersion ? '镜像版本描述' : '镜像描述'} | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -303,7 +303,7 @@ function MirrorCreate() { | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| 创建镜像 | |||||
| 确定 | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| @@ -155,7 +155,7 @@ function MirrorInfo() { | |||||
| }; | }; | ||||
| const createMirrorVersion = () => { | const createMirrorVersion = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`add-version`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| @@ -167,27 +167,34 @@ function MirrorInfo() { | |||||
| title: '镜像版本', | title: '镜像版本', | ||||
| dataIndex: 'tag_name', | dataIndex: 'tag_name', | ||||
| key: 'tag_name', | key: 'tag_name', | ||||
| width: '25%', | |||||
| width: '20%', | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像地址', | title: '镜像地址', | ||||
| dataIndex: 'url', | dataIndex: 'url', | ||||
| key: 'url', | key: 'url', | ||||
| render: tableCellRender(), | |||||
| width: '20%', | |||||
| render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), | |||||
| }, | |||||
| { | |||||
| title: '版本描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: tableCellRender(true), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | title: '状态', | ||||
| dataIndex: 'status', | dataIndex: 'status', | ||||
| key: 'status', | key: 'status', | ||||
| width: 150, | |||||
| width: 100, | |||||
| render: MirrorStatusCell, | render: MirrorStatusCell, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像大小', | title: '镜像大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| key: 'file_size', | key: 'file_size', | ||||
| width: 150, | |||||
| width: 120, | |||||
| render: tableCellRender(), | render: tableCellRender(), | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -200,7 +207,7 @@ function MirrorInfo() { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: 150, | |||||
| width: 120, | |||||
| key: 'operation', | key: 'operation', | ||||
| hidden: isPublic, | hidden: isPublic, | ||||
| render: (_: any, record: MirrorVersionData) => ( | render: (_: any, record: MirrorVersionData) => ( | ||||
| @@ -128,7 +128,7 @@ function MirrorList() { | |||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: MirrorData) => { | const toDetail = (record: MirrorData) => { | ||||
| navigate(`/dataset/mirror/info/${record.id}`); | |||||
| navigate(`info/${record.id}`); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -149,7 +149,7 @@ function MirrorList() { | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navigate(`/dataset/mirror/create`); | |||||
| navigate(`create`); | |||||
| SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | SessionStorage.setItem(SessionStorage.mirrorNameKey, ''); | ||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| @@ -262,7 +262,7 @@ function MirrorList() { | |||||
| onClick={createMirror} | onClick={createMirror} | ||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 制作镜像 | |||||
| 创建镜像 | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| <Button | <Button | ||||
| @@ -25,6 +25,7 @@ export type TableCellValueOptions<T> = { | |||||
| dateFormat?: string; // 类型为 Date 时有效 | dateFormat?: string; // 类型为 Date 时有效 | ||||
| onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | ||||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | ||||
| copyable?: boolean; // 省略时是否可以复制 | |||||
| }; | }; | ||||
| type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | ||||
| @@ -93,17 +94,17 @@ function tableCellRender<T>( | |||||
| } | } | ||||
| if (ellipsis === 'auto' && text) { | if (ellipsis === 'auto' && text) { | ||||
| return renderCell(type, text, 'auto', record, options?.onClick); | |||||
| return renderCell(type, text, 'auto', record, options); | |||||
| } else if (ellipsis && text) { | } else if (ellipsis && text) { | ||||
| const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | const tooltipProps = typeof ellipsis === 'object' ? ellipsis : {}; | ||||
| const { overlayStyle, ...rest } = tooltipProps; | const { overlayStyle, ...rest } = tooltipProps; | ||||
| return ( | return ( | ||||
| <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | <Tooltip {...rest} overlayStyle={{ maxWidth: 400, ...overlayStyle }} title={text}> | ||||
| {renderCell(type, text, true, record, options?.onClick)} | |||||
| {renderCell(type, text, true, record, options)} | |||||
| </Tooltip> | </Tooltip> | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| return renderCell(type, text, false, record, options?.onClick); | |||||
| return renderCell(type, text, false, record, options); | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| @@ -113,31 +114,38 @@ function renderCell<T>( | |||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| return type === TableCellValueType.Link | return type === TableCellValueType.Link | ||||
| ? renderLink(text, ellipsis, record, onClick) | |||||
| : renderText(text, ellipsis); | |||||
| ? renderLink(text, ellipsis, record, options) | |||||
| : renderText(text, ellipsis, options); | |||||
| } | } | ||||
| function renderLink<T>( | function renderLink<T>( | ||||
| text: any | undefined | null, | text: any | undefined | null, | ||||
| ellipsis: boolean | 'auto', | ellipsis: boolean | 'auto', | ||||
| record: T, | record: T, | ||||
| onClick?: (record: T, e: React.MouseEvent) => void, | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | ) { | ||||
| const { onClick } = options ?? {}; | |||||
| return ( | return ( | ||||
| <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | <a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}> | ||||
| {renderText(text, ellipsis)} | |||||
| {renderText(text, ellipsis, options)} | |||||
| </a> | </a> | ||||
| ); | ); | ||||
| } | } | ||||
| function renderText(text: any | undefined | null, ellipsis: boolean | 'auto') { | |||||
| function renderText<T>( | |||||
| text: any | undefined | null, | |||||
| ellipsis: boolean | 'auto', | |||||
| options?: TableCellValueOptions<T>, | |||||
| ) { | |||||
| const { copyable } = options ?? {}; | |||||
| if (ellipsis === 'auto') { | if (ellipsis === 'auto') { | ||||
| return ( | return ( | ||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| style={{ marginBottom: 0 }} | style={{ marginBottom: 0 }} | ||||
| copyable={copyable} | |||||
| ellipsis={{ | ellipsis={{ | ||||
| tooltip: { | tooltip: { | ||||
| title: text, | title: text, | ||||