feat: set default cluster

This commit is contained in:
jialin
2025-12-23 20:54:16 +08:00
parent 530e8f7537
commit 1b661bb8cd
17 changed files with 102 additions and 14 deletions
+3 -1
View File
@@ -10,6 +10,7 @@ import {
KubernetesOutlined, KubernetesOutlined,
ProfileOutlined, ProfileOutlined,
RetweetOutlined, RetweetOutlined,
StarOutlined,
ThunderboltOutlined ThunderboltOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import React from 'react'; import React from 'react';
@@ -58,7 +59,8 @@ const icons = {
LockPerson: React.createElement(IconFont, { type: 'icon-lock_person' }), LockPerson: React.createElement(IconFont, { type: 'icon-lock_person' }),
LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }), LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }),
Permission: React.createElement(IconFont, { type: 'icon-permission' }), 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; export default icons;
+3 -1
View File
@@ -110,5 +110,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': '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.'
}; };
+5 -1
View File
@@ -110,7 +110,9 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': '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) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -200,4 +202,6 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
// 80. 'clusters.table.ip.external': 'External', // 80. 'clusters.table.ip.external': 'External',
// 81. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.', // 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.', // 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 ========== // ========== End of To-Do List ==========
+5 -1
View File
@@ -110,7 +110,9 @@ export default {
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': '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) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -130,4 +132,6 @@ export default {
// 11. 'clusters.table.ip.external': 'External', // 11. 'clusters.table.ip.external': 'External',
// 12. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.' // 12. 'clusters.form.serverUrl.tips': 'Specify the server URL accessible from your cloud provider.'
// 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP <span class="text-tertiary">{type}</span>', // 13. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP <span class="text-tertiary">{type}</span>',
// 14. 'clusters.form.setDefault': 'Set as Default',
// 15. 'clusters.form.setDefault.tips': 'Default for deployment'
// ================================================================ // ================================================================
+3 -1
View File
@@ -106,5 +106,7 @@ export default {
'clusters.addworker.dataVolume.tips': '为 GPUStack 指定数据存储路径。', 'clusters.addworker.dataVolume.tips': '为 GPUStack 指定数据存储路径。',
'clusters.table.ip.internal': '内', 'clusters.table.ip.internal': '内',
'clusters.table.ip.external': '外', 'clusters.table.ip.external': '外',
'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。' 'clusters.form.serverUrl.tips': '指定可从您的云服务提供商访问的服务器地址。',
'clusters.form.setDefault': '设为默认',
'clusters.form.setDefault.tips': '部署时的默认集群。'
}; };
@@ -11,7 +11,7 @@ import { ListItem } from '../config/types';
interface ColumnsHookProps { interface ColumnsHookProps {
handleSelect: (val: string, record: ListItem) => void; handleSelect: (val: string, record: ListItem) => void;
sortOrder: string; sortOrder: string[];
} }
const actionList: Global.ActionItem[] = [ const actionList: Global.ActionItem[] = [
@@ -198,3 +198,9 @@ export async function querySystemConfig() {
method: 'GET' method: 'GET'
}); });
} }
export async function setDefaultCluster(params: { id: number }) {
return request(`${CLUSTERS_API}/${params.id}/set-default`, {
method: 'POST'
});
}
@@ -8,7 +8,13 @@ import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { PageContainerInner } from '../_components/page-box'; import { PageContainerInner } from '../_components/page-box';
import { createCluster, queryClusterToken, queryCredentialList } from './apis'; import {
createCluster,
queryClusterList,
queryClusterToken,
queryCredentialList,
setDefaultCluster
} from './apis';
import { import {
DockerStepsFromCluster, DockerStepsFromCluster,
K8sStepsFromCluter K8sStepsFromCluter
@@ -248,6 +254,20 @@ const ClusterCreate = () => {
return step?.showButtons ? step?.showButtons(extraData.provider) : {}; return step?.showButtons ? step?.showButtons(extraData.provider) : {};
}, [currentStep, steps, 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 submit = async (values: ClusterFormData) => {
const data = { const data = {
...extraData, ...extraData,
@@ -259,6 +279,7 @@ const ClusterCreate = () => {
...info, ...info,
cluster_id: res.id cluster_id: res.id
}); });
checkDefaultCluster();
return true; return true;
}; };
@@ -26,6 +26,7 @@ import {
queryClusterList, queryClusterList,
queryCredentialList, queryCredentialList,
queryWorkerPools, queryWorkerPools,
setDefaultCluster,
updateCluster, updateCluster,
WORKER_POOLS_API WORKER_POOLS_API
} from './apis'; } from './apis';
@@ -192,6 +193,10 @@ const Clusters: React.FC = () => {
} else if (val === 'register_cluster') { } else if (val === 'register_cluster') {
handleAddWorker(row); handleAddWorker(row);
setStepList(K8sStepsFromCluter); setStepList(K8sStepsFromCluter);
} else if (val === 'isDefault') {
setDefaultCluster({ id: row.id }).then(() => {
message.success(intl.formatMessage({ id: 'common.message.success' }));
});
} }
}); });
@@ -151,7 +151,11 @@ const ClusterForm: React.FC<AddModalProps> = forwardRef(
credentialList={credentialList} credentialList={credentialList}
></CloudProvider> ></CloudProvider>
)} )}
<Form.Item<FormData> name="description" rules={[{ required: false }]}> <Form.Item<FormData>
name="description"
rules={[{ required: false }]}
style={{ marginBottom: 8 }}
>
<SealTextArea <SealTextArea
autoSize={{ minRows: 2, maxRows: 4 }} autoSize={{ minRows: 2, maxRows: 4 }}
label={intl.formatMessage({ id: 'common.table.description' })} label={intl.formatMessage({ id: 'common.table.description' })}
@@ -112,6 +112,11 @@ export const clusterActionList = [
locale: true, locale: true,
icon: icons.Catalog1 icon: icons.Catalog1
}, },
{
key: 'isDefault',
label: 'clusters.form.setDefault',
icon: icons.StarOutlined
},
{ {
key: 'delete', key: 'delete',
label: 'common.button.delete', label: 'common.button.delete',
@@ -52,6 +52,7 @@ export interface NodePoolListItem extends NodePoolFormData {
export interface ClusterListItem { export interface ClusterListItem {
name: string; name: string;
display_name: string; display_name: string;
is_default: boolean;
description: string; description: string;
worker_config: Record<string, any>; worker_config: Record<string, any>;
provider: ProviderType; provider: ProviderType;
@@ -72,6 +73,7 @@ export interface ClusterFormData {
name: string; name: string;
description: string; description: string;
provider: ProviderType; provider: ProviderType;
is_default?: boolean;
credential_id: number; credential_id: number;
zone: string; zone: string;
region: string; region: string;
@@ -4,7 +4,9 @@ import DropdownButtons from '@/components/drop-down-buttons';
import { SealColumnProps } from '@/components/seal-table/types'; import { SealColumnProps } from '@/components/seal-table/types';
import StatusTag from '@/components/status-tag'; import StatusTag from '@/components/status-tag';
import { tableSorter } from '@/config/settings'; import { tableSorter } from '@/config/settings';
import { StarFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tooltip } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import {
@@ -37,7 +39,20 @@ const useClusterColumns = (
sorter: tableSorter(1), sorter: tableSorter(1),
span: 3, span: 3,
render: (text: string, record: ClusterListItem) => ( render: (text: string, record: ClusterListItem) => (
<AutoTooltip ghost>{text}</AutoTooltip> <>
<AutoTooltip ghost>{text}</AutoTooltip>
{record.is_default && (
<Tooltip
title={intl.formatMessage({
id: 'clusters.form.setDefault.tips'
})}
>
<StarFilled
style={{ color: 'var(--ant-gold-4)', marginLeft: 4 }}
/>
</Tooltip>
)}
</>
) )
}, },
{ {
@@ -191,6 +191,10 @@ const AddModal: React.FC<AddModalProps> = (props) => {
}; };
const initClusterId = (): number => { const initClusterId = (): number => {
const defaultCluster = clusterList?.find((item) => item.is_default);
if (defaultCluster) {
return defaultCluster.value;
}
const cluster_id = const cluster_id =
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready) clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
?.value || clusterList?.[0]?.value; ?.value || clusterList?.[0]?.value;
@@ -75,7 +75,7 @@ type AddModalProps = {
deploymentType?: 'modelList' | 'modelFiles'; deploymentType?: 'modelList' | 'modelFiles';
clusterList: Global.BaseOption< clusterList: Global.BaseOption<
number, number,
{ provider: string; state: string | number } { provider: string; state: string | number; is_default: boolean }
>[]; >[];
onOk: (values: FormData) => void; onOk: (values: FormData) => void;
onCancel: () => void; onCancel: () => void;
@@ -441,6 +441,12 @@ const AddModal: FC<AddModalProps> = (props) => {
if (initialValues?.cluster_id) { if (initialValues?.cluster_id) {
return 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 = const cluster_id =
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready) clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
?.value || clusterList?.[0]?.value; ?.value || clusterList?.[0]?.value;
@@ -217,7 +217,8 @@ export const useGenerateWorkerOptions = () => {
label: item.name, label: item.name,
value: item.id, value: item.id,
provider: item.provider as string, 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 [, setWorkerListAtom] = useAtom(workerListAtom);
const [clusterList, setClusterList] = useState< const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string }>[] Global.BaseOption<
number,
{ provider: string; state: string; is_default: boolean }
>[]
>([]); >([]);
const [workerList, setWorkerList] = useState<WorkerListItem[]>([]); const [workerList, setWorkerList] = useState<WorkerListItem[]>([]);
@@ -249,7 +253,8 @@ export default function useFormInitialValues() {
label: item.name, label: item.name,
value: item.id, value: item.id,
provider: item.provider as string, provider: item.provider as string,
state: item.state state: item.state,
is_default: item.is_default
})); }));
setClusterList(list); setClusterList(list);
setClusterListAtom(list); setClusterListAtom(list);
+2 -1
View File
@@ -1,4 +1,5 @@
import { initialPasswordAtom } from '@/atoms/user'; import { initialPasswordAtom } from '@/atoms/user';
import { clearStorageUserSettings } from '@/atoms/utils';
import { import {
CRYPT_TEXT, CRYPT_TEXT,
REMEMBER_ME_KEY, REMEMBER_ME_KEY,
@@ -81,7 +82,7 @@ export const useLocalAuth = ({
if (userInfo?.require_password_change) { if (userInfo?.require_password_change) {
setInitialPassword(encryptPassword(values.password)); setInitialPassword(encryptPassword(values.password));
} }
clearStorageUserSettings();
onSuccess?.(userInfo); onSuccess?.(userInfo);
} catch (error: any) { } catch (error: any) {
onError?.(error); onError?.(error);