feat: add cluster list
This commit is contained in:
@@ -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 { 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 [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 (
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({ id: 'menu.clusterManagement.clusters' }),
|
||||
style: {
|
||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||
},
|
||||
breadcrumb: {}
|
||||
}}
|
||||
extra={[]}
|
||||
>
|
||||
{intl.formatMessage({ id: 'menu.clusterManagement.clusters' })}
|
||||
</PageContainer>
|
||||
<>
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({
|
||||
id: 'menu.clusterManagement.clusters'
|
||||
}),
|
||||
style: {
|
||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||
},
|
||||
breadcrumb: {}
|
||||
}}
|
||||
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 { 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 [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 (
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({ id: 'menu.clusterManagement.credentials' }),
|
||||
style: {
|
||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||
},
|
||||
breadcrumb: {}
|
||||
}}
|
||||
extra={[]}
|
||||
>
|
||||
{intl.formatMessage({ id: 'menu.clusterManagement.credentials' })}
|
||||
</PageContainer>
|
||||
<>
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({
|
||||
id: 'menu.clusterManagement.credentials'
|
||||
}),
|
||||
style: {
|
||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||
},
|
||||
breadcrumb: {}
|
||||
}}
|
||||
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 SealSelect from '@/components/seal-form/seal-select';
|
||||
import TooltipList from '@/components/tooltip-list';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
@@ -24,8 +25,10 @@ import {
|
||||
backendLabelMap,
|
||||
backendOptionsMap,
|
||||
backendParamsHolderTips,
|
||||
backendTipsList,
|
||||
getBackendParamsTips,
|
||||
modelCategories,
|
||||
modelSourceMap,
|
||||
placementStrategyOptions
|
||||
} from '../config';
|
||||
import { useFormContext } from '../config/form-context';
|
||||
@@ -35,6 +38,8 @@ import { FormData } from '../config/types';
|
||||
import vllmConfig from '../config/vllm-config';
|
||||
import dataformStyles from '../style/data-form.less';
|
||||
import GPUCard from './gpu-card';
|
||||
import Performance from './performance';
|
||||
import Scaling from './scaling';
|
||||
|
||||
interface AdvanceConfigProps {
|
||||
isGGUF: boolean;
|
||||
@@ -42,6 +47,8 @@ interface AdvanceConfigProps {
|
||||
gpuOptions: Array<any>;
|
||||
action: PageActionType;
|
||||
source: string;
|
||||
backendOptions?: Global.BaseOption<string>[];
|
||||
handleBackendChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const placementStrategyTips = [
|
||||
@@ -92,7 +99,7 @@ const CheckboxField: React.FC<{
|
||||
};
|
||||
|
||||
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
const { form, isGGUF, gpuOptions, source } = props;
|
||||
const { form, isGGUF, gpuOptions, source, backendOptions, action } = props;
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const intl = useIntl();
|
||||
const wokerSelector = Form.useWatch('worker_selector', form);
|
||||
@@ -205,7 +212,56 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
options={modelCategories}
|
||||
></SealSelect>
|
||||
</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
|
||||
onChange={handleScheduleTypeChange}
|
||||
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
||||
@@ -225,7 +281,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
}
|
||||
]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
{scheduleType === 'auto' && (
|
||||
<>
|
||||
<Form.Item<FormData> name="placement_strategy">
|
||||
@@ -489,6 +545,32 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
</>
|
||||
);
|
||||
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',
|
||||
label: (
|
||||
@@ -527,6 +609,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
expandIconPosition="start"
|
||||
bordered={false}
|
||||
ghost
|
||||
accordion
|
||||
destroyInactivePanel={false}
|
||||
className={dataformStyles['advanced-collapse']}
|
||||
expandIcon={({ isActive }) => (
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import TooltipList from '@/components/tooltip-list';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
@@ -9,9 +7,7 @@ import { Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import {
|
||||
backendLabelMap,
|
||||
backendOptionsMap,
|
||||
backendTipsList,
|
||||
excludeFields,
|
||||
modelSourceMap,
|
||||
sourceOptions
|
||||
@@ -254,7 +250,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
<HuggingFaceForm></HuggingFaceForm>
|
||||
<LocalPathForm></LocalPathForm>
|
||||
</FormInnerContext.Provider>
|
||||
<Form.Item name="backend" rules={[{ required: true }]}>
|
||||
{/* <Form.Item name="backend" rules={[{ required: true }]}>
|
||||
<SealSelect
|
||||
required
|
||||
onChange={handleBackendChange}
|
||||
@@ -302,9 +298,9 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
props.source !== modelSourceMap.local_path_value
|
||||
}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
<CatalogFrom></CatalogFrom>
|
||||
<Form.Item<FormData>
|
||||
{/* <Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
@@ -325,7 +321,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
)}
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
<Form.Item<FormData> name="description">
|
||||
<SealInput.TextArea
|
||||
scaleSize={true}
|
||||
@@ -340,6 +336,8 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
isGGUF={isGGUF}
|
||||
action={action}
|
||||
source={props.source}
|
||||
backendOptions={backendOptions}
|
||||
handleBackendChange={handleBackendChange}
|
||||
></AdvanceConfig>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import CompatibilityAlert from './compatible-alert';
|
||||
import DataForm from './data-form';
|
||||
import HFModelFile from './hf-model-file';
|
||||
import GGUFResult from './gguf-result';
|
||||
import ModelCard from './model-card';
|
||||
import SearchModel from './search-model';
|
||||
import Separator from './separator';
|
||||
@@ -556,16 +556,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
||||
modelSource={props.source}
|
||||
setIsGGUF={handleSetIsGGUF}
|
||||
></ModelCard>
|
||||
{isGGUF && (
|
||||
<HFModelFile
|
||||
ref={modelFileRef}
|
||||
selectedModel={selectedModel}
|
||||
modelSource={props.source}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
collapsed={collapsed}
|
||||
gpuOptions={props.gpuOptions}
|
||||
></HFModelFile>
|
||||
)}
|
||||
{isGGUF && <GGUFResult></GGUFResult>}
|
||||
</ColumnWrapper>
|
||||
<Separator></Separator>
|
||||
</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 { convertFileSize } from '@/utils';
|
||||
import {
|
||||
CodeOutlined,
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
HddFilled,
|
||||
@@ -270,6 +271,12 @@ const childActionList = [
|
||||
],
|
||||
icon: <IconFont type="icon-logs" />
|
||||
},
|
||||
{
|
||||
label: 'Terminal',
|
||||
locale: false,
|
||||
key: 'terminal',
|
||||
icon: <CodeOutlined />
|
||||
},
|
||||
{
|
||||
label: 'common.button.downloadLog',
|
||||
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 { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Checkbox, Pagination, Select, Tooltip } from 'antd';
|
||||
import { Pagination, Select, Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
@@ -167,7 +167,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
search: {
|
||||
query: searchInputRef.current || '',
|
||||
sort: sort,
|
||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
||||
tags: [],
|
||||
task: HuggingFaceTaskMap[filterTaskRef.current] || task
|
||||
}
|
||||
};
|
||||
@@ -199,7 +199,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
try {
|
||||
const params = {
|
||||
Name: `${searchInputRef.current}`,
|
||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
||||
tags: [],
|
||||
tasks: filterTaskRef.current
|
||||
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
|
||||
: [],
|
||||
@@ -562,13 +562,13 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
size="middle"
|
||||
style={{ width: '150px' }}
|
||||
></Select>
|
||||
<Checkbox
|
||||
{/* <Checkbox
|
||||
onChange={handleFilterGGUFChange}
|
||||
className="m-l-8"
|
||||
checked={filterGGUFRef.current}
|
||||
>
|
||||
{renderGGUFTips}
|
||||
</Checkbox>
|
||||
</Checkbox> */}
|
||||
</span>
|
||||
<PaginationMain
|
||||
simple={{ readOnly: true }}
|
||||
|
||||
@@ -606,7 +606,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 400,
|
||||
span: 6,
|
||||
span: 5,
|
||||
render: (text: string, record: ListItem) => (
|
||||
<span className="flex-center" style={{ maxWidth: '100%' }}>
|
||||
<AutoTooltip ghost>
|
||||
@@ -616,11 +616,22 @@ const Models: React.FC<ModelsProps> = ({
|
||||
</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' }),
|
||||
dataIndex: 'source',
|
||||
key: 'source',
|
||||
span: 7,
|
||||
span: 4,
|
||||
render: (text: string, record: ListItem) => (
|
||||
<span className="flex flex-column" style={{ width: '100%' }}>
|
||||
<AutoTooltip ghost>{generateSource(record)}</AutoTooltip>
|
||||
@@ -767,7 +778,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
<Space>
|
||||
<Input
|
||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
style={{ width: 230 }}
|
||||
style={{ width: 200 }}
|
||||
size="large"
|
||||
allowClear
|
||||
onChange={handleNameChange}
|
||||
@@ -784,6 +795,15 @@ const Models: React.FC<ModelsProps> = ({
|
||||
onChange={handleCategoryChange}
|
||||
options={modelCategories.filter((item) => item.value)}
|
||||
></Select>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch={false}
|
||||
placeholder="Filter by worker"
|
||||
style={{ width: 180 }}
|
||||
size="large"
|
||||
maxTagCount={1}
|
||||
options={[]}
|
||||
></Select>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
|
||||
@@ -46,6 +46,11 @@ export const ActionList: ActionItem[] = [
|
||||
key: 'edit',
|
||||
icon: icons.EditOutlined
|
||||
},
|
||||
{
|
||||
label: 'common.button.detail',
|
||||
key: 'details',
|
||||
icon: icons.FileTextOutlined
|
||||
},
|
||||
{
|
||||
label: 'models.openinplayground',
|
||||
key: 'chat',
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
interface FormContextProps {
|
||||
isGGUF?: boolean;
|
||||
byBuiltIn?: boolean;
|
||||
source?: string;
|
||||
pageAction: PageActionType;
|
||||
sizeOptions?: Global.BaseOption<number>[];
|
||||
quantizationOptions?: Global.BaseOption<string>[];
|
||||
|
||||
@@ -45,13 +45,13 @@ const HuggingFaceForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.repoid' })}
|
||||
label="Base Model"
|
||||
required
|
||||
disabled={pageAction === PageAction.CREATE}
|
||||
onBlur={handleOnBlur}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
{isGGUF && (
|
||||
{/* {isGGUF && (
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
key="file_name"
|
||||
@@ -69,7 +69,7 @@ const HuggingFaceForm: React.FC = () => {
|
||||
onBlur={handleOnBlur}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
WindowsOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import { TabsProps } from 'antd';
|
||||
import React from 'react';
|
||||
import MacOS from './add-worker-macos';
|
||||
import ContainerInstall from './container-install';
|
||||
@@ -81,13 +81,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<Tabs
|
||||
size="small"
|
||||
items={items}
|
||||
activeKey={activeKey}
|
||||
type="card"
|
||||
onChange={(key) => setActiveKey(key)}
|
||||
></Tabs>
|
||||
<ContainerInstall token={token} />
|
||||
</ScrollerModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
import ProgressBar from '@/components/progress-bar';
|
||||
import InfoColumn from '@/components/simple-table/info-column';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import Hotkeys from '@/config/hotkeys';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import {
|
||||
CodeOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
InfoCircleOutlined
|
||||
@@ -18,7 +19,6 @@ import { useIntl } from '@umijs/max';
|
||||
import { ConfigProvider, Empty, Table, Tooltip, message } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import {
|
||||
WORKERS_API,
|
||||
deleteWorker,
|
||||
@@ -65,6 +65,18 @@ const ActionList = [
|
||||
key: 'edit',
|
||||
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',
|
||||
key: 'delete',
|
||||
@@ -198,10 +210,6 @@ const Workers: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
useHotkeys(Hotkeys.CREATE.join(','), handleAddWorker, {
|
||||
enabled: !open
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer
|
||||
@@ -216,6 +224,9 @@ const Workers: React.FC = () => {
|
||||
extra={[]}
|
||||
>
|
||||
<FilterBar
|
||||
showSelect={true}
|
||||
showPrimaryButton={false}
|
||||
selectHolder="Filter by cluster"
|
||||
marginBottom={22}
|
||||
marginTop={30}
|
||||
buttonText={intl.formatMessage({ id: 'resources.button.create' })}
|
||||
@@ -224,7 +235,7 @@ const Workers: React.FC = () => {
|
||||
handleClickPrimary={handleAddWorker}
|
||||
handleInputChange={handleNameChange}
|
||||
rowSelection={rowSelection}
|
||||
width={{ input: 300 }}
|
||||
width={{ input: 200 }}
|
||||
></FilterBar>
|
||||
<ConfigProvider renderEmpty={renderEmpty}>
|
||||
<Table
|
||||
|
||||
Reference in New Issue
Block a user