feat: add cluster list
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
RetweetOutlined,
|
RetweetOutlined,
|
||||||
ThunderboltOutlined
|
ThunderboltOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -17,6 +18,7 @@ const icons = {
|
|||||||
ThunderboltOutlined: React.createElement(ThunderboltOutlined),
|
ThunderboltOutlined: React.createElement(ThunderboltOutlined),
|
||||||
RetweetOutlined: React.createElement(RetweetOutlined),
|
RetweetOutlined: React.createElement(RetweetOutlined),
|
||||||
DownloadOutlined: React.createElement(DownloadOutlined),
|
DownloadOutlined: React.createElement(DownloadOutlined),
|
||||||
|
FileTextOutlined: React.createElement(FileTextOutlined),
|
||||||
ApiOutlined: React.createElement(ApiOutlined),
|
ApiOutlined: React.createElement(ApiOutlined),
|
||||||
Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
|
Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
|
||||||
Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
|
Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
|||||||
// import './iconfont/iconfont.js';
|
// import './iconfont/iconfont.js';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_oxxxb51iqbp.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_qgb5xlexehp.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
marginBottom = 10,
|
marginBottom = 10,
|
||||||
marginTop = 10,
|
marginTop = 10,
|
||||||
inputHolder = 'common.filter.name',
|
inputHolder = 'common.filter.name',
|
||||||
selectHolder,
|
selectHolder = '',
|
||||||
showPrimaryButton = true,
|
showPrimaryButton = true,
|
||||||
showDeleteButton = true,
|
showDeleteButton = true,
|
||||||
width
|
width
|
||||||
@@ -107,43 +107,47 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
{actionType === 'dropdown' ? (
|
{showPrimaryButton ? (
|
||||||
<DropDownActions
|
actionType === 'dropdown' ? (
|
||||||
menu={{
|
<DropDownActions
|
||||||
items: actionItems,
|
menu={{
|
||||||
onClick: handleClickPrimary
|
items: actionItems,
|
||||||
}}
|
onClick: handleClickPrimary
|
||||||
>
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<DownOutlined></DownOutlined>}
|
||||||
|
type="primary"
|
||||||
|
iconPosition="end"
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</DropDownActions>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
icon={<DownOutlined></DownOutlined>}
|
icon={buttonIcon ?? <PlusOutlined></PlusOutlined>}
|
||||||
type="primary"
|
type="primary"
|
||||||
iconPosition="end"
|
onClick={handleClickPrimary}
|
||||||
>
|
>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</Button>
|
</Button>
|
||||||
</DropDownActions>
|
)
|
||||||
) : (
|
) : null}
|
||||||
|
{showDeleteButton && (
|
||||||
<Button
|
<Button
|
||||||
icon={buttonIcon ?? <PlusOutlined></PlusOutlined>}
|
icon={<DeleteOutlined />}
|
||||||
type="primary"
|
danger
|
||||||
onClick={handleClickPrimary}
|
onClick={handleDeleteByBatch}
|
||||||
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
>
|
>
|
||||||
{buttonText}
|
<span>
|
||||||
|
{intl?.formatMessage?.({ id: 'common.button.delete' })}
|
||||||
|
{rowSelection.selectedRowKeys.length > 0 && (
|
||||||
|
<span>({rowSelection.selectedRowKeys?.length})</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
danger
|
|
||||||
onClick={handleDeleteByBatch}
|
|
||||||
disabled={!rowSelection.selectedRowKeys.length}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{intl?.formatMessage?.({ id: 'common.button.delete' })}
|
|
||||||
{rowSelection.selectedRowKeys.length > 0 && (
|
|
||||||
<span>({rowSelection.selectedRowKeys?.length})</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
|
export const CREDENTIALS_API = '/credentials';
|
||||||
|
|
||||||
|
export async function queryCredentialList(params: Global.SearchParams) {
|
||||||
|
// return request<Global.PageResponse<ListItem>>(`${CREDENTIALS_API}`, {
|
||||||
|
// method: 'GET',
|
||||||
|
// params
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCredential(params: { data: FormData }) {
|
||||||
|
return request(`${CREDENTIALS_API}`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCredential(params: { data: FormData }) {
|
||||||
|
return request(`${CREDENTIALS_API}/${params.data.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteCredential(id: number) {
|
||||||
|
return request(`${CREDENTIALS_API}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,25 +1,449 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import DeleteModal from '@/components/delete-modal';
|
||||||
|
import DropDownActions from '@/components/drop-down-actions';
|
||||||
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import PageTools from '@/components/page-tools';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import type { PageActionType } from '@/config/types';
|
||||||
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
|
import AddWorker from '@/pages/resources/components/add-worker';
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
KubernetesOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
|
SnippetsOutlined,
|
||||||
|
SyncOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import React from 'react';
|
import {
|
||||||
|
Button,
|
||||||
|
ConfigProvider,
|
||||||
|
Empty,
|
||||||
|
Input,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
message
|
||||||
|
} from 'antd';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
createCredential,
|
||||||
|
deleteCredential,
|
||||||
|
queryCredentialList,
|
||||||
|
updateCredential
|
||||||
|
} from './apis';
|
||||||
|
import AddCluster from './components/add-cluster';
|
||||||
|
import { ClusterDataList } from './config';
|
||||||
|
import {
|
||||||
|
ClusterFormData as FormData,
|
||||||
|
ClusterListItem as ListItem
|
||||||
|
} from './config/types';
|
||||||
|
const { Column } = Table;
|
||||||
|
|
||||||
|
const addActions = [
|
||||||
|
{
|
||||||
|
label: 'Custom',
|
||||||
|
locale: false,
|
||||||
|
value: 'custom',
|
||||||
|
key: 'custom',
|
||||||
|
icon: <SnippetsOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Kubernetes',
|
||||||
|
locale: false,
|
||||||
|
value: 'kubernetes',
|
||||||
|
key: 'kubernetes',
|
||||||
|
icon: <KubernetesOutlined />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const WorkerWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
.worker {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
.value {
|
||||||
|
line-height: 1em;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 4px;
|
||||||
|
&.ready {
|
||||||
|
background-color: var(--ant-color-success);
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
background-color: var(--ant-color-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.transition {
|
||||||
|
background-color: var(--ant-blue-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActionList = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: 'common.button.edit',
|
||||||
|
icon: <EditOutlined></EditOutlined>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'add',
|
||||||
|
label: 'Add Worker',
|
||||||
|
locale: false,
|
||||||
|
icon: <EditOutlined></EditOutlined>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'terminal',
|
||||||
|
label: 'common.button.detail',
|
||||||
|
icon: <ProfileOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
props: {
|
||||||
|
danger: true
|
||||||
|
},
|
||||||
|
label: 'common.button.delete',
|
||||||
|
icon: <DeleteOutlined></DeleteOutlined>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const Credentials: React.FC = () => {
|
||||||
|
const {
|
||||||
|
dataSource,
|
||||||
|
rowSelection,
|
||||||
|
queryParams,
|
||||||
|
sortOrder,
|
||||||
|
modalRef,
|
||||||
|
handleDelete,
|
||||||
|
handleDeleteBatch,
|
||||||
|
fetchData,
|
||||||
|
handlePageChange,
|
||||||
|
handleTableChange,
|
||||||
|
handleSearch,
|
||||||
|
handleNameChange
|
||||||
|
} = useTableFetch<ListItem>({
|
||||||
|
fetchAPI: queryCredentialList,
|
||||||
|
deleteAPI: deleteCredential,
|
||||||
|
contentForDelete: 'users.table.user'
|
||||||
|
});
|
||||||
|
|
||||||
const Cluster: React.FC = () => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
|
const [clusterType, setClusterType] = useState<string>('custom');
|
||||||
|
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
||||||
|
const [title, setTitle] = useState<string>('');
|
||||||
|
const [currentData, setCurrentData] = useState<ListItem | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddUser = (value: string) => {
|
||||||
|
setOpenAddModal(true);
|
||||||
|
setAction(PageAction.CREATE);
|
||||||
|
setClusterType(value);
|
||||||
|
setTitle('Add Cluster');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickDropdown = (item: any) => {
|
||||||
|
console.log('handleClickDropdown', item);
|
||||||
|
handleAddUser(item.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOk = async (data: FormData) => {
|
||||||
|
const params = {
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (action === PageAction.EDIT) {
|
||||||
|
await updateCredential({
|
||||||
|
data: {
|
||||||
|
...params,
|
||||||
|
id: currentData?.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createCredential({ data: params });
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
setOpenAddModal(false);
|
||||||
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
} catch (error) {
|
||||||
|
setOpenAddModal(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
console.log('handleModalCancel');
|
||||||
|
setOpenAddModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditUser = (row: ListItem) => {
|
||||||
|
setCurrentData(row);
|
||||||
|
setOpenAddModal(true);
|
||||||
|
setAction(PageAction.EDIT);
|
||||||
|
setTitle('Edit Cluster');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (val: any, row: ListItem) => {
|
||||||
|
if (val === 'edit') {
|
||||||
|
handleEditUser(row);
|
||||||
|
} else if (val === 'delete') {
|
||||||
|
handleDelete({ ...row, name: row.name });
|
||||||
|
} else if (val === 'add') {
|
||||||
|
setOpen(true);
|
||||||
|
setCurrentData(row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEmpty = (type?: string) => {
|
||||||
|
if (type !== 'Table') return;
|
||||||
|
if (
|
||||||
|
!dataSource.loading &&
|
||||||
|
dataSource.loadend &&
|
||||||
|
!dataSource.dataList.length
|
||||||
|
) {
|
||||||
|
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
|
||||||
|
}
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<>
|
||||||
ghost
|
<PageContainer
|
||||||
header={{
|
ghost
|
||||||
title: intl.formatMessage({ id: 'menu.clusterManagement.clusters' }),
|
header={{
|
||||||
style: {
|
title: intl.formatMessage({
|
||||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
id: 'menu.clusterManagement.clusters'
|
||||||
},
|
}),
|
||||||
breadcrumb: {}
|
style: {
|
||||||
}}
|
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||||
extra={[]}
|
},
|
||||||
>
|
breadcrumb: {}
|
||||||
{intl.formatMessage({ id: 'menu.clusterManagement.clusters' })}
|
}}
|
||||||
</PageContainer>
|
extra={[]}
|
||||||
|
>
|
||||||
|
<PageTools
|
||||||
|
marginBottom={22}
|
||||||
|
left={
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
|
style={{ width: 300 }}
|
||||||
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
|
></Input>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
onClick={handleSearch}
|
||||||
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
|
></Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
right={
|
||||||
|
<Space size={20}>
|
||||||
|
<DropDownActions
|
||||||
|
menu={{
|
||||||
|
items: addActions,
|
||||||
|
onClick: handleClickDropdown
|
||||||
|
}}
|
||||||
|
trigger={['hover']}
|
||||||
|
placement="bottomRight"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<DownOutlined></DownOutlined>}
|
||||||
|
type="primary"
|
||||||
|
iconPosition="end"
|
||||||
|
>
|
||||||
|
Add Cluster
|
||||||
|
</Button>
|
||||||
|
</DropDownActions>
|
||||||
|
<Button
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
danger
|
||||||
|
onClick={handleDeleteBatch}
|
||||||
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{intl?.formatMessage?.({ id: 'common.button.delete' })}
|
||||||
|
{rowSelection.selectedRowKeys.length > 0 && (
|
||||||
|
<span>({rowSelection.selectedRowKeys?.length})</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
></PageTools>
|
||||||
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
|
<Table
|
||||||
|
dataSource={ClusterDataList}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
loading={dataSource.loading}
|
||||||
|
rowKey="id"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: queryParams.perPage,
|
||||||
|
current: queryParams.page,
|
||||||
|
total: dataSource.total,
|
||||||
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
|
onChange: handlePageChange
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
title="Name"
|
||||||
|
dataIndex="name"
|
||||||
|
key="name"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="Provider"
|
||||||
|
dataIndex="provider"
|
||||||
|
key="provider"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="Workers"
|
||||||
|
dataIndex="workers"
|
||||||
|
key="workers"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<WorkerWrapper>
|
||||||
|
<span className="worker">
|
||||||
|
<span className="dot ready"></span>
|
||||||
|
<span className="value">3</span>
|
||||||
|
</span>
|
||||||
|
<span className="worker">
|
||||||
|
<span className="dot error"></span>
|
||||||
|
<span className="value">1</span>
|
||||||
|
</span>
|
||||||
|
</WorkerWrapper>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="GPUs"
|
||||||
|
dataIndex="gpus"
|
||||||
|
key="gpus"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Column
|
||||||
|
title="Deployments"
|
||||||
|
dataIndex="deployments"
|
||||||
|
key="deployments"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="Replicas"
|
||||||
|
dataIndex="replicas"
|
||||||
|
key="replicas"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<WorkerWrapper>
|
||||||
|
<span className="worker">
|
||||||
|
<span className="dot ready"></span>
|
||||||
|
<span className="value">3</span>
|
||||||
|
</span>
|
||||||
|
<span className="worker">
|
||||||
|
<span className="dot error"></span>
|
||||||
|
<span className="value">1</span>
|
||||||
|
</span>
|
||||||
|
<span className="worker">
|
||||||
|
<span className="dot transition"></span>
|
||||||
|
<span className="value">1</span>
|
||||||
|
</span>
|
||||||
|
</WorkerWrapper>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Column
|
||||||
|
title={intl.formatMessage({ id: 'common.table.operation' })}
|
||||||
|
key="operation"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record: ListItem) => {
|
||||||
|
return (
|
||||||
|
<DropdownButtons
|
||||||
|
items={ActionList}
|
||||||
|
onSelect={(val) => handleSelect(val, record)}
|
||||||
|
></DropdownButtons>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</ConfigProvider>
|
||||||
|
</PageContainer>
|
||||||
|
<AddCluster
|
||||||
|
clusterType={clusterType}
|
||||||
|
open={openAddModal}
|
||||||
|
action={action}
|
||||||
|
title={title}
|
||||||
|
data={currentData}
|
||||||
|
onCancel={handleModalCancel}
|
||||||
|
onOk={handleModalOk}
|
||||||
|
></AddCluster>
|
||||||
|
<AddWorker open={open} onCancel={() => setOpen(false)}></AddWorker>
|
||||||
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Cluster;
|
export default Credentials;
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import ModalFooter from '@/components/modal-footer';
|
||||||
|
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ClusterFormData as FormData,
|
||||||
|
ClusterListItem as ListItem
|
||||||
|
} from '../config/types';
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
title: string;
|
||||||
|
action: PageActionType;
|
||||||
|
open: boolean;
|
||||||
|
clusterType: string; // 'kubernetes' | 'custom'
|
||||||
|
onOk: (values: FormData) => void;
|
||||||
|
data?: ListItem;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
const AddCluster: React.FC<AddModalProps> = ({
|
||||||
|
title,
|
||||||
|
action,
|
||||||
|
open,
|
||||||
|
clusterType,
|
||||||
|
onOk,
|
||||||
|
data,
|
||||||
|
onCancel
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const handleSumit = () => {
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GSDrawer
|
||||||
|
title={title}
|
||||||
|
open={open}
|
||||||
|
onClose={onCancel}
|
||||||
|
destroyOnClose={true}
|
||||||
|
closeIcon={false}
|
||||||
|
maskClosable={false}
|
||||||
|
keyboard={false}
|
||||||
|
width={600}
|
||||||
|
styles={{}}
|
||||||
|
footer={
|
||||||
|
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={onOk} preserve={false}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: intl.formatMessage({ id: 'common.table.name' })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
|
required
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
{clusterType === 'kubernetes' && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="provider"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: 'Provider'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
label="Provider"
|
||||||
|
required
|
||||||
|
options={['Digital Ocean', 'AutoDL', 'Custom'].map((item) => ({
|
||||||
|
label: item,
|
||||||
|
value: item
|
||||||
|
}))}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="region"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: 'Region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
label="Region"
|
||||||
|
required
|
||||||
|
options={['Hangzhou', 'Guangzhou', 'Shenzhen'].map((item) => ({
|
||||||
|
label: item,
|
||||||
|
value: item
|
||||||
|
}))}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="gpuPlan"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: 'GPU Plan'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
label="GPU Plan"
|
||||||
|
required
|
||||||
|
options={['A100', 'V100', 'T4'].map((item) => ({
|
||||||
|
label: item,
|
||||||
|
value: item
|
||||||
|
}))}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="workers"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: 'Workers'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Number label="Worker Number" required></SealInput.Number>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||||
|
<SealInput.TextArea
|
||||||
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</GSDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCluster;
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import ModalFooter from '@/components/modal-footer';
|
||||||
|
import ScrollerModal from '@/components/scroller-modal';
|
||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import { PageAction, PasswordReg } from '@/config';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { FormData, ListItem } from '../config/types';
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
title: string;
|
||||||
|
action: PageActionType;
|
||||||
|
open: boolean;
|
||||||
|
onOk: (values: FormData) => void;
|
||||||
|
data?: ListItem;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
const AddModal: React.FC<AddModalProps> = ({
|
||||||
|
title,
|
||||||
|
action,
|
||||||
|
open,
|
||||||
|
onOk,
|
||||||
|
data,
|
||||||
|
onCancel
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const handleSumit = () => {
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollerModal
|
||||||
|
title={title}
|
||||||
|
open={open}
|
||||||
|
centered={true}
|
||||||
|
onOk={handleSumit}
|
||||||
|
onCancel={onCancel}
|
||||||
|
destroyOnClose={true}
|
||||||
|
closeIcon={false}
|
||||||
|
maskClosable={false}
|
||||||
|
keyboard={false}
|
||||||
|
width={600}
|
||||||
|
styles={{}}
|
||||||
|
footer={
|
||||||
|
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={onOk} preserve={false}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: intl.formatMessage({ id: 'common.table.name' })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Input
|
||||||
|
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
|
required
|
||||||
|
></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="provider"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: intl.formatMessage(
|
||||||
|
{ id: 'common.form.rule.input' },
|
||||||
|
{
|
||||||
|
name: 'Provider'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
label="Provider"
|
||||||
|
required
|
||||||
|
options={['Digital Ocean', 'AutoDL', 'Custom'].map((item) => ({
|
||||||
|
label: item,
|
||||||
|
value: item
|
||||||
|
}))}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="access_key"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: action === PageAction.CREATE,
|
||||||
|
pattern: PasswordReg,
|
||||||
|
message: intl.formatMessage({ id: 'users.form.rule.password' })
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Password
|
||||||
|
label="Access Key"
|
||||||
|
required={action === PageAction.CREATE}
|
||||||
|
></SealInput.Password>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="secret_key"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: action === PageAction.CREATE,
|
||||||
|
pattern: PasswordReg,
|
||||||
|
message: intl.formatMessage({ id: 'users.form.rule.password' })
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInput.Password
|
||||||
|
label="Secret Key"
|
||||||
|
required={action === PageAction.CREATE}
|
||||||
|
></SealInput.Password>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||||
|
<SealInput.TextArea
|
||||||
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</ScrollerModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddModal;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export const ClusterDataList = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'custom-cluster',
|
||||||
|
provider: 'Custom',
|
||||||
|
clusterType: 'Kubernetes',
|
||||||
|
workers: 2,
|
||||||
|
gpus: 4,
|
||||||
|
deployments: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Digital-Ocean-cluster',
|
||||||
|
provider: 'Digital Ocean',
|
||||||
|
clusterType: 'Custom',
|
||||||
|
workers: 3,
|
||||||
|
gpus: 6,
|
||||||
|
deployments: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'AutoDL-cluster',
|
||||||
|
provider: 'AutoDL',
|
||||||
|
clusterType: 'Custom',
|
||||||
|
workers: 4,
|
||||||
|
gpus: 8,
|
||||||
|
deployments: 3
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
export interface FormData {
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
access_key: string;
|
||||||
|
secret_key: string;
|
||||||
|
description?: string;
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
access_key: string;
|
||||||
|
secret_key: string;
|
||||||
|
description?: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterListItem {
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
workers: number;
|
||||||
|
created_at: string;
|
||||||
|
gpus: number;
|
||||||
|
deployments: number;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterFormData {
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
credential: string;
|
||||||
|
region: string;
|
||||||
|
workers: number;
|
||||||
|
gpuPlan: string;
|
||||||
|
description?: string;
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
@@ -1,24 +1,345 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import DeleteModal from '@/components/delete-modal';
|
||||||
|
import DropDownActions from '@/components/drop-down-actions';
|
||||||
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import PageTools from '@/components/page-tools';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import type { PageActionType } from '@/config/types';
|
||||||
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
|
import {
|
||||||
|
CloudOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
KeyOutlined,
|
||||||
|
SyncOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import React from 'react';
|
import {
|
||||||
|
Button,
|
||||||
|
ConfigProvider,
|
||||||
|
Empty,
|
||||||
|
Input,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
message
|
||||||
|
} from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
createCredential,
|
||||||
|
deleteCredential,
|
||||||
|
queryCredentialList,
|
||||||
|
updateCredential
|
||||||
|
} from './apis';
|
||||||
|
import AddModal from './components/add-credential';
|
||||||
|
import { FormData, ListItem } from './config/types';
|
||||||
|
const { Column } = Table;
|
||||||
|
|
||||||
|
const ActionList = [
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
label: 'common.button.edit',
|
||||||
|
icon: <EditOutlined></EditOutlined>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
props: {
|
||||||
|
danger: true
|
||||||
|
},
|
||||||
|
label: 'common.button.delete',
|
||||||
|
icon: <DeleteOutlined></DeleteOutlined>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const addActions = [
|
||||||
|
{
|
||||||
|
label: 'Cloud Credential',
|
||||||
|
locale: false,
|
||||||
|
value: 'cloud_credential',
|
||||||
|
key: 'cloud_credential',
|
||||||
|
icon: <CloudOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SSH Key',
|
||||||
|
locale: false,
|
||||||
|
value: 'ssh_key',
|
||||||
|
key: 'ssh_key',
|
||||||
|
icon: <KeyOutlined />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const Credentials: React.FC = () => {
|
||||||
|
const {
|
||||||
|
dataSource,
|
||||||
|
rowSelection,
|
||||||
|
queryParams,
|
||||||
|
sortOrder,
|
||||||
|
modalRef,
|
||||||
|
handleDelete,
|
||||||
|
handleDeleteBatch,
|
||||||
|
fetchData,
|
||||||
|
handlePageChange,
|
||||||
|
handleTableChange,
|
||||||
|
handleSearch,
|
||||||
|
handleNameChange
|
||||||
|
} = useTableFetch<ListItem>({
|
||||||
|
fetchAPI: queryCredentialList,
|
||||||
|
deleteAPI: deleteCredential,
|
||||||
|
contentForDelete: 'users.table.user'
|
||||||
|
});
|
||||||
|
|
||||||
const Credential: React.FC = () => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
|
|
||||||
|
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
||||||
|
const [title, setTitle] = useState<string>('');
|
||||||
|
const [currentData, setCurrentData] = useState<ListItem | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddUser = () => {
|
||||||
|
setOpenAddModal(true);
|
||||||
|
setAction(PageAction.CREATE);
|
||||||
|
setTitle('Add Credential');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOk = async (data: FormData) => {
|
||||||
|
const params = {
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (action === PageAction.EDIT) {
|
||||||
|
await updateCredential({
|
||||||
|
data: {
|
||||||
|
...params,
|
||||||
|
id: currentData?.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createCredential({ data: params });
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
setOpenAddModal(false);
|
||||||
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
} catch (error) {
|
||||||
|
setOpenAddModal(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
console.log('handleModalCancel');
|
||||||
|
setOpenAddModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditUser = (row: ListItem) => {
|
||||||
|
setCurrentData(row);
|
||||||
|
setOpenAddModal(true);
|
||||||
|
setAction(PageAction.EDIT);
|
||||||
|
setTitle('Edit Credential');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (val: any, row: ListItem) => {
|
||||||
|
if (val === 'edit') {
|
||||||
|
handleEditUser(row);
|
||||||
|
} else if (val === 'delete') {
|
||||||
|
handleDelete({ ...row, name: row.name });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEmpty = (type?: string) => {
|
||||||
|
if (type !== 'Table') return;
|
||||||
|
if (
|
||||||
|
!dataSource.loading &&
|
||||||
|
dataSource.loadend &&
|
||||||
|
!dataSource.dataList.length
|
||||||
|
) {
|
||||||
|
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
|
||||||
|
}
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickDropdown = (e: any) => {
|
||||||
|
handleAddUser();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<>
|
||||||
ghost
|
<PageContainer
|
||||||
header={{
|
ghost
|
||||||
title: intl.formatMessage({ id: 'menu.clusterManagement.credentials' }),
|
header={{
|
||||||
style: {
|
title: intl.formatMessage({
|
||||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
id: 'menu.clusterManagement.credentials'
|
||||||
},
|
}),
|
||||||
breadcrumb: {}
|
style: {
|
||||||
}}
|
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||||
extra={[]}
|
},
|
||||||
>
|
breadcrumb: {}
|
||||||
{intl.formatMessage({ id: 'menu.clusterManagement.credentials' })}
|
}}
|
||||||
</PageContainer>
|
extra={[]}
|
||||||
|
>
|
||||||
|
<PageTools
|
||||||
|
marginBottom={22}
|
||||||
|
left={
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
|
style={{ width: 300 }}
|
||||||
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
|
></Input>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
onClick={handleSearch}
|
||||||
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
|
></Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
right={
|
||||||
|
<Space size={20}>
|
||||||
|
<DropDownActions
|
||||||
|
menu={{
|
||||||
|
items: addActions,
|
||||||
|
onClick: handleClickDropdown
|
||||||
|
}}
|
||||||
|
trigger={['hover']}
|
||||||
|
placement="bottomRight"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<DownOutlined></DownOutlined>}
|
||||||
|
type="primary"
|
||||||
|
iconPosition="end"
|
||||||
|
>
|
||||||
|
Add Credential
|
||||||
|
</Button>
|
||||||
|
</DropDownActions>
|
||||||
|
<Button
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
danger
|
||||||
|
onClick={handleDeleteBatch}
|
||||||
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{intl?.formatMessage?.({ id: 'common.button.delete' })}
|
||||||
|
{rowSelection.selectedRowKeys.length > 0 && (
|
||||||
|
<span>({rowSelection.selectedRowKeys?.length})</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
></PageTools>
|
||||||
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
loading={dataSource.loading}
|
||||||
|
rowKey="id"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: queryParams.perPage,
|
||||||
|
current: queryParams.page,
|
||||||
|
total: dataSource.total,
|
||||||
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
|
onChange: handlePageChange
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Column
|
||||||
|
title="Name"
|
||||||
|
dataIndex="name"
|
||||||
|
key="name"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="Provider"
|
||||||
|
dataIndex="provider"
|
||||||
|
key="provider"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title={intl.formatMessage({ id: 'common.table.createTime' })}
|
||||||
|
dataIndex="created_at"
|
||||||
|
key="createTime"
|
||||||
|
defaultSortOrder="descend"
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
showSorterTooltip={false}
|
||||||
|
sorter={false}
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="Description"
|
||||||
|
dataIndex="description"
|
||||||
|
key="description"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost minWidth={20}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title={intl.formatMessage({ id: 'common.table.operation' })}
|
||||||
|
key="operation"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record: ListItem) => {
|
||||||
|
return (
|
||||||
|
<DropdownButtons
|
||||||
|
items={ActionList}
|
||||||
|
onSelect={(val) => handleSelect(val, record)}
|
||||||
|
></DropdownButtons>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</ConfigProvider>
|
||||||
|
</PageContainer>
|
||||||
|
<AddModal
|
||||||
|
open={openAddModal}
|
||||||
|
action={action}
|
||||||
|
title={title}
|
||||||
|
data={currentData}
|
||||||
|
onCancel={handleModalCancel}
|
||||||
|
onOk={handleModalOk}
|
||||||
|
></AddModal>
|
||||||
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Credential;
|
export default Credentials;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import SealCascader from '@/components/seal-form/seal-cascader';
|
|||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import TooltipList from '@/components/tooltip-list';
|
import TooltipList from '@/components/tooltip-list';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
@@ -24,8 +25,10 @@ import {
|
|||||||
backendLabelMap,
|
backendLabelMap,
|
||||||
backendOptionsMap,
|
backendOptionsMap,
|
||||||
backendParamsHolderTips,
|
backendParamsHolderTips,
|
||||||
|
backendTipsList,
|
||||||
getBackendParamsTips,
|
getBackendParamsTips,
|
||||||
modelCategories,
|
modelCategories,
|
||||||
|
modelSourceMap,
|
||||||
placementStrategyOptions
|
placementStrategyOptions
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { useFormContext } from '../config/form-context';
|
import { useFormContext } from '../config/form-context';
|
||||||
@@ -35,6 +38,8 @@ import { FormData } from '../config/types';
|
|||||||
import vllmConfig from '../config/vllm-config';
|
import vllmConfig from '../config/vllm-config';
|
||||||
import dataformStyles from '../style/data-form.less';
|
import dataformStyles from '../style/data-form.less';
|
||||||
import GPUCard from './gpu-card';
|
import GPUCard from './gpu-card';
|
||||||
|
import Performance from './performance';
|
||||||
|
import Scaling from './scaling';
|
||||||
|
|
||||||
interface AdvanceConfigProps {
|
interface AdvanceConfigProps {
|
||||||
isGGUF: boolean;
|
isGGUF: boolean;
|
||||||
@@ -42,6 +47,8 @@ interface AdvanceConfigProps {
|
|||||||
gpuOptions: Array<any>;
|
gpuOptions: Array<any>;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
source: string;
|
source: string;
|
||||||
|
backendOptions?: Global.BaseOption<string>[];
|
||||||
|
handleBackendChange?: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const placementStrategyTips = [
|
const placementStrategyTips = [
|
||||||
@@ -92,7 +99,7 @@ const CheckboxField: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||||
const { form, isGGUF, gpuOptions, source } = props;
|
const { form, isGGUF, gpuOptions, source, backendOptions, action } = props;
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const wokerSelector = Form.useWatch('worker_selector', form);
|
const wokerSelector = Form.useWatch('worker_selector', form);
|
||||||
@@ -205,7 +212,56 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
options={modelCategories}
|
options={modelCategories}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="scheduleType">
|
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||||
|
<SealSelect
|
||||||
|
required
|
||||||
|
onChange={props.handleBackendChange}
|
||||||
|
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||||
|
description={<TooltipList list={backendTipsList}></TooltipList>}
|
||||||
|
options={
|
||||||
|
backendOptions ?? [
|
||||||
|
{
|
||||||
|
label: backendLabelMap[backendOptionsMap.llamaBox],
|
||||||
|
value: backendOptionsMap.llamaBox,
|
||||||
|
disabled:
|
||||||
|
props.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: !isGGUF
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: backendLabelMap[backendOptionsMap.vllm],
|
||||||
|
value: backendOptionsMap.vllm,
|
||||||
|
disabled:
|
||||||
|
props.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: isGGUF
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: backendLabelMap[backendOptionsMap.ascendMindie],
|
||||||
|
value: backendOptionsMap.ascendMindie,
|
||||||
|
disabled:
|
||||||
|
props.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: isGGUF
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: backendLabelMap[backendOptionsMap.voxBox],
|
||||||
|
value: backendOptionsMap.voxBox,
|
||||||
|
disabled:
|
||||||
|
props.source === modelSourceMap.local_path_value
|
||||||
|
? false
|
||||||
|
: props.source === modelSourceMap.ollama_library_value ||
|
||||||
|
isGGUF
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
action === PageAction.EDIT &&
|
||||||
|
props.source !== modelSourceMap.local_path_value
|
||||||
|
}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
{/* <Form.Item name="scheduleType">
|
||||||
<SealSelect
|
<SealSelect
|
||||||
onChange={handleScheduleTypeChange}
|
onChange={handleScheduleTypeChange}
|
||||||
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
||||||
@@ -225,7 +281,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item> */}
|
||||||
{scheduleType === 'auto' && (
|
{scheduleType === 'auto' && (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData> name="placement_strategy">
|
<Form.Item<FormData> name="placement_strategy">
|
||||||
@@ -489,6 +545,32 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
style={{ fontWeight: 'var(--font-weight-medium)' }}
|
||||||
|
className="font-size-14"
|
||||||
|
>
|
||||||
|
Performance
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
forceRender: true,
|
||||||
|
children: <Performance></Performance>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
style={{ fontWeight: 'var(--font-weight-medium)' }}
|
||||||
|
className="font-size-14"
|
||||||
|
>
|
||||||
|
Scaling
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
forceRender: true,
|
||||||
|
children: <Scaling></Scaling>
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
label: (
|
label: (
|
||||||
@@ -527,6 +609,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
expandIconPosition="start"
|
expandIconPosition="start"
|
||||||
bordered={false}
|
bordered={false}
|
||||||
ghost
|
ghost
|
||||||
|
accordion
|
||||||
destroyInactivePanel={false}
|
destroyInactivePanel={false}
|
||||||
className={dataformStyles['advanced-collapse']}
|
className={dataformStyles['advanced-collapse']}
|
||||||
expandIcon={({ isActive }) => (
|
expandIcon={({ isActive }) => (
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import TooltipList from '@/components/tooltip-list';
|
|
||||||
import { PageAction } from '@/config';
|
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
@@ -9,9 +7,7 @@ import { Form } from 'antd';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
import {
|
import {
|
||||||
backendLabelMap,
|
|
||||||
backendOptionsMap,
|
backendOptionsMap,
|
||||||
backendTipsList,
|
|
||||||
excludeFields,
|
excludeFields,
|
||||||
modelSourceMap,
|
modelSourceMap,
|
||||||
sourceOptions
|
sourceOptions
|
||||||
@@ -254,7 +250,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
<HuggingFaceForm></HuggingFaceForm>
|
<HuggingFaceForm></HuggingFaceForm>
|
||||||
<LocalPathForm></LocalPathForm>
|
<LocalPathForm></LocalPathForm>
|
||||||
</FormInnerContext.Provider>
|
</FormInnerContext.Provider>
|
||||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
{/* <Form.Item name="backend" rules={[{ required: true }]}>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
required
|
required
|
||||||
onChange={handleBackendChange}
|
onChange={handleBackendChange}
|
||||||
@@ -302,9 +298,9 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
props.source !== modelSourceMap.local_path_value
|
props.source !== modelSourceMap.local_path_value
|
||||||
}
|
}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item> */}
|
||||||
<CatalogFrom></CatalogFrom>
|
<CatalogFrom></CatalogFrom>
|
||||||
<Form.Item<FormData>
|
{/* <Form.Item<FormData>
|
||||||
name="replicas"
|
name="replicas"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@@ -325,7 +321,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
min={0}
|
min={0}
|
||||||
></SealInput.Number>
|
></SealInput.Number>
|
||||||
</Form.Item>
|
</Form.Item> */}
|
||||||
<Form.Item<FormData> name="description">
|
<Form.Item<FormData> name="description">
|
||||||
<SealInput.TextArea
|
<SealInput.TextArea
|
||||||
scaleSize={true}
|
scaleSize={true}
|
||||||
@@ -340,6 +336,8 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
isGGUF={isGGUF}
|
isGGUF={isGGUF}
|
||||||
action={action}
|
action={action}
|
||||||
source={props.source}
|
source={props.source}
|
||||||
|
backendOptions={backendOptions}
|
||||||
|
handleBackendChange={handleBackendChange}
|
||||||
></AdvanceConfig>
|
></AdvanceConfig>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
import ColumnWrapper from './column-wrapper';
|
import ColumnWrapper from './column-wrapper';
|
||||||
import CompatibilityAlert from './compatible-alert';
|
import CompatibilityAlert from './compatible-alert';
|
||||||
import DataForm from './data-form';
|
import DataForm from './data-form';
|
||||||
import HFModelFile from './hf-model-file';
|
import GGUFResult from './gguf-result';
|
||||||
import ModelCard from './model-card';
|
import ModelCard from './model-card';
|
||||||
import SearchModel from './search-model';
|
import SearchModel from './search-model';
|
||||||
import Separator from './separator';
|
import Separator from './separator';
|
||||||
@@ -556,16 +556,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
modelSource={props.source}
|
modelSource={props.source}
|
||||||
setIsGGUF={handleSetIsGGUF}
|
setIsGGUF={handleSetIsGGUF}
|
||||||
></ModelCard>
|
></ModelCard>
|
||||||
{isGGUF && (
|
{isGGUF && <GGUFResult></GGUFResult>}
|
||||||
<HFModelFile
|
|
||||||
ref={modelFileRef}
|
|
||||||
selectedModel={selectedModel}
|
|
||||||
modelSource={props.source}
|
|
||||||
onSelectFile={handleSelectModelFile}
|
|
||||||
collapsed={collapsed}
|
|
||||||
gpuOptions={props.gpuOptions}
|
|
||||||
></HFModelFile>
|
|
||||||
)}
|
|
||||||
</ColumnWrapper>
|
</ColumnWrapper>
|
||||||
<Separator></Separator>
|
<Separator></Separator>
|
||||||
</ColWrapper>
|
</ColWrapper>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { WarningOutlined } from '@ant-design/icons';
|
||||||
|
import { Result } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const GGUFResult: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="info"
|
||||||
|
icon={
|
||||||
|
<WarningOutlined
|
||||||
|
style={{ color: 'var(--ant-color-text-quaternary)' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title={false}
|
||||||
|
subTitle="GGUF model is not supported yet."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GGUFResult;
|
||||||
@@ -12,6 +12,7 @@ import useDownloadStream from '@/hooks/use-download-stream';
|
|||||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import {
|
import {
|
||||||
|
CodeOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
HddFilled,
|
HddFilled,
|
||||||
@@ -270,6 +271,12 @@ const childActionList = [
|
|||||||
],
|
],
|
||||||
icon: <IconFont type="icon-logs" />
|
icon: <IconFont type="icon-logs" />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Terminal',
|
||||||
|
locale: false,
|
||||||
|
key: 'terminal',
|
||||||
|
icon: <CodeOutlined />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.downloadLog',
|
label: 'common.button.downloadLog',
|
||||||
key: 'download',
|
key: 'download',
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import TooltipList from '@/components/tooltip-list';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Checkbox, Form, Tooltip } from 'antd';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
|
|
||||||
|
const scheduleTypeTips = [
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.form.scheduletype.auto',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.form.scheduletype.auto.tips'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.form.scheduletype.manual',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.form.scheduletype.manual.tips'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const CheckboxField: React.FC<{
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
checked?: boolean;
|
||||||
|
onChange?: (e: CheckboxChangeEvent) => void;
|
||||||
|
}> = ({ title, label, checked, onChange }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox className="p-l-6" checked={checked} onChange={onChange}>
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>{label}</span>
|
||||||
|
<QuestionCircleOutlined
|
||||||
|
className="m-l-4"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Performance: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { onValuesChange, onQuantizationChange, source, quantizationOptions } =
|
||||||
|
useFormContext();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
|
const handleScheduleTypeChange = (value: string) => {
|
||||||
|
if (value === 'auto') {
|
||||||
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnQuantizationChange = (val: any) => {
|
||||||
|
onQuantizationChange?.(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item name="scheduleType">
|
||||||
|
<SealSelect
|
||||||
|
onChange={handleScheduleTypeChange}
|
||||||
|
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
||||||
|
description={<TooltipList list={scheduleTypeTips}></TooltipList>}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'models.form.scheduletype.auto'
|
||||||
|
}),
|
||||||
|
value: 'auto'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'models.form.scheduletype.manual'
|
||||||
|
}),
|
||||||
|
value: 'manual'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="quantization"
|
||||||
|
key="quantization"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('select', 'quantization', false)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealSelect
|
||||||
|
filterOption
|
||||||
|
defaultActiveFirstOption
|
||||||
|
disabled={false}
|
||||||
|
options={quantizationOptions}
|
||||||
|
onChange={handleOnQuantizationChange}
|
||||||
|
label="Quantization"
|
||||||
|
required
|
||||||
|
></SealSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="optimize_long_prompt"
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<CheckboxField
|
||||||
|
title="Optimize long prompt tips"
|
||||||
|
label="Optimize Long Prompt"
|
||||||
|
></CheckboxField>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="enable_speculative_decoding"
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<CheckboxField
|
||||||
|
title="Enable speculative decoding tips"
|
||||||
|
label="Enable Speculative Decoding"
|
||||||
|
></CheckboxField>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Performance;
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import SealInputNumber from '@/components/seal-form/input-number';
|
||||||
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Checkbox, Form, Tooltip } from 'antd';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
|
|
||||||
|
const scheduleTypeTips = [
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.form.scheduletype.auto',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.form.scheduletype.auto.tips'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.form.scheduletype.manual',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.form.scheduletype.manual.tips'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const CheckboxField: React.FC<{
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
checked?: boolean;
|
||||||
|
onChange?: (e: CheckboxChangeEvent) => void;
|
||||||
|
}> = ({ title, label, checked, onChange }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox className="p-l-6" checked={checked} onChange={onChange}>
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>{label}</span>
|
||||||
|
<QuestionCircleOutlined
|
||||||
|
className="m-l-4"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Scaling: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { onValuesChange, onQuantizationChange, source, quantizationOptions } =
|
||||||
|
useFormContext();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
|
const handleScheduleTypeChange = (value: string) => {
|
||||||
|
if (value === 'auto') {
|
||||||
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnQuantizationChange = (val: any) => {
|
||||||
|
onQuantizationChange?.(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="enable_auto_scaling"
|
||||||
|
valuePropName="checked"
|
||||||
|
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<CheckboxField
|
||||||
|
title="Enable auto scaling tips"
|
||||||
|
label="Enable Auto Scaling"
|
||||||
|
></CheckboxField>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="min_replicas"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInputNumber
|
||||||
|
min={0}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
label="Min Replicas"
|
||||||
|
required
|
||||||
|
></SealInputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="max_replicas"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInputNumber
|
||||||
|
min={0}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
label="Max Replicas"
|
||||||
|
required
|
||||||
|
></SealInputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="scale_to_zero_window"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInputNumber
|
||||||
|
min={0}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
label="Scale to Zero Window"
|
||||||
|
required
|
||||||
|
></SealInputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="scale_down_window"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInputNumber
|
||||||
|
min={0}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
label="Scale Down Window"
|
||||||
|
required
|
||||||
|
></SealInputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="scale_up_window"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SealInputNumber
|
||||||
|
min={0}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
label="Scale Up Window"
|
||||||
|
required
|
||||||
|
></SealInputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Scaling;
|
||||||
@@ -2,7 +2,7 @@ import { getRequestId, setRquestId } from '@/atoms/models';
|
|||||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Checkbox, Pagination, Select, Tooltip } from 'antd';
|
import { Pagination, Select, Tooltip } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -167,7 +167,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
search: {
|
search: {
|
||||||
query: searchInputRef.current || '',
|
query: searchInputRef.current || '',
|
||||||
sort: sort,
|
sort: sort,
|
||||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
tags: [],
|
||||||
task: HuggingFaceTaskMap[filterTaskRef.current] || task
|
task: HuggingFaceTaskMap[filterTaskRef.current] || task
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -199,7 +199,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
Name: `${searchInputRef.current}`,
|
Name: `${searchInputRef.current}`,
|
||||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
tags: [],
|
||||||
tasks: filterTaskRef.current
|
tasks: filterTaskRef.current
|
||||||
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
|
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
|
||||||
: [],
|
: [],
|
||||||
@@ -562,13 +562,13 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
size="middle"
|
size="middle"
|
||||||
style={{ width: '150px' }}
|
style={{ width: '150px' }}
|
||||||
></Select>
|
></Select>
|
||||||
<Checkbox
|
{/* <Checkbox
|
||||||
onChange={handleFilterGGUFChange}
|
onChange={handleFilterGGUFChange}
|
||||||
className="m-l-8"
|
className="m-l-8"
|
||||||
checked={filterGGUFRef.current}
|
checked={filterGGUFRef.current}
|
||||||
>
|
>
|
||||||
{renderGGUFTips}
|
{renderGGUFTips}
|
||||||
</Checkbox>
|
</Checkbox> */}
|
||||||
</span>
|
</span>
|
||||||
<PaginationMain
|
<PaginationMain
|
||||||
simple={{ readOnly: true }}
|
simple={{ readOnly: true }}
|
||||||
|
|||||||
@@ -606,7 +606,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 400,
|
width: 400,
|
||||||
span: 6,
|
span: 5,
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<span className="flex-center" style={{ maxWidth: '100%' }}>
|
<span className="flex-center" style={{ maxWidth: '100%' }}>
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
@@ -616,11 +616,22 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Cluster',
|
||||||
|
dataIndex: 'cluster',
|
||||||
|
key: 'cluster',
|
||||||
|
span: 4,
|
||||||
|
render: (text: string, record: ListItem) => (
|
||||||
|
<span className="flex flex-column" style={{ width: '100%' }}>
|
||||||
|
Custom
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'models.form.source' }),
|
title: intl.formatMessage({ id: 'models.form.source' }),
|
||||||
dataIndex: 'source',
|
dataIndex: 'source',
|
||||||
key: 'source',
|
key: 'source',
|
||||||
span: 7,
|
span: 4,
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<span className="flex flex-column" style={{ width: '100%' }}>
|
<span className="flex flex-column" style={{ width: '100%' }}>
|
||||||
<AutoTooltip ghost>{generateSource(record)}</AutoTooltip>
|
<AutoTooltip ghost>{generateSource(record)}</AutoTooltip>
|
||||||
@@ -767,7 +778,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
<Space>
|
<Space>
|
||||||
<Input
|
<Input
|
||||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
style={{ width: 230 }}
|
style={{ width: 200 }}
|
||||||
size="large"
|
size="large"
|
||||||
allowClear
|
allowClear
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
@@ -784,6 +795,15 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
onChange={handleCategoryChange}
|
onChange={handleCategoryChange}
|
||||||
options={modelCategories.filter((item) => item.value)}
|
options={modelCategories.filter((item) => item.value)}
|
||||||
></Select>
|
></Select>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder="Filter by worker"
|
||||||
|
style={{ width: 180 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
options={[]}
|
||||||
|
></Select>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ export const ActionList: ActionItem[] = [
|
|||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: icons.EditOutlined
|
icon: icons.EditOutlined
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'common.button.detail',
|
||||||
|
key: 'details',
|
||||||
|
icon: icons.FileTextOutlined
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'models.openinplayground',
|
label: 'models.openinplayground',
|
||||||
key: 'chat',
|
key: 'chat',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React from 'react';
|
|||||||
interface FormContextProps {
|
interface FormContextProps {
|
||||||
isGGUF?: boolean;
|
isGGUF?: boolean;
|
||||||
byBuiltIn?: boolean;
|
byBuiltIn?: boolean;
|
||||||
|
source?: string;
|
||||||
pageAction: PageActionType;
|
pageAction: PageActionType;
|
||||||
sizeOptions?: Global.BaseOption<number>[];
|
sizeOptions?: Global.BaseOption<number>[];
|
||||||
quantizationOptions?: Global.BaseOption<string>[];
|
quantizationOptions?: Global.BaseOption<string>[];
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ const HuggingFaceForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
label="Base Model"
|
||||||
required
|
required
|
||||||
disabled={pageAction === PageAction.CREATE}
|
disabled={pageAction === PageAction.CREATE}
|
||||||
onBlur={handleOnBlur}
|
onBlur={handleOnBlur}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{isGGUF && (
|
{/* {isGGUF && (
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="file_name"
|
name="file_name"
|
||||||
key="file_name"
|
key="file_name"
|
||||||
@@ -69,7 +69,7 @@ const HuggingFaceForm: React.FC = () => {
|
|||||||
onBlur={handleOnBlur}
|
onBlur={handleOnBlur}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)} */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
WindowsOutlined
|
WindowsOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tabs, TabsProps } from 'antd';
|
import { TabsProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MacOS from './add-worker-macos';
|
import MacOS from './add-worker-macos';
|
||||||
import ContainerInstall from './container-install';
|
import ContainerInstall from './container-install';
|
||||||
@@ -81,13 +81,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
footer={null}
|
footer={null}
|
||||||
>
|
>
|
||||||
<Tabs
|
<ContainerInstall token={token} />
|
||||||
size="small"
|
|
||||||
items={items}
|
|
||||||
activeKey={activeKey}
|
|
||||||
type="card"
|
|
||||||
onChange={(key) => setActiveKey(key)}
|
|
||||||
></Tabs>
|
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
import { FilterBar } from '@/components/page-tools';
|
import { FilterBar } from '@/components/page-tools';
|
||||||
import ProgressBar from '@/components/progress-bar';
|
import ProgressBar from '@/components/progress-bar';
|
||||||
import InfoColumn from '@/components/simple-table/info-column';
|
import InfoColumn from '@/components/simple-table/info-column';
|
||||||
import StatusTag from '@/components/status-tag';
|
import StatusTag from '@/components/status-tag';
|
||||||
import Hotkeys from '@/config/hotkeys';
|
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import {
|
import {
|
||||||
|
CodeOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
InfoCircleOutlined
|
InfoCircleOutlined
|
||||||
@@ -18,7 +19,6 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { ConfigProvider, Empty, Table, Tooltip, message } from 'antd';
|
import { ConfigProvider, Empty, Table, Tooltip, message } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import {
|
import {
|
||||||
WORKERS_API,
|
WORKERS_API,
|
||||||
deleteWorker,
|
deleteWorker,
|
||||||
@@ -65,6 +65,18 @@ const ActionList = [
|
|||||||
key: 'edit',
|
key: 'edit',
|
||||||
icon: <EditOutlined />
|
icon: <EditOutlined />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'common.button.logs',
|
||||||
|
locale: false,
|
||||||
|
key: 'logs',
|
||||||
|
icon: <IconFont type="icon-logs" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Terminal',
|
||||||
|
locale: false,
|
||||||
|
key: 'terminal',
|
||||||
|
icon: <CodeOutlined />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.delete',
|
label: 'common.button.delete',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
@@ -198,10 +210,6 @@ const Workers: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useHotkeys(Hotkeys.CREATE.join(','), handleAddWorker, {
|
|
||||||
enabled: !open
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageContainer
|
<PageContainer
|
||||||
@@ -216,6 +224,9 @@ const Workers: React.FC = () => {
|
|||||||
extra={[]}
|
extra={[]}
|
||||||
>
|
>
|
||||||
<FilterBar
|
<FilterBar
|
||||||
|
showSelect={true}
|
||||||
|
showPrimaryButton={false}
|
||||||
|
selectHolder="Filter by cluster"
|
||||||
marginBottom={22}
|
marginBottom={22}
|
||||||
marginTop={30}
|
marginTop={30}
|
||||||
buttonText={intl.formatMessage({ id: 'resources.button.create' })}
|
buttonText={intl.formatMessage({ id: 'resources.button.create' })}
|
||||||
@@ -224,7 +235,7 @@ const Workers: React.FC = () => {
|
|||||||
handleClickPrimary={handleAddWorker}
|
handleClickPrimary={handleAddWorker}
|
||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
width={{ input: 300 }}
|
width={{ input: 200 }}
|
||||||
></FilterBar>
|
></FilterBar>
|
||||||
<ConfigProvider renderEmpty={renderEmpty}>
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
Reference in New Issue
Block a user