fix: remove the pined worker config changed notice
This commit is contained in:
@@ -113,8 +113,6 @@ export default {
|
|||||||
'clusters.create.workerConfig': 'Worker Configuration',
|
'clusters.create.workerConfig': 'Worker Configuration',
|
||||||
'clusters.edit.k8sOptions.changed.tip':
|
'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.',
|
'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': 'Worker Container Name',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Specify a name for the worker container.',
|
'Specify a name for the worker container.',
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export default {
|
|||||||
'clusters.create.workerConfig': 'Worker Configuration',
|
'clusters.create.workerConfig': 'Worker Configuration',
|
||||||
'clusters.edit.k8sOptions.changed.tip':
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
|
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
|
||||||
'clusters.edit.workerConfig.tip':
|
|
||||||
'ワーカー設定の変更は、対象のワーカーを再起動した後に有効になります。',
|
|
||||||
'clusters.addworker.containerName': 'Worker Container Name',
|
'clusters.addworker.containerName': 'Worker Container Name',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Specify a name for the worker container.',
|
'Specify a name for the worker container.',
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export default {
|
|||||||
'clusters.create.workerConfig': 'Конфигурация воркера',
|
'clusters.create.workerConfig': 'Конфигурация воркера',
|
||||||
'clusters.edit.k8sOptions.changed.tip':
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
|
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
|
||||||
'clusters.edit.workerConfig.tip':
|
|
||||||
'Изменения конфигурации воркера вступают в силу только после перезапуска соответствующих воркеров.',
|
|
||||||
'clusters.addworker.containerName': 'Имя контейнера воркера',
|
'clusters.addworker.containerName': 'Имя контейнера воркера',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Укажите имя для контейнера воркера.',
|
'Укажите имя для контейнера воркера.',
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export default {
|
|||||||
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
|
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
|
||||||
'clusters.edit.k8sOptions.changed.tip':
|
'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.',
|
'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': 'İşçi Düğüm Konteyner Adı',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'İşçi düğüm konteyneri için bir ad belirtin.',
|
'İşçi düğüm konteyneri için bir ad belirtin.',
|
||||||
|
|||||||
@@ -111,8 +111,6 @@ export default {
|
|||||||
'clusters.create.workerConfig': '节点配置',
|
'clusters.create.workerConfig': '节点配置',
|
||||||
'clusters.edit.k8sOptions.changed.tip':
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
|
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
|
||||||
'clusters.edit.workerConfig.tip':
|
|
||||||
'修改节点配置后,需要重启对应节点才会生效。',
|
|
||||||
'clusters.addworker.containerName': '节点容器名称',
|
'clusters.addworker.containerName': '节点容器名称',
|
||||||
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
|
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
|
||||||
'clusters.addworker.dataVolume': 'GPUStack 数据卷',
|
'clusters.addworker.dataVolume': 'GPUStack 数据卷',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Input as CInput, LabelSelector } from '@gpustack/core-ui';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useId } from 'react';
|
import React, { useEffect, useId, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ClusterListItem as ListItem } from '../config/types';
|
import { ClusterListItem as ListItem } from '../config/types';
|
||||||
import ImageCredential from './image-credential';
|
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
|
// Strip UI-only noise so two k8s_options snapshots compare on real content.
|
||||||
// on real content. `sourceType` is derived from `volumeSource` purely for the
|
// `sourceType` is derived from `volumeSource` purely for the volume-mount UI
|
||||||
// volume-mount UI (see cluster-form init), and the JSON round-trip drops
|
// (see cluster-form init).
|
||||||
// undefined-valued keys so a missing key and `key: undefined` compare equal.
|
|
||||||
const cleanK8sOptions = (opts: any) => {
|
const cleanK8sOptions = (opts: any) => {
|
||||||
const cloned = _.cloneDeep(opts || {});
|
const cloned = _.cloneDeep(opts || {});
|
||||||
if (Array.isArray(cloned.volumeMounts)) {
|
if (Array.isArray(cloned.volumeMounts)) {
|
||||||
@@ -300,24 +299,32 @@ const cleanK8sOptions = (opts: any) => {
|
|||||||
({ sourceType, ...rest }: any) => rest
|
({ sourceType, ...rest }: any) => rest
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// The edit form always seeds k8s_options.volumeMounts to [] even when the
|
return cloned;
|
||||||
// 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
|
// Custom comparator for isEqualWith: treats null, undefined, empty strings,
|
||||||
// non-empty -> empty edit is still detected, since only the empty side drops.
|
// empty arrays, and empty objects as equivalent. Form normalize converts empty
|
||||||
Object.keys(cloned).forEach((key) => {
|
// inputs to null while the API may omit absent keys (read as undefined when
|
||||||
if (Array.isArray(cloned[key]) && cloned[key].length === 0) {
|
// accessed on the object). This lets "user clears field back to original"
|
||||||
delete cloned[key];
|
// compare as unchanged without needing to recursively strip nullish values.
|
||||||
}
|
const nullishCustomizer = (val1: any, val2: any) => {
|
||||||
});
|
if (
|
||||||
return JSON.parse(JSON.stringify(cloned));
|
(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
|
// 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
|
// changed any k8s_options field or the top-level system_default_container_registry
|
||||||
// nothing — the notice itself is shown in the form footer, above Save/Cancel
|
// from the cluster's saved values. It renders nothing — the notice itself is shown
|
||||||
// (see cluster-create.tsx), mirroring the model edit interaction. Must be
|
// in the form footer, above Save/Cancel (see cluster-create.tsx), mirroring the
|
||||||
// mounted inside the cluster <Form> so the watch reads the form store.
|
// model edit interaction. Must be mounted inside the cluster <Form> so the watch
|
||||||
|
// reads the form store.
|
||||||
export const K8sOptionsChangeWatcher: React.FC<{
|
export const K8sOptionsChangeWatcher: React.FC<{
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
currentData?: ListItem;
|
currentData?: ListItem;
|
||||||
@@ -327,13 +334,31 @@ export const K8sOptionsChangeWatcher: React.FC<{
|
|||||||
// gpuInstanceOptions which is toggled via setFieldValue without a mounted
|
// gpuInstanceOptions which is toggled via setFieldValue without a mounted
|
||||||
// Form.Item (mirrors ClusterTypeSelector).
|
// Form.Item (mirrors ClusterTypeSelector).
|
||||||
const k8sOptions = Form.useWatch(['k8s_options'], { preserve: true });
|
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 =
|
const changed =
|
||||||
action === PageAction.EDIT &&
|
action === PageAction.EDIT && (k8sOptionsChanged || registryChanged);
|
||||||
!_.isEqual(
|
|
||||||
cleanK8sOptions(currentData?.k8s_options),
|
|
||||||
cleanK8sOptions(k8sOptions)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(changed);
|
onChange(changed);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useUserSettings from '@/hooks/use-user-settings';
|
import useUserSettings from '@/hooks/use-user-settings';
|
||||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
import { Input as CInput, IconFont } from '@gpustack/core-ui';
|
||||||
import { AlertBlockInfo, Input as CInput, IconFont } from '@gpustack/core-ui';
|
|
||||||
import { YamlEditor } from '@gpustack/core-ui/yaml-editor';
|
import { YamlEditor } from '@gpustack/core-ui/yaml-editor';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Form } from 'antd';
|
import { Button, Form } from 'antd';
|
||||||
@@ -120,14 +119,6 @@ const ClusterAdvanceConfig: React.FC<{
|
|||||||
<Title>
|
<Title>
|
||||||
{intl.formatMessage({ id: 'clusters.create.workerConfig' })}
|
{intl.formatMessage({ id: 'clusters.create.workerConfig' })}
|
||||||
</Title>
|
</Title>
|
||||||
{action === PageAction.EDIT && (
|
|
||||||
<AlertBlockInfo
|
|
||||||
type="warning"
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
icon={<ExclamationCircleFilled />}
|
|
||||||
message={intl.formatMessage({ id: 'clusters.edit.workerConfig.tip' })}
|
|
||||||
></AlertBlockInfo>
|
|
||||||
)}
|
|
||||||
<YamlEditor
|
<YamlEditor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
isDarkTheme={isDarkTheme}
|
isDarkTheme={isDarkTheme}
|
||||||
|
|||||||
Reference in New Issue
Block a user