diff --git a/src/components/icon-font/icons.ts b/src/components/icon-font/icons.ts index 119d661a..8e36f053 100644 --- a/src/components/icon-font/icons.ts +++ b/src/components/icon-font/icons.ts @@ -10,6 +10,7 @@ import { KubernetesOutlined, ProfileOutlined, RetweetOutlined, + StarOutlined, ThunderboltOutlined } from '@ant-design/icons'; import React from 'react'; @@ -58,7 +59,8 @@ const icons = { LockPerson: React.createElement(IconFont, { type: 'icon-lock_person' }), LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }), Permission: React.createElement(IconFont, { type: 'icon-permission' }), - CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }) + CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }), + StarOutlined: React.createElement(StarOutlined) }; export default icons; diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index b1624f39..f528f55f 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -110,5 +110,7 @@ Same applies to the /opt/dtk directory.`, 'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.external': 'External', 'clusters.form.serverUrl.tips': - 'Specify the server URL accessible from your cloud provider.' + 'Specify the server URL accessible from your cloud provider.', + 'clusters.form.setDefault': 'Set as Default', + 'clusters.form.setDefault.tips': 'Default for deployment.' }; diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index 4b0eaaf2..95a3089a 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -110,7 +110,9 @@ Same applies to the /opt/dtk directory.`, 'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.external': 'External', 'clusters.form.serverUrl.tips': - 'Specify the server URL accessible from your cloud provider.' + 'Specify the server URL accessible from your cloud provider.', + 'clusters.form.setDefault': 'Set as Default', + 'clusters.form.setDefault.tips': 'Default for deployment.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -200,4 +202,6 @@ Same applies to the /opt/dtk directory.`, // 80. 'clusters.table.ip.external': 'External', // 81. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.', // 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.', +// 83. 'clusters.form.setDefault': 'Set as Default', +// 84. 'clusters.form.setDefault.tips': 'Default for deployment' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index d802f2cc..c9abacfc 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -110,7 +110,9 @@ export default { 'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.external': 'External', 'clusters.form.serverUrl.tips': - 'Specify the server URL accessible from your cloud provider.' + 'Specify the server URL accessible from your cloud provider.', + 'clusters.form.setDefault': 'Set as Default', + 'clusters.form.setDefault.tips': 'Default for deployment.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -130,4 +132,6 @@ export default { // 11. 'clusters.table.ip.external': 'External', // 12. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.' // 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP {type}', +// 14. 'clusters.form.setDefault': 'Set as Default', +// 15. 'clusters.form.setDefault.tips': 'Default for deployment' // ================================================================ diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index 438022eb..385eabd0 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -106,5 +106,7 @@ export default { 'clusters.addworker.dataVolume.tips': '为 GPUStack 指定数据存储路径。', 'clusters.table.ip.internal': '内', 'clusters.table.ip.external': '外', - 'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。' + 'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。', + 'clusters.form.setDefault': '设为默认', + 'clusters.form.setDefault.tips': '部署时的默认集群。' }; diff --git a/src/pages/api-keys/hooks/use-keys-columns.tsx b/src/pages/api-keys/hooks/use-keys-columns.tsx index 7deb0758..ad4a2a80 100644 --- a/src/pages/api-keys/hooks/use-keys-columns.tsx +++ b/src/pages/api-keys/hooks/use-keys-columns.tsx @@ -11,7 +11,7 @@ import { ListItem } from '../config/types'; interface ColumnsHookProps { handleSelect: (val: string, record: ListItem) => void; - sortOrder: string; + sortOrder: string[]; } const actionList: Global.ActionItem[] = [ diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts index b6f19a82..4b3b9b37 100644 --- a/src/pages/cluster-management/apis/index.ts +++ b/src/pages/cluster-management/apis/index.ts @@ -198,3 +198,9 @@ export async function querySystemConfig() { method: 'GET' }); } + +export async function setDefaultCluster(params: { id: number }) { + return request(`${CLUSTERS_API}/${params.id}/set-default`, { + method: 'POST' + }); +} diff --git a/src/pages/cluster-management/cluster-create.tsx b/src/pages/cluster-management/cluster-create.tsx index 6376e5f8..3bf43870 100644 --- a/src/pages/cluster-management/cluster-create.tsx +++ b/src/pages/cluster-management/cluster-create.tsx @@ -8,7 +8,13 @@ import _ from 'lodash'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { PageContainerInner } from '../_components/page-box'; -import { createCluster, queryClusterToken, queryCredentialList } from './apis'; +import { + createCluster, + queryClusterList, + queryClusterToken, + queryCredentialList, + setDefaultCluster +} from './apis'; import { DockerStepsFromCluster, K8sStepsFromCluter @@ -248,6 +254,20 @@ const ClusterCreate = () => { return step?.showButtons ? step?.showButtons(extraData.provider) : {}; }, [currentStep, steps, extraData.provider]); + const checkDefaultCluster = async () => { + try { + const res = await queryClusterList({ page: 1, perPage: 10 }); + if (res.items.length === 1) { + const cluster = res.items[0]; + if (!cluster.is_default) { + await setDefaultCluster({ + id: cluster.id + }); + } + } + } catch (error) {} + }; + const submit = async (values: ClusterFormData) => { const data = { ...extraData, @@ -259,6 +279,7 @@ const ClusterCreate = () => { ...info, cluster_id: res.id }); + checkDefaultCluster(); return true; }; diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index c22188b8..0cf9e045 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -26,6 +26,7 @@ import { queryClusterList, queryCredentialList, queryWorkerPools, + setDefaultCluster, updateCluster, WORKER_POOLS_API } from './apis'; @@ -192,6 +193,10 @@ const Clusters: React.FC = () => { } else if (val === 'register_cluster') { handleAddWorker(row); setStepList(K8sStepsFromCluter); + } else if (val === 'isDefault') { + setDefaultCluster({ id: row.id }).then(() => { + message.success(intl.formatMessage({ id: 'common.message.success' })); + }); } }); diff --git a/src/pages/cluster-management/components/cluster-form.tsx b/src/pages/cluster-management/components/cluster-form.tsx index 4b8703e7..e84682ec 100644 --- a/src/pages/cluster-management/components/cluster-form.tsx +++ b/src/pages/cluster-management/components/cluster-form.tsx @@ -151,7 +151,11 @@ const ClusterForm: React.FC = forwardRef( credentialList={credentialList} > )} - name="description" rules={[{ required: false }]}> + + name="description" + rules={[{ required: false }]} + style={{ marginBottom: 8 }} + > ; provider: ProviderType; @@ -72,6 +73,7 @@ export interface ClusterFormData { name: string; description: string; provider: ProviderType; + is_default?: boolean; credential_id: number; zone: string; region: string; diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index 10fa261a..99ad869f 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -4,7 +4,9 @@ import DropdownButtons from '@/components/drop-down-buttons'; import { SealColumnProps } from '@/components/seal-table/types'; import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; +import { StarFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; +import { Tooltip } from 'antd'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { @@ -37,7 +39,20 @@ const useClusterColumns = ( sorter: tableSorter(1), span: 3, render: (text: string, record: ClusterListItem) => ( - {text} + <> + {text} + {record.is_default && ( + + + + )} + ) }, { diff --git a/src/pages/llmodels/components/deploy-builtin-modal.tsx b/src/pages/llmodels/components/deploy-builtin-modal.tsx index a7de8d05..a7e7d7c4 100644 --- a/src/pages/llmodels/components/deploy-builtin-modal.tsx +++ b/src/pages/llmodels/components/deploy-builtin-modal.tsx @@ -191,6 +191,10 @@ const AddModal: React.FC = (props) => { }; const initClusterId = (): number => { + const defaultCluster = clusterList?.find((item) => item.is_default); + if (defaultCluster) { + return defaultCluster.value; + } const cluster_id = clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready) ?.value || clusterList?.[0]?.value; diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 4959659d..4548ee24 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -75,7 +75,7 @@ type AddModalProps = { deploymentType?: 'modelList' | 'modelFiles'; clusterList: Global.BaseOption< number, - { provider: string; state: string | number } + { provider: string; state: string | number; is_default: boolean } >[]; onOk: (values: FormData) => void; onCancel: () => void; @@ -441,6 +441,12 @@ const AddModal: FC = (props) => { if (initialValues?.cluster_id) { return initialValues.cluster_id; } + // Find default cluster + const defaultCluster = clusterList?.find((item) => item.is_default); + if (defaultCluster) { + return defaultCluster.value; + } + const cluster_id = clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready) ?.value || clusterList?.[0]?.value; diff --git a/src/pages/llmodels/hooks/use-form-initial-values.ts b/src/pages/llmodels/hooks/use-form-initial-values.ts index 482d9773..00efd192 100644 --- a/src/pages/llmodels/hooks/use-form-initial-values.ts +++ b/src/pages/llmodels/hooks/use-form-initial-values.ts @@ -217,7 +217,8 @@ export const useGenerateWorkerOptions = () => { label: item.name, value: item.id, provider: item.provider as string, - state: item.state + state: item.state, + is_default: item.is_default })) ); }; @@ -235,7 +236,10 @@ export default function useFormInitialValues() { const [, setWorkerListAtom] = useAtom(workerListAtom); const [clusterList, setClusterList] = useState< - Global.BaseOption[] + Global.BaseOption< + number, + { provider: string; state: string; is_default: boolean } + >[] >([]); const [workerList, setWorkerList] = useState([]); @@ -249,7 +253,8 @@ export default function useFormInitialValues() { label: item.name, value: item.id, provider: item.provider as string, - state: item.state + state: item.state, + is_default: item.is_default })); setClusterList(list); setClusterListAtom(list); diff --git a/src/pages/login/hooks/use-local-auth.ts b/src/pages/login/hooks/use-local-auth.ts index acc90cd6..d6fa3412 100644 --- a/src/pages/login/hooks/use-local-auth.ts +++ b/src/pages/login/hooks/use-local-auth.ts @@ -1,4 +1,5 @@ import { initialPasswordAtom } from '@/atoms/user'; +import { clearStorageUserSettings } from '@/atoms/utils'; import { CRYPT_TEXT, REMEMBER_ME_KEY, @@ -81,7 +82,7 @@ export const useLocalAuth = ({ if (userInfo?.require_password_change) { setInitialPassword(encryptPassword(values.password)); } - + clearStorageUserSettings(); onSuccess?.(userInfo); } catch (error: any) { onError?.(error);