diff --git a/config/routes.ts b/config/routes.ts index 43703822..571250bb 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -161,9 +161,9 @@ export default [ name: 'clusters', path: '/cluster-management/clusters', key: 'clusters', - icon: 'icon-k8s-outline', - selectedIcon: 'icon-k8s-filled', - defaultIcon: 'icon-k8s-outline', + icon: 'icon-cluster2-outline', + selectedIcon: 'icon-cluster2-filled', + defaultIcon: 'icon-cluster2-outline', component: './cluster-management/clusters' }, { diff --git a/src/components/icon-font/icons.ts b/src/components/icon-font/icons.ts index fc8e022e..9ce0a092 100644 --- a/src/components/icon-font/icons.ts +++ b/src/components/icon-font/icons.ts @@ -6,6 +6,8 @@ import { EditOutlined, ExperimentOutlined, FileTextOutlined, + KubernetesOutlined, + ProfileOutlined, RetweetOutlined, ThunderboltOutlined } from '@ant-design/icons'; @@ -20,6 +22,8 @@ const icons = { DownloadOutlined: React.createElement(DownloadOutlined), FileTextOutlined: React.createElement(FileTextOutlined), ApiOutlined: React.createElement(ApiOutlined), + KubernetesOutlined: React.createElement(KubernetesOutlined), + ProfileOutlined: React.createElement(ProfileOutlined), Stop: React.createElement(IconFont, { type: 'icon-stop1' }), Play: React.createElement(IconFont, { type: 'icon-outline-play' }), Catalog: React.createElement(IconFont, { type: 'icon-catalog' }), @@ -29,7 +33,9 @@ const icons = { LocalPath: React.createElement(IconFont, { type: 'icon-hard-disk' }), Launch: React.createElement(IconFont, { type: 'icon-rocket-launch' }), Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }), - Docker: React.createElement(IconFont, { type: 'icon-docker' }) + Docker: React.createElement(IconFont, { type: 'icon-docker' }), + DigitalOcean: React.createElement(IconFont, { type: 'icon-digitalocean' }), + DetailInfo: React.createElement(IconFont, { type: 'icon-detail-info' }) }; export default icons; diff --git a/src/components/icon-font/index.tsx b/src/components/icon-font/index.tsx index ca2555d4..c1218d42 100644 --- a/src/components/icon-font/index.tsx +++ b/src/components/icon-font/index.tsx @@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; // import './iconfont/iconfont.js'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4613488_arejw0wk0kd.js' + scriptUrl: '//at.alicdn.com/t/c/font_4613488_wy0l8ooa03c.js' }); export default IconFont; diff --git a/src/components/scroller-modal/index.tsx b/src/components/scroller-modal/index.tsx index dcee81fc..79051bc9 100644 --- a/src/components/scroller-modal/index.tsx +++ b/src/components/scroller-modal/index.tsx @@ -56,10 +56,12 @@ const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => { body: { padding: '0' }, - footer: { - padding: '12px 24px 24px', - margin: '0' - } + footer: props.footer + ? { + padding: '12px 24px 24px', + margin: '0' + } + : {} }} > = (props) => { }, [options, intl]); useEffect(() => { - if (isNotEmptyValue(props.value) || (allowNull && props.value === null)) { + if ( + isNotEmptyValue(props.value) || + (allowNull && (props.value === null || props.value === undefined)) + ) { setIsFocus(true); } }, [props.value, allowNull]); diff --git a/src/config/global.d.ts b/src/config/global.d.ts index 11fe3b36..cb5bd4d5 100644 --- a/src/config/global.d.ts +++ b/src/config/global.d.ts @@ -24,18 +24,18 @@ declare namespace Global { avatar_url?: string; } - interface BaseListItem { + type BaseListItem> = { key: string; locale?: boolean; value: T; - } + } & U; - interface BaseOption { + type BaseOption> = { label: string; locale?: boolean; value: T; meta?: Record; - } + } & U; interface HintOptions { label: string; diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts index 7666c496..6bcba5b0 100644 --- a/src/pages/cluster-management/apis/index.ts +++ b/src/pages/cluster-management/apis/index.ts @@ -8,13 +8,13 @@ import { NodePoolListItem } from '../config/types'; -export const CREDENTIALS_API = '/credentials'; +export const CREDENTIALS_API = '/cloud-credentials'; export const CLUSTERS_API = '/clusters'; export const WORKER_POOLS_API = '/worker-pools'; -export const CLUSTER_TOKEN = 'registration_token'; +export const CLUSTER_TOKEN = 'registration-token'; // ===================== Credentials ===================== @@ -89,8 +89,8 @@ export async function queryClusterDetail(id: number) { }); } -export async function queryClusterToken(id: number) { - return request(`${CLUSTERS_API}/${id}/${CLUSTER_TOKEN}`, { +export async function queryClusterToken(params: { id: number }) { + return request(`${CLUSTERS_API}/${params.id}/${CLUSTER_TOKEN}`, { method: 'GET' }); } diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 95e2f10f..77629cfe 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -1,5 +1,4 @@ import DeleteModal from '@/components/delete-modal'; -import IconFont from '@/components/icon-font'; import { FilterBar } from '@/components/page-tools'; import CardList from '@/components/templates/card-list'; import CardSkeleton from '@/components/templates/card-skelton'; @@ -7,12 +6,6 @@ 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, - EditOutlined, - KubernetesOutlined, - ProfileOutlined -} from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; import { Table, message } from 'antd'; @@ -21,76 +14,19 @@ import { createCluster, deleteCluster, queryClusterList, + queryClusterToken, updateCluster } from './apis'; import AddCluster from './components/add-cluster'; import ClusterDetailModal from './components/cluster-detail-modal'; import ClusterItem from './components/cluster-item'; -import { ClusterDataList } from './config'; +import { ProviderLabelMap, ProviderValueMap, addActions } 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: - }, - { - label: 'Kubernetes', - locale: false, - value: 'kubernetes', - key: 'kubernetes', - icon: - }, - { - label: 'Digital Ocean', - locale: false, - value: 'digitalocean', - key: 'digitalocean', - icon: - } -]; - -const ActionList = [ - { - key: 'edit', - label: 'common.button.edit', - icon: - }, - { - key: 'add', - label: 'Add Worker', - locale: false, - icon: - }, - - { - key: 'terminal', - label: 'common.button.detail', - icon: - }, - { - key: 'addPool', - label: 'Add Node Pool', - locale: false, - icon: - }, - { - key: 'delete', - props: { - danger: true - }, - label: 'common.button.delete', - icon: - } -]; - const Credentials: React.FC = () => { const { dataSource, @@ -121,15 +57,33 @@ const Credentials: React.FC = () => { }); const [openAddWorker, setOpenAddWorker] = useState<{ open: boolean; - token: string; + registrationInfo: { + token: string; + image: string; + server_url: string; + }; }>({ open: false, - token: '' + registrationInfo: { + token: '', + image: '', + server_url: '' + } }); - const [openAddModal, setOpenAddModal] = useState(false); - const [provider, setProvider] = useState('custom'); - const [action, setAction] = useState(PageAction.CREATE); - const [title, setTitle] = useState(''); + const [openAddModal, setOpenAddModal] = useState<{ + open: boolean; + action: PageActionType; + currentData?: ListItem; + title: string; + provider: string; + }>({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '', + provider: '' + }); + const [addPoolStatus, setAddPoolStatus] = useState<{ open: boolean; action: PageActionType; @@ -139,25 +93,19 @@ const Credentials: React.FC = () => { open: false, action: PageAction.CREATE, title: '', - provider: 'digitalocean' + provider: ProviderValueMap.DigitalOcean }); - const [currentData, setCurrentData] = useState( - undefined - ); - - const setActions = (row: ListItem) => { - if (row.provider !== 'custom') { - return ActionList.filter((item) => item.key !== 'add'); - } - return ActionList; - }; const handleAddCluster = (value: string) => { - setOpenAddModal(true); - setAction(PageAction.CREATE); - setProvider(value); - const label = addActions.find((item) => item.value === value)?.label; - setTitle(`Add ${label} Cluster`); + const label = ProviderLabelMap[value]; + + setOpenAddModal({ + open: true, + action: PageAction.CREATE, + currentData: undefined, + title: `Add ${label} Cluster`, + provider: value + }); }; const handleAddPool = (value: string) => { @@ -178,32 +126,65 @@ const Credentials: React.FC = () => { ...data }; try { - if (action === PageAction.EDIT) { + if (openAddModal.action === PageAction.EDIT) { await updateCluster({ data: params, - id: currentData!.id + id: openAddModal.currentData!.id }); } else { await createCluster({ data: params }); } fetchData(); - setOpenAddModal(false); + setOpenAddModal({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '', + provider: '' + }); message.success(intl.formatMessage({ id: 'common.message.success' })); } catch (error) { - setOpenAddModal(false); + setOpenAddModal({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '', + provider: '' + }); } }; const handleModalCancel = () => { console.log('handleModalCancel'); - setOpenAddModal(false); + setOpenAddModal({ + open: false, + action: PageAction.CREATE, + currentData: undefined, + title: '', + provider: '' + }); }; const handleEditCluster = (row: ListItem) => { - setCurrentData(row); - setOpenAddModal(true); - setAction(PageAction.EDIT); - setTitle(`Edit ${row.name} Cluster`); + setOpenAddModal({ + open: true, + action: PageAction.EDIT, + currentData: row, + title: `Edit ${row.name} Cluster`, + provider: row.provider + }); + }; + + const handleAddWorker = async (row: ListItem) => { + try { + const data = await queryClusterToken({ id: row.id }); + setOpenAddWorker({ + open: true, + registrationInfo: data + }); + } catch (error: any) { + message.error(error.message || 'Failed to fetch cluster token'); + } }; const handleSelect = (val: any, row: ListItem) => { @@ -212,11 +193,7 @@ const Credentials: React.FC = () => { } else if (val === 'delete') { handleDelete({ ...row, name: row.name }); } else if (val === 'add_worker') { - setOpenAddWorker({ - open: true, - token: '${token}' - }); - setCurrentData(row); + handleAddWorker(row); } else if (val === 'addPool') { handleAddPool(row.provider); } else if (val === 'details') { @@ -255,14 +232,14 @@ const Credentials: React.FC = () => { marginTop={30} handleInputChange={handleNameChange} handleSearch={handleSearch} - width={{ input: 200 }} + width={{ input: 300 }} buttonText="Add Cluster" actionType="dropdown" actionItems={addActions} handleClickPrimary={handleClickDropdown} > { > setOpenAddWorker({ open: false, token: '' })} - token={openAddWorker.token} + onCancel={() => + setOpenAddWorker({ + open: false, + registrationInfo: { token: '', image: '', server_url: '' } + }) + } + registrationInfo={openAddWorker.registrationInfo} > void; onCancel: () => void; @@ -28,6 +29,7 @@ const AddCluster: React.FC = ({ action, open, provider, + currentData, onOk, onCancel }) => { @@ -36,12 +38,20 @@ const AddCluster: React.FC = ({ const [submissionStatus, setSubmissionStatus] = React.useState<{ success: boolean; data: ListItem; - }>({ success: true, data: {} as ListItem }); + }>({ success: false, data: {} as ListItem }); const handleSubmit = () => { form.submit(); }; + const handleOk = async (data: FormData) => { + console.log('handleOk===', data); + onOk({ + ...data, + provider + }); + }; + const handleCancel = () => { form.resetFields(); onCancel(); @@ -109,9 +119,14 @@ const AddCluster: React.FC = ({ {submissionStatus.success ? ( renderAddWorkerContent() ) : ( -
+ - name="display_name" + name="name" rules={[ { required: true, @@ -129,7 +144,7 @@ const AddCluster: React.FC = ({ required > - {provider === 'digitalocean' && ( + {provider === ProviderValueMap.DigitalOcean && ( )} name="description" rules={[{ required: false }]}> diff --git a/src/pages/cluster-management/components/add-credential.tsx b/src/pages/cluster-management/components/add-credential.tsx index 461851d6..ca1e9a4c 100644 --- a/src/pages/cluster-management/components/add-credential.tsx +++ b/src/pages/cluster-management/components/add-credential.tsx @@ -1,19 +1,23 @@ import ModalFooter from '@/components/modal-footer'; import ScrollerModal from '@/components/scroller-modal'; import SealInput from '@/components/seal-form/seal-input'; -import { PageAction, PasswordReg } from '@/config'; +import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; import React from 'react'; -import { FormData, ListItem } from '../config/types'; +import { ProviderValueMap } from '../config'; +import { + CredentialFormData as FormData, + CredentialListItem as ListItem +} from '../config/types'; type AddModalProps = { title: string; action: PageActionType; open: boolean; onOk: (values: FormData) => void; - data?: ListItem; + currentData?: ListItem; onCancel: () => void; provider: string; // 'kubernetes' | 'digitalocean'; }; @@ -22,7 +26,7 @@ const AddModal: React.FC = ({ action, open, onOk, - data, + currentData, provider, onCancel }) => { @@ -33,6 +37,10 @@ const AddModal: React.FC = ({ form.submit(); }; + const handleOk = async (data: FormData) => { + onOk(data); + }; + return ( = ({ > } > - + name="name" rules={[ @@ -73,14 +86,13 @@ const AddModal: React.FC = ({ required > - {provider === 'digital_ocean' && ( + {provider === ProviderValueMap.DigitalOcean && ( <> - name="access_key" + name="key" rules={[ { required: action === PageAction.CREATE, - pattern: PasswordReg, message: intl.formatMessage({ id: 'users.form.rule.password' }) @@ -93,11 +105,10 @@ const AddModal: React.FC = ({ > - name="secret_key" + name="secret" rules={[ { required: action === PageAction.CREATE, - pattern: PasswordReg, message: intl.formatMessage({ id: 'users.form.rule.password' }) @@ -105,17 +116,12 @@ const AddModal: React.FC = ({ ]} > )} - {provider === 'kubernetes' && ( - name="kubeconfig" rules={[{ required: false }]}> - - - )} name="description" rules={[{ required: false }]}> = () => { label="Credential" required options={['credential1', 'credential2', 'credential3'].map( - (item) => ({ + (item, i) => ({ label: item, - value: item + value: i }) )} > diff --git a/src/pages/cluster-management/components/cluster-item.tsx b/src/pages/cluster-management/components/cluster-item.tsx index 12427e05..82ed4cf8 100644 --- a/src/pages/cluster-management/components/cluster-item.tsx +++ b/src/pages/cluster-management/components/cluster-item.tsx @@ -5,78 +5,22 @@ import StatusTag from '@/components/status-tag'; import ThemeTag from '@/components/tags-wrapper/theme-tag'; import Card from '@/components/templates/card'; import { PageAction } from '@/config'; -import { - DeleteOutlined, - EditOutlined, - KubernetesOutlined -} from '@ant-design/icons'; import { Card as ACard, Col, Collapse, Row } from 'antd'; import React, { useMemo } from 'react'; import styled from 'styled-components'; +import { queryClusterToken } from '../apis'; import { ClusterStatus, ClusterStatusLabelMap, ProviderLabelMap, - ProviderValueMap + ProviderValueMap, + poolActionList } from '../config'; import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types'; import AddPool from './add-pool'; import RegisterCluster from './register-cluster'; import WorkerPools from './worker-pools'; -const actionItems = [ - { - key: 'edit', - label: 'common.button.edit', - icon: - }, - { - key: 'details', - label: 'common.button.view', - icon: - }, - { - key: 'add_worker', - label: 'Add Worker', - provider: ProviderValueMap.Custom, - locale: false, - icon: - }, - { - key: 'register_cluster', - label: 'Register Cluster', - provider: ProviderValueMap.Kubernetes, - locale: false, - icon: - }, - { - key: 'addPool', - label: 'Add Node Pool', - provider: ProviderValueMap.DigitalOcean, - locale: false, - icon: - }, - { - key: 'delete', - label: 'common.button.delete', - icon: , - props: { - danger: true - } - } -]; - -const CollapseTitle = styled.div` - display: flex; - align-items: center; - gap: 8px; - font-size: var(--font-size-middle); - font-weight: 500; - color: var(--ant-color-text); - height: 32px; - cursor: pointer; -`; - const Content = styled.div` display: flex; justify-content: space-between; @@ -216,12 +160,39 @@ const CardItem: React.FC = (props) => { open: false, action: PageAction.CREATE, title: '', - provider: 'digitalocean' + provider: ProviderValueMap.DigitalOcean }); - const [registerClusterStatus, setRegisterClusterStatus] = React.useState({ - open: false + const [registerClusterStatus, setRegisterClusterStatus] = React.useState<{ + open: boolean; + registrationInfo: { + token: string; + image: string; + server_url: string; + cluster_id: number; + }; + }>({ + open: false, + registrationInfo: { + token: '', + image: '', + server_url: '', + cluster_id: 0 + } }); + const handleRegisterCluster = async () => { + try { + const info = await queryClusterToken({ id: data.id }); + setRegisterClusterStatus({ + open: true, + registrationInfo: { + ...info, + cluster_id: data.id + } + }); + } catch (error) {} + }; + // cluster action handler const handleOnSelect = (key: string) => { if (key === 'addPool') { @@ -235,9 +206,7 @@ const CardItem: React.FC = (props) => { } if (key === 'register_cluster') { - setRegisterClusterStatus({ - open: true - }); + handleRegisterCluster(); return; } onSelect?.(key, data); @@ -256,7 +225,7 @@ const CardItem: React.FC = (props) => { }; const actions = useMemo(() => { - return actionItems.filter((item) => { + return poolActionList.filter((item) => { if (item.provider) { return item.provider === data.provider; } @@ -269,7 +238,7 @@ const CardItem: React.FC = (props) => { height={'auto'} clickable={false} ghost - footer={ + footerHolder={ setShow(!show)} expandIconPosition="end" @@ -340,11 +309,13 @@ const CardItem: React.FC = (props) => {
{data.name} - {ProviderLabelMap[data.provider]} + + {ProviderLabelMap[data.provider]} + @@ -359,15 +330,17 @@ const CardItem: React.FC = (props) => {
Workers
-
1/1
+
+ {data.ready_workers} / {data.workers} +
GPUs
-
12
+
{data.gpus}
Deployments
-
2
+
{data.models}
@@ -397,10 +370,16 @@ const CardItem: React.FC = (props) => { { setRegisterClusterStatus({ - open: false + open: false, + registrationInfo: { + token: '', + image: '', + server_url: '', + cluster_id: 0 + } }); }} > diff --git a/src/pages/cluster-management/components/register-cluster.tsx b/src/pages/cluster-management/components/register-cluster.tsx index b826c2df..b35b28f7 100644 --- a/src/pages/cluster-management/components/register-cluster.tsx +++ b/src/pages/cluster-management/components/register-cluster.tsx @@ -1,18 +1,22 @@ import ScrollerModal from '@/components/scroller-modal/index'; import React from 'react'; -import { ClusterListItem as ListItem } from '../config/types'; import RegisterClusterInner from './resiter-cluster-inner'; type AddModalProps = { title: string; open: boolean; - data: ListItem; + registrationInfo: { + token: string; + image: string; + server_url: string; + cluster_id: number; + }; onCancel: () => void; }; const AddCluster: React.FC = ({ title, open, - data, + registrationInfo, onCancel }) => { const handleCancel = () => { @@ -31,7 +35,7 @@ const AddCluster: React.FC = ({ width={600} footer={false} > - + ); }; diff --git a/src/pages/cluster-management/components/resiter-cluster-inner.tsx b/src/pages/cluster-management/components/resiter-cluster-inner.tsx index c078c652..82ca11b2 100644 --- a/src/pages/cluster-management/components/resiter-cluster-inner.tsx +++ b/src/pages/cluster-management/components/resiter-cluster-inner.tsx @@ -1,41 +1,39 @@ import HighlightCode from '@/components/highlight-code'; -import React, { useEffect } from 'react'; -import { queryClusterToken } from '../apis'; +import React, { useMemo } from 'react'; +import styled from 'styled-components'; import { generateRegisterCommand } from '../config'; -import { ClusterListItem as ListItem } from '../config/types'; + +const Title = styled.h3` + font-weight: 600; + color: var(--ant-color-text); + margin-bottom: 12px; + margin-top: 12px; + font-size: var(--font-size-normal); + .ant-tag { + color: var(--ant-color-text-secondary); + font-weight: 400; + } +`; type AddModalProps = { - data: ListItem; + registrationInfo: { + token: string; + image: string; + server_url: string; + cluster_id: number; + }; }; -const AddCluster: React.FC = ({ data }) => { - const [code, setCode] = React.useState(''); - const getToken = async () => { - const res = await queryClusterToken(data?.id); - return res.data?.token || ''; - }; +const AddCluster: React.FC = ({ registrationInfo }) => { + const code = useMemo(() => { + return generateRegisterCommand({ + server: registrationInfo?.server_url || window.location.origin, + clusterId: registrationInfo?.cluster_id, + registrationToken: registrationInfo?.token + }); + }, [registrationInfo]); - const getCode = async () => { - try { - const token = await getToken(); - const command = generateRegisterCommand({ - server: window.location.origin, - clusterId: data?.id || 0, - registrationToken: token - }); - setCode(command); - } catch (error) { - setCode( - generateRegisterCommand({ - server: window.location.origin, - clusterId: data?.id || 0, - registrationToken: '{token}' - }) - ); - } - }; - - useEffect(() => { - getCode(); + const applyCommand = useMemo(() => { + return `kubectl apply -f manifest.yaml`; }, []); return ( diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index e23562f0..3a92313b 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -1,57 +1,29 @@ +import icons from '@/components/icon-font/icons'; import { StatusMaps } from '@/config'; import { StatusType } from '@/config/types'; -export const ClusterDataList = [ - { - id: 3, - name: 'Custom-cluster', - provider: 'custom', - clusterType: 'Custom', - workers: 4, - gpus: 8, - status: 'ready', - deployments: 3 - }, - { - id: 1, - name: 'kubernetes-cluster', - provider: 'kubernetes', - clusterType: 'Kubernetes', - workers: 2, - gpus: 4, - status: 'ready', - deployments: 1 - }, - { - id: 2, - name: 'Digital-Ocean-cluster', - provider: 'digitalocean', - workers: 3, - gpus: 6, - status: 'error', - deployments: 2 - } -]; - export const ClusterStatusValueMap = { - Ready: 'ready', - Error: 'error' + Provisioning: 0, + Ready: 3, + ProvisionedNotReady: 1 }; export const ClusterStatusLabelMap = { + [ClusterStatusValueMap.Provisioning]: 'Provisioning', [ClusterStatusValueMap.Ready]: 'Ready', - [ClusterStatusValueMap.Error]: 'Error' + [ClusterStatusValueMap.ProvisionedNotReady]: 'Provisioned Not Ready' }; export const ClusterStatus: Record = { + [ClusterStatusValueMap.Provisioning]: StatusMaps.transitioning, [ClusterStatusValueMap.Ready]: StatusMaps.success, - [ClusterStatusValueMap.Error]: StatusMaps.error + [ClusterStatusValueMap.ProvisionedNotReady]: StatusMaps.error }; export const ProviderValueMap = { - Kubernetes: 'kubernetes', - DigitalOcean: 'digitalocean', - Custom: 'custom' + Kubernetes: 'Kubernetes', + DigitalOcean: 'DigitalOcean', + Custom: 'Custom' }; export const ProviderLabelMap = { @@ -65,6 +37,72 @@ export const generateRegisterCommand = (params: { clusterId: number; registrationToken: string; }) => { - return `curl -k -L '${params.server}/v2/clusters/${params.clusterId}/manifests' \\ ---header 'Authorization: Bearer ${params.registrationToken}'`; + return `curl -k -L '${params.server}/v1/clusters/${params.clusterId}/manifests' \\ +--header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`; }; + +export const addActions = [ + { + label: 'Custom', + locale: false, + value: ProviderValueMap.Custom, + key: ProviderValueMap.Custom, + icon: icons.Docker + }, + { + label: 'Kubernetes', + locale: false, + value: ProviderValueMap.Kubernetes, + key: ProviderValueMap.Kubernetes, + icon: icons.KubernetesOutlined + }, + { + label: 'Digital Ocean', + locale: false, + value: ProviderValueMap.DigitalOcean, + key: ProviderValueMap.DigitalOcean, + icon: icons.DigitalOcean + } +]; + +export const poolActionList = [ + { + key: 'edit', + label: 'common.button.edit', + icon: icons.EditOutlined + }, + { + key: 'details', + label: 'common.button.view', + icon: icons.DetailInfo + }, + { + key: 'add_worker', + label: 'Add Worker', + provider: ProviderValueMap.Custom, + locale: false, + icon: icons.Docker + }, + { + key: 'register_cluster', + label: 'Register Cluster', + provider: ProviderValueMap.Kubernetes, + locale: false, + icon: icons.KubernetesOutlined + }, + { + key: 'addPool', + label: 'Add Node Pool', + provider: ProviderValueMap.DigitalOcean, + locale: false, + icon: icons.Catalog + }, + { + key: 'delete', + label: 'common.button.delete', + icon: icons.DeleteOutlined, + props: { + danger: true + } + } +]; diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts index 3fe2ef75..6e258b13 100644 --- a/src/pages/cluster-management/config/types.ts +++ b/src/pages/cluster-management/config/types.ts @@ -1,12 +1,14 @@ export interface CredentialFormData { name: string; provider: string; - access_key: string; - secret_key: string; + key: string; + secret: string; description?: string; id?: number; } +export type ClusterStatusType = 0 | 1 | 3; + export interface CredentialListItem { id: number; name: string; @@ -19,7 +21,7 @@ export interface CredentialListItem { } export interface ClusterFormData { - display_name: string; + name: string; description: string; provider: string; credential_id: number; @@ -55,9 +57,11 @@ export interface ClusterListItem { zone: string; region: string; gpus: number; - deployments: number; + models: number; + workers: number; + ready_workers: number; id: number; - status: string; + state: ClusterStatusType; state_message: string; worker_pools: NodePoolListItem[]; } diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index c037c697..05d54a5d 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -1,30 +1,19 @@ 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 IconFont from '@/components/icon-font'; -import PageTools from '@/components/page-tools'; +import { FilterBar } from '@/components/page-tools'; import { PageAction } from '@/config'; import type { PageActionType } from '@/config/types'; import useTableFetch from '@/hooks/use-table-fetch'; import { DeleteOutlined, - DownOutlined, EditOutlined, - KubernetesOutlined, - SyncOutlined + KubernetesOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; -import { - Button, - ConfigProvider, - Empty, - Input, - Space, - Table, - message -} from 'antd'; +import { ConfigProvider, Empty, Table, message } from 'antd'; import dayjs from 'dayjs'; import { useState } from 'react'; import { @@ -34,6 +23,7 @@ import { updateCredential } from './apis'; import AddModal from './components/add-credential'; +import { ProviderValueMap } from './config'; import { CredentialFormData as FormData, CredentialListItem as ListItem @@ -99,26 +89,22 @@ const Credentials: React.FC = () => { open: boolean; action: PageActionType; title: string; + currentData: ListItem | undefined; }>({ provider: '', open: false, action: PageAction.CREATE, - title: '' + title: '', + currentData: undefined }); - const [currentData, setCurrentData] = useState( - undefined - ); - const handleAddCredential = (value: string) => { - const title = - value === 'digital_ocean' - ? 'Add Digital Ocean Credential' - : 'Add Kubernetes Credential'; + const handleAddCredential = () => { setOpenModalStatus({ - provider: value, + provider: ProviderValueMap.DigitalOcean, open: true, action: PageAction.CREATE, - title: title + title: 'Add Cloud Credential', + currentData: undefined }); }; @@ -132,7 +118,7 @@ const Credentials: React.FC = () => { data: { ...params }, - id: currentData!.id + id: openModalStatus.currentData!.id }); } else { await createCredential({ data: params }); @@ -151,12 +137,12 @@ const Credentials: React.FC = () => { }; const handleEditUser = (row: ListItem) => { - setCurrentData(row); setOpenModalStatus({ provider: row.provider, open: true, action: PageAction.EDIT, - title: `Edit ${row.provider} Credential` + title: `Edit ${row.name} Credential`, + currentData: row }); }; @@ -180,11 +166,6 @@ const Credentials: React.FC = () => { return
; }; - const handleClickDropdown = (e: any) => { - const value = e.key; - handleAddCredential(value); - }; - return ( <> { }} extra={[]} > - - - - - } - right={ - - - - - - - } - > + marginTop={30} + buttonText={'Add Cloud Credential'} + handleDeleteByBatch={handleDeleteBatch} + handleSearch={handleSearch} + handleInputChange={handleNameChange} + handleClickPrimary={handleAddCredential} + rowSelection={rowSelection} + width={{ input: 300 }} + > + { open={openModalStatus.open} action={openModalStatus.action} title={openModalStatus.title} - data={currentData} + currentData={openModalStatus.currentData} onCancel={handleModalCancel} onOk={handleModalOk} > diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 43219383..03562d1a 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -52,9 +52,11 @@ export async function queryModelsList( ); } -export async function queryGPUList(params?: Global.SearchParams) { +export async function queryGPUList>( + params?: Global.SearchParams & T +) { return request>(`/gpu-devices`, { - methos: 'GET', + method: 'GET', params }); } @@ -172,7 +174,7 @@ export async function queryModelScopeModels( config?: any ) { const tagsCriterion = params.tags?.map((tag: string) => { - return { category: 'libraries', predicate: 'contains', values: [tag] }; + return { category: 'tags', predicate: 'contains', values: [tag] }; }); const tasksCriterion = params.tasks?.map((task: string) => { return { category: 'tasks', predicate: 'contains', values: [task] }; diff --git a/src/pages/llmodels/components/advance-config.tsx b/src/pages/llmodels/components/advance-config.tsx index 15d01d7a..5455108a 100644 --- a/src/pages/llmodels/components/advance-config.tsx +++ b/src/pages/llmodels/components/advance-config.tsx @@ -30,7 +30,6 @@ 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; @@ -460,19 +459,19 @@ const AdvanceConfig: React.FC = (props) => { forceRender: true, children: }, - { - key: '3', - label: ( - - Scaling - - ), - forceRender: true, - children: - }, + // { + // key: '3', + // label: ( + // + // Scaling + // + // ), + // forceRender: true, + // children: + // }, { key: '1', label: ( diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx index 0458cc64..b5c4a48f 100644 --- a/src/pages/llmodels/components/data-form.tsx +++ b/src/pages/llmodels/components/data-form.tsx @@ -6,17 +6,13 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle } from 'react'; -import { - backendOptionsMap, - excludeFields, - modelSourceMap, - sourceOptions -} from '../config'; +import { backendOptionsMap, excludeFields, sourceOptions } from '../config'; import { FormInnerContext } from '../config/form-context'; import { FormData, SourceType } from '../config/types'; import CatalogFrom from '../forms/catalog'; import HuggingFaceForm from '../forms/hugging-face'; import LocalPathForm from '../forms/local-path'; +import { useGenerateGPUOptions } from '../hooks/use-form-initial-values'; import AdvanceConfig from './advance-config'; interface DataFormProps { @@ -29,8 +25,7 @@ interface DataFormProps { sourceDisable?: boolean; backendOptions?: Global.BaseOption[]; sourceList?: Global.BaseOption[]; - gpuOptions: any[]; - modelFileOptions?: any[]; + clusterList: Global.BaseOption[]; fields?: string[]; onValuesChange?: (changedValues: any, allValues: any) => void; onSourceChange?: (value: string) => void; @@ -38,11 +33,6 @@ interface DataFormProps { onBackendChange?: (value: string) => void; } -const SEARCH_SOURCE = [ - modelSourceMap.huggingface_value, - modelSourceMap.modelscope_value -]; - const DataForm: React.FC = forwardRef((props, ref) => { const { action, @@ -51,13 +41,13 @@ const DataForm: React.FC = forwardRef((props, ref) => { sourceDisable = true, backendOptions, sourceList, - gpuOptions = [], - modelFileOptions = [], + clusterList = [], fields = ['source'], onSourceChange, onValuesChange, onOk } = props; + const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions(); const { getRuleMessage } = useAppUtils(); const [form] = Form.useForm(); const intl = useIntl(); @@ -143,6 +133,10 @@ const DataForm: React.FC = forwardRef((props, ref) => { onSourceChange?.(val); }; + const handleClusterChange = (value: number) => { + getGPUOptionList({ clusterId: value }); + }; + const handleOnValuesChange = async (changedValues: any, allValues: any) => { const fieldName = Object.keys(changedValues)[0]; @@ -172,6 +166,9 @@ const DataForm: React.FC = forwardRef((props, ref) => { }, getFieldsValue: () => { return form.getFieldsValue(); + }, + getGPUOptionList(params: { clusterId: number }) { + getGPUOptionList(params); } }; }, @@ -179,78 +176,96 @@ const DataForm: React.FC = forwardRef((props, ref) => { ); return ( - - - name="name" - rules={[ - { - required: true, - message: getRuleMessage('input', 'common.table.name') - } - ]} + - - - {fields.includes('source') && ( - name="source" + name="name" rules={[ { required: true, - message: getRuleMessage('select', 'models.form.source') + message: getRuleMessage('input', 'common.table.name') + } + ]} + > + + + {fields.includes('source') && ( + + name="source" + rules={[ + { + required: true, + message: getRuleMessage('select', 'models.form.source') + } + ]} + > + { + + } + + )} + + + + + + name="cluster_id" + rules={[ + { + required: true, + message: getRuleMessage('select', 'Cluster', false) } ]} > { } - )} - - - - - - {/* + {/* = forwardRef((props, ref) => { } > */} - - name="description"> - - - - + + name="description"> + + + + + ); }); diff --git a/src/pages/llmodels/components/deploy-builtin-modal.tsx b/src/pages/llmodels/components/deploy-builtin-modal.tsx index e2fd592d..6c6f91e5 100644 --- a/src/pages/llmodels/components/deploy-builtin-modal.tsx +++ b/src/pages/llmodels/components/deploy-builtin-modal.tsx @@ -2,6 +2,7 @@ import ModalFooter from '@/components/modal-footer'; import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import { createAxiosToken } from '@/hooks/use-chunk-request'; +import { ProviderValueMap } from '@/pages/cluster-management/config'; import { CloseOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; @@ -17,11 +18,8 @@ import { } from '../config'; import { FormContext } from '../config/form-context'; import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types'; -import { - checkOnlyAscendNPU, - useCheckCompatibility, - useGenerateFormEditInitialValues -} from '../hooks'; +import { useCheckCompatibility } from '../hooks'; +import useFormInitialValues from '../hooks/use-form-initial-values'; import ColumnWrapper from './column-wrapper'; import CompatibilityAlert from './compatible-alert'; import DataForm from './data-form'; @@ -104,10 +102,9 @@ const AddModal: React.FC = (props) => { handleOnValuesChange, warningStatus } = useCheckCompatibility(); + const { getClusterList, clusterList } = useFormInitialValues(); const intl = useIntl(); - const { getGPUList } = useGenerateFormEditInitialValues(); const form = useRef({}); - const [gpuOptions, setGpuOptions] = useState([]); const [isGGUF, setIsGGUF] = useState(false); const [sourceList, setSourceList] = useState([]); const [backendList, setBackendList] = useState([]); @@ -161,15 +158,6 @@ const AddModal: React.FC = (props) => { return EmbeddingRerankFirstQuant.includes(_.toUpper(data.quantOption)); } - if ( - data.backend === backendOptionsMap.llamaBox && - checkOnlyAscendNPU(gpuOptions) - ) { - return hasF16Ref.current - ? AscendNPUQuant_F16.includes(_.toUpper(data.quantOption)) - : AscendNPUQuant_Q8.includes(_.toUpper(data.quantOption)); - } - return defaultQuant.includes(_.toUpper(data.quantOption)); }; @@ -360,6 +348,15 @@ const AddModal: React.FC = (props) => { handleCheckFormData(); }; + const initClusterId = () => { + const cluster_id = + clusterList?.find((item) => item.provider === ProviderValueMap.Custom) + ?.value || clusterList?.[0]?.value; + + console.log('cluster_id:', cluster_id); + return cluster_id; + }; + const fetchSpecData = async () => { try { axiosToken.current?.cancel?.(); @@ -407,7 +404,10 @@ const AddModal: React.FC = (props) => { size: defaultSpec.size, backend: defaultSpec.backend }); - initFormDataBySource(defaultSpec); + initFormDataBySource({ + ...defaultSpec, + cluster_id: initClusterId() + }); const name = _.toLower(current.name).replace(/\s/g, '-') || ''; form.current.setFieldValue('name', name); @@ -420,6 +420,7 @@ const AddModal: React.FC = (props) => { const allValues = generateSubmitData({ ...defaultSpec, categories: _.get(current, 'categories.0', null), + cluster_id: initClusterId(), name }); handleCheckCompatibility(allValues); @@ -484,7 +485,9 @@ const AddModal: React.FC = (props) => { useEffect(() => { if (open) { - fetchSpecData(); + setTimeout(() => { + fetchSpecData(); + }, 100); } return () => { axiosToken.current?.cancel?.(); @@ -498,9 +501,7 @@ const AddModal: React.FC = (props) => { }, [open, current]); useEffect(() => { - getGPUList().then((data) => { - setGpuOptions(data); - }); + getClusterList(); }, []); return ( @@ -608,7 +609,7 @@ const AddModal: React.FC = (props) => { sourceDisable={false} backendOptions={backendList} sourceList={sourceList} - gpuOptions={gpuOptions} + clusterList={clusterList} onBackendChange={handleBackendChange} onSourceChange={handleSourceChange} onValuesChange={onValuesChange} diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 6677dad4..3691abd8 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -1,8 +1,8 @@ -import { getRequestId } from '@/atoms/models'; import ModalFooter from '@/components/modal-footer'; import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import useDeferredRequest from '@/hooks/use-deferred-request'; +import { ProviderValueMap } from '@/pages/cluster-management/config'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import _ from 'lodash'; @@ -68,10 +68,12 @@ type AddModalProps = { source: SourceType; isGGUF?: boolean; width?: string | number; - gpuOptions: any[]; - modelFileOptions: any[]; initialValues?: any; deploymentType?: 'modelList' | 'modelFiles'; + clusterList: Global.BaseOption< + number, + { provider: string; state: string | number } + >[]; onOk: (values: FormData) => void; onCancel: () => void; }; @@ -95,7 +97,8 @@ const AddModal: FC = (props) => { action, width = 600, deploymentType = 'modelList', - initialValues + initialValues, + clusterList } = props || {}; const SEARCH_SOURCE = [ modelSourceMap.huggingface_value, @@ -113,7 +116,7 @@ const AddModal: FC = (props) => { warningStatus, submitAnyway } = useCheckCompatibility(); - const { onSelectModel } = useSelectModel({ gpuOptions: props.gpuOptions }); + const { onSelectModel } = useSelectModel({ gpuOptions: [] }); const form = useRef({}); const intl = useIntl(); const [selectedModel, setSelectedModel] = useState({}); @@ -161,15 +164,6 @@ const AddModal: FC = (props) => { evaluateStateRef.current = state; }; - const updateEvaluateState = (state: EvaluateProccessType) => { - const currentRequestModelId = evaluateStateRef.current.requestModelId; - setEvaluteState({ - ...evaluateStateRef.current, - state - }); - return currentRequestModelId; - }; - const handleOnValuesChange = (data: { changedValues: any; allValues: any; @@ -199,71 +193,6 @@ const AddModal: FC = (props) => { return categories || null; }; - const { run: onSelectFile } = useDeferredRequest( - async (item: any, modelInfo: any, manual?: boolean) => { - unlockWarningStatus(); - - const evaluateRes = await handleOnValuesChangeBefore?.({ - changedValues: {}, - allValues: form.current?.form?.getFieldsValue?.(), - source: props.source - }); - console.log('onSelectFile:', item, modelInfo, evaluateRes); - - // for cancel evaluate request case - if (!evaluateRes) { - return; - } - - const defaultSpec = getDefaultSpec({ - evaluateResult: evaluateRes - }); - - /** - * do not reset backend_parameters when select a model file - */ - const formValues = form.current?.getFieldsValue?.(pickFieldsFromSpec); - - form.current?.setFieldsValue?.({ - ..._.omit(modelInfo, ['name']), - file_name: item.fakeName, - backend_parameters: - formValues.backend_parameters?.length > 0 - ? formValues.backend_parameters - : defaultSpec.backend_parameters || [], - backend_version: - formValues.backend_version || defaultSpec.backend_version, - env: formValues.env || defaultSpec.env, - categories: getCategory(item) - }); - }, - 100 - ); - - const handleSelectModelFile = async ( - item: any, - options: { requestModelId: number; manual?: boolean } - ) => { - const { requestModelId, manual } = options || {}; - if (requestModelId !== getRequestId()) { - return; - } - console.log('handleSelectModelFile:', item, selectedModel); - - const modelInfo = onSelectModel(selectedModel, props.source); - - form.current?.setFieldsValue?.({ - ..._.omit(modelInfo, ['name']), - file_name: item.fakeName, - categories: getCategory(item) - }); - - // evaluate the form data when select a model file - if (item.fakeName) { - onSelectFile(item, modelInfo, manual); - } - }; - const handleCancelFiles = () => { cancelEvaluate(); modelFileRef.current?.cancelRequest(); @@ -441,22 +370,37 @@ const AddModal: FC = (props) => { onCancel?.(); }, [onCancel]); + const initClusterId = () => { + const cluster_id = + clusterList?.find((item) => item.provider === ProviderValueMap.Custom) + ?.value || clusterList?.[0]?.value; + + return cluster_id; + }; + const handleOnOpen = () => { if (props.deploymentType === 'modelFiles') { form.current?.form?.setFieldsValue({ - ...props.initialValues + ...props.initialValues, + cluster_id: initClusterId() }); handleOnValuesChange?.({ changedValues: {}, - allValues: props.initialValues, + allValues: { + ...props.initialValues, + cluster_id: initClusterId() + }, source: source }); } else { - let backend = checkOnlyAscendNPU(props.gpuOptions) + let backend = checkOnlyAscendNPU([]) ? backendOptionsMap.ascendMindie : backendOptionsMap.vllm; - form.current?.setFieldValue?.('backend', backend); + form.current?.setFieldsValue?.({ + backend, + cluster_id: initClusterId() + }); } }; @@ -483,6 +427,9 @@ const AddModal: FC = (props) => { useEffect(() => { if (open) { handleOnOpen(); + form.current?.getGPUOptionList?.({ + clusterId: initClusterId() + }); } else { cancelEvaluate(); clearCahceFormValues(); @@ -495,7 +442,7 @@ const AddModal: FC = (props) => { message: [] }); }; - }, [open, props.gpuOptions.length]); + }, [open, clusterList]); return ( = (props) => { handleOnSelectModelAfterEvaluate } displayEvaluateStatus={displayEvaluateStatus} - gpuOptions={props.gpuOptions} + gpuOptions={[]} > @@ -559,8 +506,6 @@ const AddModal: FC = (props) => { value={{ isGGUF: isGGUF, pageAction: action, - modelFileOptions: props.modelFileOptions, - gpuOptions: props.gpuOptions, onValuesChange: onValuesChange }} > @@ -609,12 +554,11 @@ const AddModal: FC = (props) => { initialValues={initialValues} source={source} action={action} + clusterList={clusterList} selectedModel={selectedModel} onOk={handleOnOk} ref={form} isGGUF={isGGUF} - gpuOptions={props.gpuOptions} - modelFileOptions={props.modelFileOptions} onBackendChange={handleBackendChange} onValuesChange={onValuesChange} > diff --git a/src/pages/llmodels/components/performance.tsx b/src/pages/llmodels/components/performance.tsx index e916b57a..c9b4640b 100644 --- a/src/pages/llmodels/components/performance.tsx +++ b/src/pages/llmodels/components/performance.tsx @@ -1,4 +1,3 @@ -import CheckboxField from '@/components/seal-form/checkbox-field'; import SealCascader from '@/components/seal-form/seal-cascader'; import SealSelect from '@/components/seal-form/seal-select'; import TooltipList from '@/components/tooltip-list'; @@ -7,8 +6,7 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; import { backendOptionsMap } from '../config'; -import { useFormContext } from '../config/form-context'; -import { FormData } from '../config/types'; +import { useFormContext, useFormInnerContext } from '../config/form-context'; import GPUCard from './gpu-card'; const scheduleTypeTips = [ @@ -30,13 +28,8 @@ const scheduleTypeTips = [ const Performance: React.FC = () => { const intl = useIntl(); - const { - onValuesChange, - onQuantizationChange, - gpuOptions, - source, - quantizationOptions - } = useFormContext(); + const { gpuOptions } = useFormInnerContext(); + const { onValuesChange, onQuantizationChange } = useFormContext(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); @@ -167,7 +160,7 @@ const Performance: React.FC = () => { )} -
+ {/*
name="optimize_long_prompt" valuePropName="checked" @@ -192,7 +185,7 @@ const Performance: React.FC = () => { })} > -
+
*/} ); }; diff --git a/src/pages/llmodels/components/scaling.tsx b/src/pages/llmodels/components/scaling.tsx index 39063308..ccaa83b6 100644 --- a/src/pages/llmodels/components/scaling.tsx +++ b/src/pages/llmodels/components/scaling.tsx @@ -45,8 +45,7 @@ const CheckboxField: React.FC<{ const Scaling: React.FC = () => { const intl = useIntl(); - const { onValuesChange, onQuantizationChange, source, quantizationOptions } = - useFormContext(); + const { onValuesChange, onQuantizationChange } = useFormContext(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); diff --git a/src/pages/llmodels/components/search-model.tsx b/src/pages/llmodels/components/search-model.tsx index 74142bbb..f3caca1a 100644 --- a/src/pages/llmodels/components/search-model.tsx +++ b/src/pages/llmodels/components/search-model.tsx @@ -199,7 +199,7 @@ const SearchModel: React.FC = (props) => { try { const params = { Name: `${searchInputRef.current}`, - tags: [], + tags: ['gptq'], tasks: filterTaskRef.current ? ([ModelscopeTaskMap[filterTaskRef.current]] as string[]) : [], diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 25f2f7bf..71d577ae 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -14,11 +14,6 @@ import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableSort from '@/hooks/use-table-sort'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { handleBatchRequest } from '@/utils'; -import { - IS_FIRST_LOGIN, - readState, - writeState -} from '@/utils/localstore/index'; import { DownOutlined, QuestionCircleOutlined, @@ -26,16 +21,7 @@ import { } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl, useNavigate } from '@umijs/max'; -import { - Button, - Empty, - Input, - Select, - Space, - Tooltip, - Typography, - message -} from 'antd'; +import { Button, Input, Select, Space, Tooltip, message } from 'antd'; import dayjs from 'dayjs'; import { useAtom } from 'jotai'; import _ from 'lodash'; @@ -76,7 +62,7 @@ import { ModelInstanceListItem, SourceType } from '../config/types'; -import { useGenerateFormEditInitialValues } from '../hooks'; +import useFormInitialValues from '../hooks/use-form-initial-values'; import APIAccessInfoModal from './api-access-info'; import DeployModal from './deploy-modal'; import Instances from './instances'; @@ -88,6 +74,7 @@ interface ModelsProps { handleNameChange: (e: any) => void; handleShowSizeChange?: (page: number, size: number) => void; handlePageChange: (page: number, pageSize: number | undefined) => void; + handleClusterChange: (value: number) => void; handleDeleteSuccess: () => void; handleCategoryChange: (val: any) => void; onViewLogs: () => void; @@ -103,29 +90,12 @@ interface ModelsProps { }; deleteIds?: number[]; workerList: WorkerListItem[]; - modelFileOptions: any[]; - catalogList?: any[]; dataSource: ListItem[]; loading: boolean; loadend: boolean; total: number; } -const clusterList = [ - { - label: 'Custom', - value: 'custom' - }, - { - label: 'Kubernetes', - value: 'kubernetes' - }, - { - label: 'Digital Ocean', - value: 'digital_ocean' - } -]; - const statusList = [ { label: 'Running', @@ -164,34 +134,27 @@ const Models: React.FC = ({ onCancelViewLogs, handleCategoryChange, handleOnToggleExpandAll, + handleClusterChange, onStop, onStart, - modelFileOptions, deleteIds, dataSource, workerList, - catalogList, queryParams, loading, loadend, total }) => { - const { getGPUList, generateFormValues, gpuDeviceList } = - useGenerateFormEditInitialValues(); + const { getGPUOptionList, generateFormValues, clusterList, getClusterList } = + useFormInitialValues(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const [updateFormInitials, setUpdateFormInitials] = useState<{ - gpuOptions: any[]; - modelFileOptions?: any[]; data: any; isGGUF: boolean; }>({ - gpuOptions: [], - modelFileOptions: [], data: {}, isGGUF: false }); - const [isFirstLogin, setIsFirstLogin] = useState(false); - const [isLoading, setIsLoading] = useState(false); const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom); const intl = useIntl(); const navigate = useNavigate(); @@ -218,17 +181,13 @@ const Models: React.FC = ({ width: number | string; hasLinuxWorker?: boolean; source: SourceType; - gpuOptions: any[]; isGGUF?: boolean; - modelFileOptions?: any[]; }>({ show: false, hasLinuxWorker: false, width: 600, isGGUF: false, - source: modelSourceMap.huggingface_value as SourceType, - gpuOptions: [], - modelFileOptions: [] + source: modelSourceMap.huggingface_value as SourceType }); const currentData = useRef({} as ListItem); const [currentInstance, setCurrentInstance] = useState<{ @@ -243,17 +202,6 @@ const Models: React.FC = ({ }); const modalRef = useRef(null); - useEffect(() => { - if (!catalogList?.length) { - return; - } - const getFirstLoginState = async () => { - const is_first_login = await readState(IS_FIRST_LOGIN); - setIsFirstLogin(is_first_login); - }; - getFirstLoginState(); - }, [catalogList?.length]); - useEffect(() => { if (deleteIds?.length) { rowSelection.removeSelectedKey(deleteIds); @@ -262,7 +210,9 @@ const Models: React.FC = ({ useEffect(() => { const getData = async () => { - await getGPUList(); + const res = await getClusterList(); + const clusterId = res[0]?.value; + await getGPUOptionList({ clusterId }); }; getData(); return () => { @@ -470,10 +420,8 @@ const Models: React.FC = ({ }, []); const handleEdit = async (row: ListItem) => { - const initialValues = generateFormValues(row, gpuDeviceList.current); + const initialValues = generateFormValues(row, []); setUpdateFormInitials({ - gpuOptions: gpuDeviceList.current, - modelFileOptions: modelFileOptions, data: initialValues, isGGUF: row.backend === backendOptionsMap.llamaBox }); @@ -584,9 +532,7 @@ const Models: React.FC = ({ if (config) { setOpenDeployModal({ ...config, - hasLinuxWorker: hasLinuxWorker, - gpuOptions: gpuDeviceList.current, - modelFileOptions: modelFileOptions + hasLinuxWorker: hasLinuxWorker }); } }; @@ -652,7 +598,10 @@ const Models: React.FC = ({ span: 3, render: (text: string, record: ListItem) => ( - {['Custom', 'Kubernetes', 'Digital Ocean'][record.id] || 'Custom'} + { + clusterList.find((item) => item.value === record.cluster_id) + ?.label + } ) }, @@ -724,31 +673,6 @@ const Models: React.FC = ({ ]; }, [sortOrder, intl, handleSelect]); - const handleOnClick = async () => { - if (isLoading) { - return; - } - - const data = catalogList?.[0] || {}; - try { - setIsLoading(true); - const modelData = await createModel({ - data: data - }); - writeState(IS_FIRST_LOGIN, false); - setIsFirstLogin(false); - setTimeout(() => { - updateExpandedRowKeys([modelData.id]); - }, 300); - message.success(intl.formatMessage({ id: 'common.message.success' })); - handleSearch?.(); - } catch (error) { - // ingore - } finally { - setIsLoading(false); - } - }; - const handleToggleExpandAll = useCallback( (expanded: boolean) => { const keys = dataSource.map((item) => item.id); @@ -760,33 +684,6 @@ const Models: React.FC = ({ [dataSource] ); - const renderEmpty = useMemo(() => { - if (dataSource.length || !isFirstLogin || !catalogList?.length) { - return null; - } - return ( -
- - - {intl.formatMessage({ id: 'models.table.list.empty' })} - -
- -
-
- ); - }, [dataSource.length, isFirstLogin, isLoading, intl]); - return ( <> = ({ style={{ width: 160 }} size="large" maxTagCount={1} + onChange={handleClusterChange} options={clusterList} > -
- setOpen(false)}> ; state: string; ip: string; + cluster_id: number; state_message: string; status: { cpu: {