From ac800a303a6b208951bef47f0de1435a2ffed038 Mon Sep 17 00:00:00 2001 From: Yuxing Deng Date: Tue, 9 Jun 2026 16:45:52 +0800 Subject: [PATCH] fix: remove the pined worker config changed notice --- src/locales/en-US/clusters.ts | 2 - src/locales/ja-JP/clusters.ts | 2 - src/locales/ru-RU/clusters.ts | 2 - src/locales/tr-TR/clusters.ts | 2 - src/locales/zh-CN/clusters.ts | 2 - .../components/k8s-pod-spec.tsx | 75 ++++++++++++------- .../step-forms/advance-config.tsx | 11 +-- 7 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index 61237568..b217004e 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -113,8 +113,6 @@ export default { 'clusters.create.workerConfig': 'Worker Configuration', 'clusters.edit.k8sOptions.changed.tip': 'You have changed the Kubernetes options. Re-run the registration command on the target cluster for the changes to take effect.', - 'clusters.edit.workerConfig.tip': - 'Changes to the worker configuration take effect only after restarting the affected workers.', 'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName.tips': 'Specify a name for the worker container.', diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index e3593a3e..60a668c7 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -113,8 +113,6 @@ export default { 'clusters.create.workerConfig': 'Worker Configuration', 'clusters.edit.k8sOptions.changed.tip': 'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。', - 'clusters.edit.workerConfig.tip': - 'ワーカー設定の変更は、対象のワーカーを再起動した後に有効になります。', 'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName.tips': 'Specify a name for the worker container.', diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index 2f530855..aed4a17e 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -113,8 +113,6 @@ export default { 'clusters.create.workerConfig': 'Конфигурация воркера', 'clusters.edit.k8sOptions.changed.tip': 'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.', - 'clusters.edit.workerConfig.tip': - 'Изменения конфигурации воркера вступают в силу только после перезапуска соответствующих воркеров.', 'clusters.addworker.containerName': 'Имя контейнера воркера', 'clusters.addworker.containerName.tips': 'Укажите имя для контейнера воркера.', diff --git a/src/locales/tr-TR/clusters.ts b/src/locales/tr-TR/clusters.ts index 81b7b0e9..f5f84ddc 100644 --- a/src/locales/tr-TR/clusters.ts +++ b/src/locales/tr-TR/clusters.ts @@ -113,8 +113,6 @@ export default { 'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması', 'clusters.edit.k8sOptions.changed.tip': 'Kubernetes seçeneklerini değiştirdiniz. Değişikliklerin etkili olması için kayıt komutunu hedef kümede yeniden çalıştırın.', - 'clusters.edit.workerConfig.tip': - 'İşçi düğüm yapılandırmasındaki değişiklikler yalnızca ilgili işçi düğümleri yeniden başlatıldıktan sonra etkili olur.', 'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı', 'clusters.addworker.containerName.tips': 'İşçi düğüm konteyneri için bir ad belirtin.', diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index 25fd1350..d12d070b 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -111,8 +111,6 @@ export default { 'clusters.create.workerConfig': '节点配置', 'clusters.edit.k8sOptions.changed.tip': '您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。', - 'clusters.edit.workerConfig.tip': - '修改节点配置后,需要重启对应节点才会生效。', 'clusters.addworker.containerName': '节点容器名称', 'clusters.addworker.containerName.tips': '为节点容器指定一个名称。', 'clusters.addworker.dataVolume': 'GPUStack 数据卷', diff --git a/src/pages/cluster-management/components/k8s-pod-spec.tsx b/src/pages/cluster-management/components/k8s-pod-spec.tsx index a507c45e..7e504739 100644 --- a/src/pages/cluster-management/components/k8s-pod-spec.tsx +++ b/src/pages/cluster-management/components/k8s-pod-spec.tsx @@ -4,7 +4,7 @@ import { Input as CInput, LabelSelector } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; -import React, { useEffect, useId } from 'react'; +import React, { useEffect, useId, useMemo } from 'react'; import styled from 'styled-components'; import { ClusterListItem as ListItem } from '../config/types'; import ImageCredential from './image-credential'; @@ -289,10 +289,9 @@ export const GpuInstancesStaticAddressForm: React.FC = () => { ); }; -// Strip UI-only / undefined-valued noise so two k8s_options snapshots compare -// on real content. `sourceType` is derived from `volumeSource` purely for the -// volume-mount UI (see cluster-form init), and the JSON round-trip drops -// undefined-valued keys so a missing key and `key: undefined` compare equal. +// Strip UI-only noise so two k8s_options snapshots compare on real content. +// `sourceType` is derived from `volumeSource` purely for the volume-mount UI +// (see cluster-form init). const cleanK8sOptions = (opts: any) => { const cloned = _.cloneDeep(opts || {}); if (Array.isArray(cloned.volumeMounts)) { @@ -300,24 +299,32 @@ const cleanK8sOptions = (opts: any) => { ({ sourceType, ...rest }: any) => rest ); } - // The edit form always seeds k8s_options.volumeMounts to [] even when the - // saved cluster had no value, so an absent field would otherwise read as a - // change the moment the drawer opens. Drop empty top-level arrays on both - // sides: "absent" and "empty list" both mean nothing configured. A - // non-empty -> empty edit is still detected, since only the empty side drops. - Object.keys(cloned).forEach((key) => { - if (Array.isArray(cloned[key]) && cloned[key].length === 0) { - delete cloned[key]; - } - }); - return JSON.parse(JSON.stringify(cloned)); + return cloned; +}; + +// Custom comparator for isEqualWith: treats null, undefined, empty strings, +// empty arrays, and empty objects as equivalent. Form normalize converts empty +// inputs to null while the API may omit absent keys (read as undefined when +// accessed on the object). This lets "user clears field back to original" +// compare as unchanged without needing to recursively strip nullish values. +const nullishCustomizer = (val1: any, val2: any) => { + if ( + (val1 == null && val2 === '') || + (val1 === '' && val2 == null) || + (_.isEmpty(val1) && val2 == null) || + (val1 == null && _.isEmpty(val2)) + ) { + return true; + } + return undefined; }; // Headless watcher: in EDIT mode it reports (via onChange) whether the user has -// changed any k8s_options field from the cluster's saved values. It renders -// nothing — the notice itself is shown in the form footer, above Save/Cancel -// (see cluster-create.tsx), mirroring the model edit interaction. Must be -// mounted inside the cluster
so the watch reads the form store. +// changed any k8s_options field or the top-level system_default_container_registry +// from the cluster's saved values. It renders nothing — the notice itself is shown +// in the form footer, above Save/Cancel (see cluster-create.tsx), mirroring the +// model edit interaction. Must be mounted inside the cluster so the watch +// reads the form store. export const K8sOptionsChangeWatcher: React.FC<{ action: PageActionType; currentData?: ListItem; @@ -327,13 +334,31 @@ export const K8sOptionsChangeWatcher: React.FC<{ // gpuInstanceOptions which is toggled via setFieldValue without a mounted // Form.Item (mirrors ClusterTypeSelector). const k8sOptions = Form.useWatch(['k8s_options'], { preserve: true }); + const containerRegistry = Form.useWatch('system_default_container_registry', { + preserve: true + }); + + // currentData?.k8s_options is static for the form's lifetime — memoize the + // cleaned version to avoid redundant deep-clone on every render + // (Form.useWatch triggers re-render on each keystroke). + const currentK8sOptionsCleaned = useMemo( + () => cleanK8sOptions(currentData?.k8s_options), + [currentData?.k8s_options] + ); + + const k8sOptionsChanged = !_.isEqualWith( + currentK8sOptionsCleaned, + cleanK8sOptions(k8sOptions), + nullishCustomizer + ); + const registryChanged = !_.isEqualWith( + currentData?.system_default_container_registry, + containerRegistry, + nullishCustomizer + ); const changed = - action === PageAction.EDIT && - !_.isEqual( - cleanK8sOptions(currentData?.k8s_options), - cleanK8sOptions(k8sOptions) - ); + action === PageAction.EDIT && (k8sOptionsChanged || registryChanged); useEffect(() => { onChange(changed); diff --git a/src/pages/cluster-management/step-forms/advance-config.tsx b/src/pages/cluster-management/step-forms/advance-config.tsx index 9fb924e5..64fc2bc7 100644 --- a/src/pages/cluster-management/step-forms/advance-config.tsx +++ b/src/pages/cluster-management/step-forms/advance-config.tsx @@ -1,8 +1,7 @@ import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import useUserSettings from '@/hooks/use-user-settings'; -import { ExclamationCircleFilled } from '@ant-design/icons'; -import { AlertBlockInfo, Input as CInput, IconFont } from '@gpustack/core-ui'; +import { Input as CInput, IconFont } from '@gpustack/core-ui'; import { YamlEditor } from '@gpustack/core-ui/yaml-editor'; import { useIntl } from '@umijs/max'; import { Button, Form } from 'antd'; @@ -120,14 +119,6 @@ const ClusterAdvanceConfig: React.FC<{ {intl.formatMessage({ id: 'clusters.create.workerConfig' })} - {action === PageAction.EDIT && ( - } - message={intl.formatMessage({ id: 'clusters.edit.workerConfig.tip' })} - > - )}