chore: get started for add cluster
This commit is contained in:
@@ -60,4 +60,5 @@ export const fromClusterCreationAtom = atom(false);
|
||||
*/
|
||||
export const clusterSessionAtom = atom<{
|
||||
firstAddWorker: boolean;
|
||||
firstAddCluster: boolean;
|
||||
} | null>(null);
|
||||
|
||||
@@ -64,6 +64,7 @@ const SealPassword: React.FC<InputProps & SealFormItemProps> = (props) => {
|
||||
required={required}
|
||||
description={description}
|
||||
disabled={props.disabled}
|
||||
labelExtra={props.labelExtra}
|
||||
hasPrefix={!!props.prefix}
|
||||
onClick={handleClickWrapper}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@ export const HEADER_HEIGHT = 56;
|
||||
|
||||
export const DEFAULT_ENTER_PAGE = {
|
||||
adminForNormal: '/dashboard',
|
||||
adminForFirst: '/models/deployments',
|
||||
adminForFirst: '/resources/workers',
|
||||
user: '/models/user-models',
|
||||
login: '/login'
|
||||
};
|
||||
|
||||
@@ -80,5 +80,6 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'e.g. /data/models (path must start with /)',
|
||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||
'clusters.button.genToken': 'Generate Token'
|
||||
};
|
||||
|
||||
@@ -261,5 +261,11 @@ export default {
|
||||
'models.accessControlModal.includeusers': 'Include Users',
|
||||
'models.table.genericProxy':
|
||||
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.',
|
||||
'models.form.backendVersion.deprecated': 'Deprecated'
|
||||
'models.form.backendVersion.deprecated': 'Deprecated',
|
||||
'models.accessSettings.public.desc':
|
||||
'Accessible to anyone without authentication.',
|
||||
'models.accessSettings.authed.tips':
|
||||
'Accessible to all authenticated platform users.',
|
||||
'models.accessSettings.allowedUsers.tips':
|
||||
'Only designated users can access the model.'
|
||||
};
|
||||
|
||||
@@ -33,5 +33,11 @@ export default {
|
||||
'noresult.keys.nofound': 'No matching API keys found.',
|
||||
'noresult.catalog.title': 'No Models',
|
||||
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
||||
'noresult.catalog.nofound': 'No matching models found.'
|
||||
'noresult.catalog.nofound': 'No matching models found.',
|
||||
'noresult.resources.cluster':
|
||||
'No clusters available. Add a cluster to get started.',
|
||||
'noresult.resources.worker':
|
||||
'No workers available. Add a worker to get started.',
|
||||
'noresult.resources.gotocluster': 'Create a Docker or Other Cluster',
|
||||
'noresult.resources.gotoworker': 'Add Worker'
|
||||
};
|
||||
|
||||
@@ -80,7 +80,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'e.g. /data/models (path must start with /)',
|
||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||
'clusters.button.genToken': 'Generate Token'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -151,5 +152,6 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
||||
// 65. 'clusters.addworker.notSpecified': 'Not Specified',
|
||||
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
||||
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
||||
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
||||
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||
// 69. 'clusters.button.genToken': 'Generate Token'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -261,7 +261,13 @@ export default {
|
||||
'models.accessControlModal.includeusers': 'Include Users',
|
||||
'models.table.genericProxy':
|
||||
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.',
|
||||
'models.form.backendVersion.deprecated': 'Deprecated'
|
||||
'models.form.backendVersion.deprecated': 'Deprecated',
|
||||
'models.accessSettings.public.desc':
|
||||
'Accessible to anyone without authentication.',
|
||||
'models.accessSettings.authed.tips':
|
||||
'Accessible to all authenticated platform users.',
|
||||
'models.accessSettings.allowedUsers.tips':
|
||||
'Only designated users can access the model.'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -349,5 +355,8 @@ export default {
|
||||
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
|
||||
// 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
|
||||
// 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',,
|
||||
// 68. 'models.form.backendVersion.deprecated': 'Deprecated'
|
||||
// 68. 'models.form.backendVersion.deprecated': 'Deprecated',
|
||||
// 69. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
|
||||
// 70. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
|
||||
// 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -33,5 +33,11 @@ export default {
|
||||
'noresult.keys.nofound': 'No matching API keys found.',
|
||||
'noresult.catalog.title': 'No Models',
|
||||
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
||||
'noresult.catalog.nofound': 'No matching models found.'
|
||||
'noresult.catalog.nofound': 'No matching models found.',
|
||||
'noresult.resources.cluster':
|
||||
'No clusters available. Add a cluster to get started.',
|
||||
'noresult.resources.worker':
|
||||
'No workers available. Add a worker to get started.',
|
||||
'noresult.resources.gotocluster': 'Create a Docker or Other Cluster',
|
||||
'noresult.resources.gotoworker': 'Add Worker'
|
||||
};
|
||||
|
||||
@@ -36,21 +36,34 @@ export default {
|
||||
'clusters.create.noImages': 'Нет доступных образов',
|
||||
'clusters.create.noInstanceTypes': 'Нет доступных типов инстансов',
|
||||
'clusters.create.noRegions': 'Нет доступных регионов',
|
||||
'clusters.workerpool.batchSize.desc': 'Количество воркеров, создаваемых одновременно в пуле воркеров',
|
||||
'clusters.create.addworker.tips': 'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.',
|
||||
'clusters.create.addCommand.tips': 'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
|
||||
'cluster.create.checkEnv.tips': 'Используйте следующую команду для проверки готовности окружения',
|
||||
'clusters.create.register.tips': 'На Kubernetes кластере, который необходимо добавить, выполните следующую команду, чтобы присоединить его узлы к кластеру.',
|
||||
'clusters.workerpool.batchSize.desc':
|
||||
'Количество воркеров, создаваемых одновременно в пуле воркеров',
|
||||
'clusters.create.addworker.tips':
|
||||
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.',
|
||||
'clusters.create.addCommand.tips':
|
||||
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
|
||||
'cluster.create.checkEnv.tips':
|
||||
'Используйте следующую команду для проверки готовности окружения',
|
||||
'clusters.create.register.tips':
|
||||
'На Kubernetes кластере, который необходимо добавить, выполните следующую команду, чтобы присоединить его узлы к кластеру.',
|
||||
'cluster.provider.comingsoon': 'Скоро будет',
|
||||
'clusters.addworker.nvidiaNotes-01': 'Если существует несколько исходящих IP-адресов, укажите тот, который должен использовать воркер. Пожалуйста, перепроверьте с помощью <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'clusters.addworker.nvidiaNotes-02': 'Если директория с моделями уже существует на воркере, вы можете указать путь для её монтирования.',
|
||||
'clusters.addworker.hygonNotes': 'Если директория <span class="bold-text">/opt/hyhal</span> не существует, создайте символическую ссылку на путь установки Hygon: <span class="bold-text">/opt/hyhal</span>. Аналогично для директории <span class="bold-text">/opt/dtk</span>.',
|
||||
'clusters.addworker.corexNotes': 'Если директория <span class="bold-text">/lib/modules</span> не существует, создайте символическую ссылку на путь установки Iluvatar: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Аналогично для директории <span class="bold-text">/usr/local/corex</span>.',
|
||||
'clusters.addworker.metaxNotes': 'Если директория <span class="bold-text">/opt/mxdriver</span> не существует, создайте символическую ссылку на путь установки MetaX: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Аналогично для директории <span class="bold-text">/opt/maca</span>.',
|
||||
'clusters.addworker.cambriconNotes': 'Если директория <span class="bold-text">/usr/local/neuware</span> не существует, создайте символическую ссылку на путь установки Cambricon: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||
'clusters.addworker.hygonNotes-02': 'Если не удается обнаружить устройства, попробуйте удалить <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
'clusters.addworker.nvidiaNotes-01':
|
||||
'Если существует несколько исходящих IP-адресов, укажите тот, который должен использовать воркер. Пожалуйста, перепроверьте с помощью <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||
'clusters.addworker.nvidiaNotes-02':
|
||||
'Если директория с моделями уже существует на воркере, вы можете указать путь для её монтирования.',
|
||||
'clusters.addworker.hygonNotes':
|
||||
'Если директория <span class="bold-text">/opt/hyhal</span> не существует, создайте символическую ссылку на путь установки Hygon: <span class="bold-text">/opt/hyhal</span>. Аналогично для директории <span class="bold-text">/opt/dtk</span>.',
|
||||
'clusters.addworker.corexNotes':
|
||||
'Если директория <span class="bold-text">/lib/modules</span> не существует, создайте символическую ссылку на путь установки Iluvatar: <span class="bold-text">ln -s /path/to/corex /lib/modules</span>. Аналогично для директории <span class="bold-text">/usr/local/corex</span>.',
|
||||
'clusters.addworker.metaxNotes':
|
||||
'Если директория <span class="bold-text">/opt/mxdriver</span> не существует, создайте символическую ссылку на путь установки MetaX: <span class="bold-text">ln -s /path/to/metax /opt/mxdriver</span>. Аналогично для директории <span class="bold-text">/opt/maca</span>.',
|
||||
'clusters.addworker.cambriconNotes':
|
||||
'Если директория <span class="bold-text">/usr/local/neuware</span> не существует, создайте символическую ссылку на путь установки Cambricon: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||
'clusters.addworker.hygonNotes-02':
|
||||
'Если не удается обнаружить устройства, попробуйте удалить <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||
'clusters.addworker.selectCluster': 'Выбрать кластер',
|
||||
'clusters.addworker.selectCluster.tips': 'Для не-Docker кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
||||
'clusters.addworker.selectCluster.tips':
|
||||
'Для не-Docker кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
||||
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
||||
'clusters.addworker.checkEnv': 'Проверить окружение',
|
||||
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
||||
@@ -65,6 +78,8 @@ export default {
|
||||
'clusters.addworker.workerIP': 'IP воркера',
|
||||
'clusters.addworker.notSpecified': 'Не указано',
|
||||
'clusters.addworker.autoDetect': 'Автоопределение',
|
||||
'clusters.addworker.extraVolume.holder': 'напр. /data/models (путь должен начинаться с /)',
|
||||
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}'
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'напр. /data/models (путь должен начинаться с /)',
|
||||
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}',
|
||||
'clusters.button.genToken': 'Сгенерировать токен'
|
||||
};
|
||||
|
||||
@@ -266,5 +266,17 @@ export default {
|
||||
'models.accessControlModal.includeusers': 'Включить пользователей',
|
||||
'models.table.genericProxy':
|
||||
'Используйте следующий префикс пути и укажите имя модели либо в заголовке запроса <span class="bold-text">X-GPUStack-Model</span>, либо в поле model в теле запроса. Все запросы с этим префиксом пути будут перенаправлены в бэкенд вывода.',
|
||||
'models.form.backendVersion.deprecated': 'Устаревший'
|
||||
'models.form.backendVersion.deprecated': 'Устаревший',
|
||||
'models.accessSettings.public.desc':
|
||||
'Accessible to anyone without authentication.',
|
||||
'models.accessSettings.authed.tips':
|
||||
'Accessible to all authenticated platform users.',
|
||||
'models.accessSettings.allowedUsers.tips':
|
||||
'Only designated users can access the model.'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
// 1. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
|
||||
// 2. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
|
||||
// 3. 'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export default {
|
||||
'noresult.button.add': 'Добавить сейчас',
|
||||
'noresult.mymodels.title': 'Нет доступных моделей',
|
||||
'noresult.mymodels.subTitle': 'Обратитесь к администратору для получения доступа.',
|
||||
'noresult.mymodels.subTitle':
|
||||
'Обратитесь к администратору для получения доступа.',
|
||||
'noresult.mymodels.nofound': 'Подходящие модели не найдены',
|
||||
'noresult.deployments.title': 'Нет развернутых моделей',
|
||||
'noresult.deployments.subTitle': 'Вы еще не развернули ни одной модели. Нажмите кнопку ниже, чтобы начать.',
|
||||
'noresult.deployments.subTitle':
|
||||
'Вы еще не развернули ни одной модели. Нажмите кнопку ниже, чтобы начать.',
|
||||
'noresult.gpus.title': 'GPU устройства не обнаружены',
|
||||
'noresult.gpus.subTitle': 'Проверьте, что статус Worker READY.',
|
||||
'noresult.gpus.nofound': 'Подходящие GPU устройства не найдены.',
|
||||
@@ -32,5 +34,18 @@ export default {
|
||||
'noresult.keys.nofound': 'Подходящие API-ключи не найдены.',
|
||||
'noresult.catalog.title': 'Нет моделей',
|
||||
'noresult.catalog.subTitle': 'Модели еще не настроены.',
|
||||
'noresult.catalog.nofound': 'Подходящие модели не найдены.'
|
||||
'noresult.catalog.nofound': 'Подходящие модели не найдены.',
|
||||
'noresult.resources.cluster':
|
||||
'No clusters available. Add a cluster to get started.',
|
||||
'noresult.resources.worker':
|
||||
'No workers available. Add a worker to get started.',
|
||||
'noresult.resources.gotocluster': 'Create a Docker or Other Cluster',
|
||||
'noresult.resources.gotoworker': 'Add Worker'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
// 1. 'noresult.resources.cluster': 'No clusters available. Add a cluster to get started.',
|
||||
// 2. 'noresult.resources.worker': 'No workers available. Add a worker to get started.',
|
||||
// 3. 'noresult.resources.gotocluster': 'Create a Docker or Other Cluster',
|
||||
// 4. 'noresult.resources.gotoworker': 'Add Worker'
|
||||
// ========================================================================
|
||||
|
||||
@@ -78,5 +78,6 @@ export default {
|
||||
'clusters.addworker.autoDetect': '自动检测',
|
||||
'clusters.addworker.extraVolume.holder':
|
||||
'例如:/data/models(路径需以 / 开头)',
|
||||
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项'
|
||||
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项',
|
||||
'clusters.button.genToken': '生成令牌'
|
||||
};
|
||||
|
||||
@@ -248,5 +248,8 @@ export default {
|
||||
'models.accessControlModal.includeusers': '显示用户',
|
||||
'models.table.genericProxy':
|
||||
'使用以下路径前缀,并在请求头 <span class="bold-text">X-GPUStack-Model</span> 或请求体中的 model 字段设置模型名称后访问该模型。该路径的所有子路径请求会被转发到推理后端。',
|
||||
'models.form.backendVersion.deprecated': '已弃用'
|
||||
'models.form.backendVersion.deprecated': '已弃用',
|
||||
'models.accessSettings.public.desc': '任何人无需认证即可访问。',
|
||||
'models.accessSettings.authed.tips': '平台内所有已认证用户可访问。',
|
||||
'models.accessSettings.allowedUsers.tips': '仅允许选定的特定用户访问。'
|
||||
};
|
||||
|
||||
@@ -33,5 +33,9 @@ export default {
|
||||
'noresult.keys.nofound': '未找到匹配的 API 密钥',
|
||||
'noresult.catalog.title': '暂无模型',
|
||||
'noresult.catalog.subTitle': '尚未配置任何模型。',
|
||||
'noresult.catalog.nofound': '未找到匹配的模型'
|
||||
'noresult.catalog.nofound': '未找到匹配的模型',
|
||||
'noresult.resources.cluster': '暂无可用集群,请添加集群以开始使用。',
|
||||
'noresult.resources.worker': '暂无可用节点,请添加节点以开始使用。',
|
||||
'noresult.resources.gotocluster': '创建 Docker 或其他类型的集群',
|
||||
'noresult.resources.gotoworker': '添加节点'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import PageBreadcrumb from '@/pages/_components/page-breadcrumb';
|
||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||
import { useAtom } from 'jotai';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
@@ -39,6 +41,7 @@ const ClusterCreate = () => {
|
||||
const action =
|
||||
(searchParams.get('action') as PageActionType) || PageAction.CREATE;
|
||||
const navigate = useNavigate();
|
||||
const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom);
|
||||
const [credentialList, setCredentialList] = useState<
|
||||
Global.BaseOption<number, { provider: ProviderType }>[]
|
||||
>([]);
|
||||
@@ -253,6 +256,14 @@ const ClusterCreate = () => {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (clusterSession?.firstAddCluster) {
|
||||
setClusterSession({
|
||||
firstAddCluster: false,
|
||||
firstAddWorker: false
|
||||
});
|
||||
navigate(`/cluster-management/clusters/list`);
|
||||
return;
|
||||
}
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expandKeysAtom } from '@/atoms/clusters';
|
||||
import { clusterSessionAtom, expandKeysAtom } from '@/atoms/clusters';
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
@@ -33,7 +33,11 @@ import {
|
||||
K8sStepsFromCluter
|
||||
} from './components/add-worker/config';
|
||||
import PoolRows from './components/pool-rows';
|
||||
import { ProviderType, ProviderValueMap } from './config';
|
||||
import {
|
||||
ClusterStatusValueMap,
|
||||
ProviderType,
|
||||
ProviderValueMap
|
||||
} from './config';
|
||||
import {
|
||||
ClusterListItem,
|
||||
CredentialListItem,
|
||||
@@ -65,6 +69,7 @@ const Clusters: React.FC = () => {
|
||||
});
|
||||
const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API);
|
||||
const [expandAtom] = useAtom(expandKeysAtom);
|
||||
const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom);
|
||||
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
||||
useExpandedRowKeys(expandAtom);
|
||||
const navigate = useNavigate();
|
||||
@@ -241,6 +246,27 @@ const Clusters: React.FC = () => {
|
||||
fetchCredentialList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
clusterSession?.firstAddWorker &&
|
||||
dataSource.loadend &&
|
||||
dataSource.dataList?.length > 0
|
||||
) {
|
||||
const targetCluster = dataSource.dataList.find(
|
||||
(cluster) =>
|
||||
cluster.state === ClusterStatusValueMap.Ready &&
|
||||
!cluster.workers &&
|
||||
!cluster.worker_pools?.length
|
||||
);
|
||||
|
||||
if (targetCluster) {
|
||||
handleAddWorker(targetCluster);
|
||||
// reset session
|
||||
setClusterSession(null);
|
||||
}
|
||||
}
|
||||
}, [clusterSession, dataSource.loadend, dataSource.dataList]);
|
||||
|
||||
const renderChildren = (
|
||||
list: any,
|
||||
options: { parent?: any; [key: string]: any }
|
||||
|
||||
@@ -4,8 +4,9 @@ import SealInput from '@/components/seal-form/seal-input';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { ExportOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Typography } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ProviderType, ProviderValueMap } from '../config';
|
||||
import {
|
||||
@@ -100,8 +101,21 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
]}
|
||||
>
|
||||
<SealInput.Password
|
||||
label={intl.formatMessage({ id: 'clusters.credential.token' })}
|
||||
label={intl.formatMessage({
|
||||
id: 'clusters.credential.token'
|
||||
})}
|
||||
required={action === PageAction.CREATE}
|
||||
labelExtra={
|
||||
<Typography.Link
|
||||
href="https://cloud.digitalocean.com/account/api/tokens"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ marginLeft: 8, lineHeight: 1 }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'clusters.button.genToken' })}{' '}
|
||||
<ExportOutlined style={{ fontSize: 12 }} />
|
||||
</Typography.Link>
|
||||
}
|
||||
></SealInput.Password>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AlertBlockInfo from '@/components/alert-info/block';
|
||||
import TooltipList from '@/components/tooltip-list';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import TransferInner from '@/pages/_components/transfer';
|
||||
@@ -29,7 +30,34 @@ import { AccessControlFormData, ListItem } from '../../config/types';
|
||||
|
||||
type TransferKey = string | number | bigint;
|
||||
|
||||
const accessScopeTips = [
|
||||
{
|
||||
title: {
|
||||
text: 'models.accessSettings.authed',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.accessSettings.authed.tips'
|
||||
},
|
||||
{
|
||||
title: {
|
||||
text: 'models.accessSettings.allowedUsers',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.accessSettings.allowedUsers.tips'
|
||||
},
|
||||
{
|
||||
title: {
|
||||
text: 'models.accessSettings.public',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.accessSettings.public.desc'
|
||||
}
|
||||
];
|
||||
|
||||
const Label = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-weight: 500;
|
||||
margin-block: 8px 12px;
|
||||
font-size: 14px;
|
||||
@@ -263,7 +291,13 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
||||
access_policy: action === PageAction.CREATE ? 'authed' : undefined
|
||||
}}
|
||||
>
|
||||
<Label>{intl.formatMessage({ id: 'models.table.accessScope' })}</Label>
|
||||
<Label>
|
||||
{intl.formatMessage({ id: 'models.table.accessScope' })}
|
||||
<Tooltip title={<TooltipList list={accessScopeTips}></TooltipList>}>
|
||||
<QuestionCircleOutlined />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
|
||||
<Form.Item<AccessControlFormData> name="access_policy" noStyle>
|
||||
<Radio.Group
|
||||
onChange={handleOnPolicyChange}
|
||||
@@ -307,7 +341,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
||||
id: 'models.table.userSelection.tips'
|
||||
})}
|
||||
>
|
||||
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
|
||||
<QuestionCircleOutlined />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
<Form.Item<AccessControlFormData> name="users">
|
||||
|
||||
@@ -110,7 +110,6 @@ const AddModal: FC<AddModalProps> = (props) => {
|
||||
|
||||
const { checkOnlyAscendNPU } = useCheckBackend();
|
||||
const {
|
||||
handleShowCompatibleAlert,
|
||||
setWarningStatus,
|
||||
handleBackendChangeBefore,
|
||||
cancelEvaluate,
|
||||
|
||||
@@ -429,6 +429,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
data: row
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelect = useMemoizedFn(async (val: any, row: ListItem) => {
|
||||
try {
|
||||
if (val === 'edit') {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { modelNameReg } from '@/config';
|
||||
import { modelNameReg, PageAction } from '@/config';
|
||||
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import {
|
||||
ClusterStatusLabelMap,
|
||||
ClusterStatusValueMap
|
||||
} from '@/pages/cluster-management/config';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Link, useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import { sourceOptions } from '../config';
|
||||
import { FormData } from '../config/types';
|
||||
@@ -28,6 +30,26 @@ interface BasicFormProps {
|
||||
onSourceChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const NotFoundCredentialContent: React.FC = () => {
|
||||
const [, setFromClusterCreation] = useAtom(clusterSessionAtom);
|
||||
const intl = useIntl();
|
||||
const handleOnClick = () => {
|
||||
setFromClusterCreation({
|
||||
firstAddWorker: false,
|
||||
firstAddCluster: true
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/cluster-management/clusters/create?action=${PageAction.CREATE}`}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{intl.formatMessage({ id: 'noresult.resources.gotocluster' })}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const BasicForm: React.FC<BasicFormProps> = (props) => {
|
||||
const {
|
||||
fields = [],
|
||||
@@ -118,6 +140,9 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
||||
>
|
||||
{
|
||||
<SealSelect
|
||||
notFoundContent={
|
||||
<NotFoundCredentialContent></NotFoundCredentialContent>
|
||||
}
|
||||
onChange={handleClusterChange}
|
||||
label={intl.formatMessage({ id: 'clusters.title' })}
|
||||
options={clusterOptions}
|
||||
|
||||
@@ -173,6 +173,16 @@ export const useCheckCompatibility = () => {
|
||||
|
||||
const handleEvaluate = async (data: any) => {
|
||||
try {
|
||||
// when no cluster selected, show warning and prompt user to add cluster first
|
||||
if (!data.cluster_id) {
|
||||
setWarningStatus({
|
||||
show: true,
|
||||
title: '',
|
||||
type: 'warning',
|
||||
message: intl.formatMessage({ id: 'noresult.resources.cluster' })
|
||||
});
|
||||
return;
|
||||
}
|
||||
checkTokenRef.current?.cancel();
|
||||
checkTokenRef.current = createAxiosToken();
|
||||
setWarningStatus({
|
||||
@@ -407,6 +417,7 @@ export const useCheckCompatibility = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// when custom backend, and no run_command or image_name, skip evaluate
|
||||
if (
|
||||
backendOptionsMap.custom === allValues.backend &&
|
||||
(!allValues.run_command || !allValues.image_name)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { PageAction } from '@/config';
|
||||
import NoResult from '@/pages/_components/no-result';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
const useNoResourceResult = (props: {
|
||||
iconType: string;
|
||||
loading?: boolean;
|
||||
loadend?: boolean;
|
||||
dataSource?: any[];
|
||||
queryParams?: Record<string, any>;
|
||||
title: React.ReactNode;
|
||||
noClusters?: boolean;
|
||||
noWorkers?: boolean;
|
||||
subTitle?: React.ReactNode;
|
||||
defaultContent?: {
|
||||
subTitle: string;
|
||||
noFoundText: string;
|
||||
buttonText: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
noClusters,
|
||||
noWorkers,
|
||||
defaultContent,
|
||||
loading,
|
||||
loadend,
|
||||
dataSource,
|
||||
queryParams,
|
||||
iconType,
|
||||
title,
|
||||
subTitle
|
||||
} = props;
|
||||
const [, setClusterSession] = useAtom(clusterSessionAtom);
|
||||
|
||||
const handleClick = useMemoizedFn(() => {
|
||||
if (noClusters) {
|
||||
setClusterSession({
|
||||
firstAddWorker: false,
|
||||
firstAddCluster: true
|
||||
});
|
||||
|
||||
navigate(
|
||||
`/cluster-management/clusters/create?action=${PageAction.CREATE}`
|
||||
);
|
||||
}
|
||||
|
||||
if (noWorkers) {
|
||||
setClusterSession({
|
||||
firstAddWorker: true,
|
||||
firstAddCluster: false
|
||||
});
|
||||
navigate(`/cluster-management/clusters/list`);
|
||||
}
|
||||
});
|
||||
|
||||
const statusContent = useMemo(() => {
|
||||
if (noClusters) {
|
||||
return {
|
||||
subTitle:
|
||||
subTitle || intl.formatMessage({ id: 'noresult.resources.cluster' }),
|
||||
noFoundText: defaultContent?.noFoundText || '',
|
||||
buttonText: intl.formatMessage({
|
||||
id: 'noresult.resources.gotocluster'
|
||||
}),
|
||||
onClick: handleClick
|
||||
};
|
||||
}
|
||||
|
||||
if (noWorkers) {
|
||||
return {
|
||||
subTitle:
|
||||
subTitle || intl.formatMessage({ id: 'noresult.resources.worker' }),
|
||||
noFoundText: defaultContent?.noFoundText || '',
|
||||
buttonText: intl.formatMessage({ id: 'noresult.resources.gotoworker' }),
|
||||
onClick: handleClick
|
||||
};
|
||||
}
|
||||
return {
|
||||
...defaultContent
|
||||
};
|
||||
}, [noClusters, noWorkers, intl]);
|
||||
|
||||
const noResourceResult = (
|
||||
<NoResult
|
||||
loading={loading}
|
||||
loadend={loadend}
|
||||
dataSource={dataSource}
|
||||
image={<IconFont type={iconType} />}
|
||||
filters={queryParams}
|
||||
noFoundText={statusContent.noFoundText}
|
||||
title={title}
|
||||
subTitle={statusContent.subTitle}
|
||||
>
|
||||
<Button type="primary" iconPosition="end" onClick={statusContent.onClick}>
|
||||
{statusContent.buttonText}
|
||||
</Button>
|
||||
</NoResult>
|
||||
);
|
||||
return {
|
||||
noResourceResult
|
||||
};
|
||||
};
|
||||
|
||||
export default useNoResourceResult;
|
||||
@@ -156,11 +156,15 @@ const Playground: React.FC = () => {
|
||||
activeKey={activeKey}
|
||||
key="view-code-buttons"
|
||||
/>,
|
||||
<Divider
|
||||
key="divider"
|
||||
type="vertical"
|
||||
style={{ height: 24, marginInline: 16 }}
|
||||
/>,
|
||||
<>
|
||||
{activeKey === 'chat' && (
|
||||
<Divider
|
||||
key="divider"
|
||||
type="vertical"
|
||||
style={{ height: 24, marginInline: 16 }}
|
||||
/>
|
||||
)}
|
||||
</>,
|
||||
<ExtraContent key="extra-content" />
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import DeleteModal from '@/components/delete-modal';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import PageBox from '@/pages/_components/page-box';
|
||||
@@ -11,11 +10,11 @@ import {
|
||||
} from '@/pages/cluster-management/config';
|
||||
import { ClusterListItem } from '@/pages/cluster-management/config/types';
|
||||
import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker';
|
||||
import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, ConfigProvider, Table, message } from 'antd';
|
||||
import { ConfigProvider, Table, message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import NoResult from '../../_components/no-result';
|
||||
import {
|
||||
WORKERS_API,
|
||||
deleteWorker,
|
||||
@@ -191,24 +190,26 @@ const Workers: React.FC = () => {
|
||||
handleAddWorker(currentData as ClusterListItem);
|
||||
};
|
||||
|
||||
const { noResourceResult } = useNoResourceResult({
|
||||
loadend: dataSource.loadend,
|
||||
loading: dataSource.loading,
|
||||
dataSource: dataSource.dataList,
|
||||
queryParams: queryParams,
|
||||
iconType: 'icon-resources',
|
||||
title: intl.formatMessage({ id: 'noresult.workers.title' }),
|
||||
noClusters: !clusterData.list.length,
|
||||
noWorkers: dataSource.dataList.length === 0 && clusterData.list.length > 0,
|
||||
defaultContent: {
|
||||
subTitle: intl.formatMessage({ id: 'noresult.workers.subTitle' }),
|
||||
noFoundText: intl.formatMessage({ id: 'noresult.workers.nofound' }),
|
||||
buttonText: intl.formatMessage({ id: 'noresult.workers.button.add' }),
|
||||
onClick: handleOnAddWorker
|
||||
}
|
||||
});
|
||||
|
||||
const renderEmpty = (type?: string) => {
|
||||
if (type !== 'Table') return;
|
||||
return (
|
||||
<NoResult
|
||||
loading={dataSource.loading}
|
||||
loadend={dataSource.loadend}
|
||||
dataSource={dataSource.dataList}
|
||||
image={<IconFont type="icon-resources" />}
|
||||
filters={queryParams}
|
||||
noFoundText={intl.formatMessage({ id: 'noresult.workers.nofound' })}
|
||||
title={intl.formatMessage({ id: 'noresult.workers.title' })}
|
||||
subTitle={intl.formatMessage({ id: 'noresult.workers.subTitle' })}
|
||||
>
|
||||
<Button type="primary" onClick={handleOnAddWorker}>
|
||||
{intl.formatMessage({ id: 'noresult.workers.button.add' })}
|
||||
</Button>
|
||||
</NoResult>
|
||||
);
|
||||
return noResourceResult;
|
||||
};
|
||||
|
||||
const handleClusterChange = (value: number) => {
|
||||
|
||||
Reference in New Issue
Block a user