diff --git a/ui/src/components/EventsTable/index.tsx b/ui/src/components/EventsTable/index.tsx index fc51e065f..bf2323a52 100644 --- a/ui/src/components/EventsTable/index.tsx +++ b/ui/src/components/EventsTable/index.tsx @@ -137,7 +137,7 @@ export default function EventsTable({ defaultExpand = false, name }: EventsTableProps) { - const defaultParams = {}; + const defaultParams:API.EventParams = {}; if(objectType){ defaultParams.objectType = objectType; } diff --git a/ui/src/components/MonitorComp/index.tsx b/ui/src/components/MonitorComp/index.tsx index 0b385073d..d864576ef 100644 --- a/ui/src/components/MonitorComp/index.tsx +++ b/ui/src/components/MonitorComp/index.tsx @@ -23,7 +23,7 @@ interface MonitorCompProps { filterLabel: API.MetricsLabels; queryRange: QueryRangeType; isRefresh?: boolean; - queryScope:API.EventObjectType; + queryScope:API.MetricScope; type: API.MonitorUseTarget; groupLabels:API.LableKeys[]; useFor?: API.MonitorUseFor; diff --git a/ui/src/components/TopoComponent/G6register.tsx b/ui/src/components/TopoComponent/G6register.tsx index 18fddf1f8..a16a0a30d 100644 --- a/ui/src/components/TopoComponent/G6register.tsx +++ b/ui/src/components/TopoComponent/G6register.tsx @@ -119,6 +119,7 @@ const reactStyles = { function ReactNode(handleClick?: any) { return ({ cfg }: any) => { + // Zones not included in the tenant will be disabled const { label, status, typeText, disable } = cfg; return ( @@ -191,7 +192,7 @@ function ReactNode(handleClick?: any) { {cfg.type !== 'server' && ( { + return haveResourcePool + ? [ + { + value: 'editResourcePools', + label: intl.formatMessage({ + id: 'Dashboard.components.TopoComponent.constants.EditResourcePool', + defaultMessage: '编辑资源池', + }), + }, + { + value: 'deleteResourcePool', + label: intl.formatMessage({ + id: 'Dashboard.components.TopoComponent.constants.DeleteAResourcePool', + defaultMessage: '删除资源池', + }), + }, + ] + : [ + { + value: 'createResourcePools', + label: intl.formatMessage({ + id: 'Dashboard.components.TopoComponent.constants.AddAResourcePool', + defaultMessage: '新增资源池', + }), + }, + ]; +}; export { clusterOperate, clusterOperateOfTenant, + getZoneOperateOfTenant, serverOperate, zoneOperate, - zoneOperateOfTenant, }; export type { OperateTypeLabel }; diff --git a/ui/src/components/TopoComponent/index.tsx b/ui/src/components/TopoComponent/index.tsx index 9a1a4e6f2..277b8bc47 100644 --- a/ui/src/components/TopoComponent/index.tsx +++ b/ui/src/components/TopoComponent/index.tsx @@ -11,16 +11,18 @@ import showDeleteConfirm from '@/components/customModal/DeleteModal'; import OperateModal from '@/components/customModal/OperateModal'; import { RESULT_STATUS } from '@/constants'; import BasicInfo from '@/pages/Cluster/Detail/Overview/BasicInfo'; +import { getClusterFromTenant, getZonesOptions } from '@/pages/Tenant/helper'; import { deleteObcluster, deleteObzone, getClusterDetailReq } from '@/services'; +import { deleteObtenantPool } from '@/services/tenant'; import { getNSName } from '../../pages/Cluster/Detail/Overview/helper'; import { ReactNode, config } from './G6register'; import type { OperateTypeLabel } from './constants'; import { clusterOperate, clusterOperateOfTenant, + getZoneOperateOfTenant, serverOperate, zoneOperate, - zoneOperateOfTenant, } from './constants'; import { appenAutoShapeListener, checkIsSame, getServerNumber } from './helper'; @@ -29,6 +31,9 @@ interface TopoProps { namespace?: string; clusterNameOfKubectl?: string; // k8s resource name header?: ReactElement; + resourcePoolDefaultValue?: any; + refreshTenant?: () => void; + defaultUnitCount?: number; } //Cluster topology diagram component @@ -37,11 +42,13 @@ export default function TopoComponent({ header, namespace, clusterNameOfKubectl, + resourcePoolDefaultValue, + refreshTenant, + defaultUnitCount, }: TopoProps) { const clusterOperateList = tenantReplicas ? clusterOperateOfTenant : clusterOperate; - const zoneOperateList = tenantReplicas ? zoneOperateOfTenant : zoneOperate; const modelRef = useRef(null); const [visible, setVisible] = useState(false); const [operateList, setOprateList] = @@ -79,6 +86,7 @@ export default function TopoComponent({ }, }, ); + //Node more icon click event const handleClick = (evt: IG6GraphEvent) => { if (modelRef.current) { @@ -87,8 +95,18 @@ export default function TopoComponent({ setOprateList(clusterOperateList); break; case 'zone': - setOprateList(zoneOperateList); - chooseZoneName.current = evt.item?._cfg?.model?.label as string; + const zone = evt.item?._cfg?.model?.label as string; + if (tenantReplicas) { + const { setEditZone } = resourcePoolDefaultValue; + if (setEditZone) setEditZone(zone); + const haveResourcePool = !!tenantReplicas?.find( + (replica) => replica.zone === zone, + ); + setOprateList(getZoneOperateOfTenant(haveResourcePool)); + } else { + setOprateList(zoneOperate); + } + chooseZoneName.current = zone; break; case 'server': setOprateList(serverOperate); @@ -171,6 +189,21 @@ export default function TopoComponent({ graph.current.render(); appenAutoShapeListener(graph.current); }; + // delete resource pool + const deleteResourcePool = async (zoneName: string) => { + const [ns, name] = getNSName(); + const res = await deleteObtenantPool({ ns, name, zoneName }); + if (res.successful) { + if (refreshTenant) refreshTenant(); + message.success( + res.message || + intl.formatMessage({ + id: 'Dashboard.Detail.Overview.Replicas.DeletedSuccessfully', + defaultMessage: '删除成功', + }), + ); + } + }; /** * Call up the operation and maintenance operation modal @@ -215,10 +248,30 @@ export default function TopoComponent({ setOperateModalVisible(true); } - if (operate === 'modifyUnitSpecification') { - modalType.current = 'modifyUnitSpecification'; + if (operate === 'editResourcePools') { + modalType.current = 'editResourcePools'; setOperateModalVisible(true); } + + if (operate === 'createResourcePools') { + modalType.current = 'createResourcePools'; + setOperateModalVisible(true); + } + + if (operate === 'deleteResourcePool') { + modalType.current = 'deleteResourcePool'; + showDeleteConfirm({ + onOk: () => deleteResourcePool(chooseZoneName.current), + title: intl.formatMessage( + { + id: 'Dashboard.components.TopoComponent.AreYouSureYouWant', + defaultMessage: + '确定要删除该租户在{{chooseZoneNameCurrent}}上的资源池吗?', + }, + { chooseZoneNameCurrent: chooseZoneName.current }, + ), + }); + } }; const mouseEnter = () => setInModal(true); @@ -286,6 +339,7 @@ export default function TopoComponent({ modelRef.current?.removeEventListener('mouseleave', mouseLeave); }; }, []); + const isCreatePool = modalType.current === 'createResourcePools'; // Use different pictures for nodes in different states return ( @@ -320,8 +374,22 @@ export default function TopoComponent({ visible={operateModalVisible} setVisible={setOperateModalVisible} successCallback={operateSuccess} - zoneName={chooseZoneName.current} - defaultValue={chooseServerNum} + params={{ + zoneName: chooseZoneName.current, + defaultValue: chooseServerNum, + defaultUnitCount: defaultUnitCount, + ...resourcePoolDefaultValue, + newResourcePool: isCreatePool, + zonesOptions: isCreatePool + ? getZonesOptions( + getClusterFromTenant( + resourcePoolDefaultValue?.clusterList, + resourcePoolDefaultValue?.clusterResourceName, + ), + resourcePoolDefaultValue?.replicaList, + ) + : undefined, + }} /> ); diff --git a/ui/src/components/customModal/ModifyUnitDetailModal.tsx b/ui/src/components/customModal/ModifyUnitDetailModal.tsx index f29d38ac0..1fb1ef68d 100644 --- a/ui/src/components/customModal/ModifyUnitDetailModal.tsx +++ b/ui/src/components/customModal/ModifyUnitDetailModal.tsx @@ -1,9 +1,12 @@ import InputNumber from '@/components/InputNumber'; -import { SUFFIX_UNIT } from '@/constants'; +import { SUFFIX_UNIT, getMinResource } from '@/constants'; import { RULER_ZONE } from '@/constants/rules'; import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; import { TooltipItemContent } from '@/pages/Cluster/New/Observer'; -import type { MaxResourceType } from '@/pages/Tenant/New/ResourcePools'; +import type { + MaxResourceType, + MinResourceConfig, +} from '@/pages/Tenant/New/ResourcePools'; import ZoneItem from '@/pages/Tenant/ZoneItem'; import { findMinParameter, @@ -46,34 +49,42 @@ const formatReplicaList = ( }; type UnitConfigType = { - clusterList?: API.SimpleClusterList; - clusterResourceName?: string; - essentialParameter?: API.EssentialParametersType; - setClusterList: React.Dispatch>; - editZone?: string; - replicaList?: API.ReplicaDetailType[]; - newResourcePool?: boolean; - setEditZone?: React.Dispatch>; - zonesOptions?: API.OptionsType; + params: { + clusterList?: API.SimpleClusterList; + clusterResourceName?: string; + essentialParameter?: API.EssentialParametersType; + setClusterList: React.Dispatch>; + editZone?: string; + replicaList?: API.ReplicaDetailType[]; + newResourcePool?: boolean; + setEditZone?: React.Dispatch>; + zonesOptions?: API.OptionsType; + zoneName?: string; + }; }; export default function ModifyUnitDetailModal({ visible, setVisible, successCallback, - clusterList = [], - setClusterList, - essentialParameter = {}, - clusterResourceName = '', - editZone, - replicaList, - newResourcePool = false, - setEditZone, - zonesOptions, + params: { + clusterList = [], + setClusterList, + essentialParameter = {}, + clusterResourceName = '', + editZone, + replicaList, + newResourcePool = false, + setEditZone, + zonesOptions, + zoneName, + }, }: CommonModalType & UnitConfigType) { const [form] = Form.useForm(); - const [minMemory, setMinMemory] = useState(2); const [maxResource, setMaxResource] = useState({}); + const [minResource, setMinResource] = useState( + getMinResource({ minMemory: essentialParameter.minPoolMemory }), + ); const [selectZones, setSelectZones] = useState( editZone ? [editZone] : [], ); @@ -157,6 +168,7 @@ export default function ModifyUnitDetailModal({ } return result; }; + let targetCluster = clusterList.find( (cluster) => cluster.name === clusterResourceName, ); @@ -171,9 +183,13 @@ export default function ModifyUnitDetailModal({ } const selectOptions = formatReplicaList(replicaList || []); + useEffect(() => { if (essentialParameter) { - setMinMemory(essentialParameter.minPoolMemory / (1 << 30)); + setMinResource({ + ...minResource, + minMemory: essentialParameter.minPoolMemory, + }); } }, [essentialParameter]); @@ -183,7 +199,17 @@ export default function ModifyUnitDetailModal({ setMaxResource({}); return; } - setMaxResource(findMinParameter(selectZones, essentialParameter)); + const maxResource = findMinParameter(selectZones, essentialParameter); + if (maxResource.maxCPU < minResource.minCPU) { + maxResource.maxCPU = minResource.minCPU; + } + if (maxResource.maxLogDisk < minResource.minLogDisk) { + maxResource.maxLogDisk = minResource.minLogDisk; + } + if (maxResource.maxMemory < minResource.minMemory) { + maxResource.maxMemory = minResource.minMemory; + } + setMaxResource(maxResource); } }, [selectZones, essentialParameter]); @@ -205,6 +231,12 @@ export default function ModifyUnitDetailModal({ } }, [editZone]); + useEffect(() => { + if (zoneName && newResourcePool && zonesOptions?.length) { + form.setFieldValue('zoneName', zoneName); + } + }, [zoneName, newResourcePool]); + return ( ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter).maxCPU < + minResource.minCPU + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit', + defaultMessage: + '可用 CPU 过小, Zone 无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), ]} > ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter) + .maxMemory < minResource.minMemory + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.components.customModal.ModifyUnitDetailModal.IfTheAvailableMemorySize', + defaultMessage: + '可用 Memory size 过小,Zone 将无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), ]} > ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter) + .maxLogDisk < minResource.minLogDisk + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit.1', + defaultMessage: + '可用日志磁盘空间过小, Zone 无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), ]} label="LogDiskSize" name={['unitConfig', 'logDiskSize']} > { try { await form.validateFields(); @@ -48,10 +50,10 @@ export default function ModifyUnitModal({ }; useEffect(() => { - if (defaultValue !== form.getFieldValue('unitNum')) { + if (defaultUnitCount !== form.getFieldValue('unitNum')) { form.resetFields(); } - }, [defaultValue]); + }, [defaultUnitCount]); return (
; } - if (type === 'scaleServer' && props.zoneName) { + if (type === 'scaleServer' && props?.params?.zoneName) { return ; } @@ -53,8 +48,12 @@ export default function OperateModal({ return ; } - if(type === 'modifyUnitSpecification'){ - return + if(type === 'editResourcePools'){ + return + } + + if(type === 'createResourcePools'){ + return } if(type === 'changeUnitCount'){ diff --git a/ui/src/components/customModal/ScaleModal.tsx b/ui/src/components/customModal/ScaleModal.tsx index a91768d30..22b29de46 100644 --- a/ui/src/components/customModal/ScaleModal.tsx +++ b/ui/src/components/customModal/ScaleModal.tsx @@ -8,17 +8,20 @@ import { useEffect } from 'react'; import type { CommonModalType } from '.'; interface ScaleModalProps { - zoneName?: string; - defaultValue?: number; + params?:{ + zoneName?: string; + defaultValue?: number; + } } export default function ScaleModal({ visible, setVisible, successCallback, - zoneName, - defaultValue = 1, + params, }: ScaleModalProps & CommonModalType) { + const zoneName = params?.zoneName; + const defaultValue = params?.defaultValue; const [form] = Form.useForm(); const handleSubmit = async () => { try { diff --git a/ui/src/components/customModal/index.tsx b/ui/src/components/customModal/index.tsx index 6611c19ab..9f711251d 100644 --- a/ui/src/components/customModal/index.tsx +++ b/ui/src/components/customModal/index.tsx @@ -14,7 +14,7 @@ interface CustomModalProps { export type CommonModalType = { visible: boolean; setVisible: (prop: boolean) => void; - successCallback: (val:any) => void; + successCallback: (val?:any) => void; }; export default function CustomModal(props: CustomModalProps) { diff --git a/ui/src/constants/index.ts b/ui/src/constants/index.ts index f1fe6b96e..63502c651 100644 --- a/ui/src/constants/index.ts +++ b/ui/src/constants/index.ts @@ -10,6 +10,7 @@ import serverOperating from '@/assets/server/warning.svg'; import zoneDeleting from '@/assets/zone/deleting.svg'; import zoneOperating from '@/assets/zone/operating.svg'; import zoneRunning from '@/assets/zone/running.svg'; +import { intl } from '@/utils/intl'; //Unify status constants and colors const STATUS = ['running', 'deleting', 'operating']; @@ -58,12 +59,13 @@ const MINIMAL_CONFIG = { redoLog: 30, }; -const RESULT_STATUS = ['running','failed']; +const RESULT_STATUS = ['running', 'failed']; -const BACKUP_RESULT_STATUS = ['RUNNING','FAILED','PAUSED']; +const BACKUP_RESULT_STATUS = ['RUNNING', 'FAILED', 'PAUSED']; const CLUSTER_INFO_CONFIG = [ 'name', + 'clusterName', 'namespace', 'status', 'image', @@ -73,39 +75,98 @@ const CLUSTER_INFO_CONFIG = [ 'monitor', 'rootPasswordSecret', 'mode', - 'parameters' -] + 'parameters', +]; const TOPO_INFO_CONFIG = [ 'name', + 'clusterName', 'namespace', 'status', 'image', 'mode', 'rootPasswordSecret', -] +]; const RESOURCE_NAME_REG = /^[a-z\-]+$/; // use for tenant name or zone name -const TZ_NAME_REG = /^[_a-zA-Z][^-\n]*$/; +const TZ_NAME_REG = /^[_a-zA-Z][^-\n]*$/; + +const MIN_RESOURCE_CONFIG = { + minCPU: 1, + minLogDisk: 5, + minMemory: 2, + minIops: 1024, + maxIops: 1024, +}; + +const getMinResource = (defaultValue?: any) => { + return { + ...MIN_RESOURCE_CONFIG, + ...defaultValue, + }; +}; + +const MODE_MAP = new Map([ + [ + 'NORMAL', + { + text: intl.formatMessage({ + id: 'Dashboard.src.constants.RegularMode', + defaultMessage: '常规模式', + }), + }, + ], + + [ + 'STANDALONE', + { + text: intl.formatMessage({ + id: 'Dashboard.src.constants.MonomerMode', + defaultMessage: '单体模式', + }), + limit: intl.formatMessage({ + id: 'Dashboard.src.constants.RequiredKernelVersion', + defaultMessage: '要求内核版本 >= 4.2.0.0', + }), + }, + ], + + [ + 'SERVICE', + { + text: intl.formatMessage({ + id: 'Dashboard.src.constants.ServiceMode', + defaultMessage: 'Service模式', + }), + limit: intl.formatMessage({ + id: 'Dashboard.src.constants.RequiredKernelVersion.1', + defaultMessage: '要求内核版本 >= 4.2.3.0', + }), + }, + ], +]); export { + BACKUP_RESULT_STATUS, BADGE_IMG_MAP, CLUSTER_IMG_MAP, + CLUSTER_INFO_CONFIG, COLOR_MAP, MINIMAL_CONFIG, + MIN_RESOURCE_CONFIG, + MODE_MAP, POINT_NUMBER, REFRESH_CLUSTER_TIME, REFRESH_FREQUENCY, REFRESH_TENANT_TIME, + RESOURCE_NAME_REG, + RESULT_STATUS, SERVER_IMG_MAP, STATUS, SUFFIX_UNIT, - ZONE_IMG_MAP, - RESULT_STATUS, - BACKUP_RESULT_STATUS, - RESOURCE_NAME_REG, + TOPO_INFO_CONFIG, TZ_NAME_REG, - CLUSTER_INFO_CONFIG, - TOPO_INFO_CONFIG + ZONE_IMG_MAP, + getMinResource, }; diff --git a/ui/src/i18n/strings/en-US.json b/ui/src/i18n/strings/en-US.json index f281f1244..52108d79a 100644 --- a/ui/src/i18n/strings/en-US.json +++ b/ui/src/i18n/strings/en-US.json @@ -657,5 +657,27 @@ "Dashboard.Detail.Overview.BasicInfo.ReplicaDistribution": "Replica distribution", "Dashboard.Tenant.New.ResourcePools.SelectTheZoneToDeploy": "Select the Zone to deploy the resource pool", "Dashboard.Tenant.New.ResourcePools.ResourceUnitSpecifications": "Resource Unit specifications", - "Dashboard.Tenant.New.SelectAtLeastOneZone": "Select at least one Zone" + "Dashboard.Tenant.New.SelectAtLeastOneZone": "Select at least one Zone", + "Dashboard.pages.Cluster.ClusterList.ResourceName": "Resource Name", + "Dashboard.Detail.Overview.ZoneTable.ZoneResourceName": "Zone Resource Name", + "Dashboard.Detail.Overview.ZoneTable.ZoneName": "Zone name", + "Dashboard.Cluster.New.Observer.EnterLabel": "Enter {label}", + "Dashboard.Cluster.New.Observer.EnterAnImage": "Enter an image", + "Dashboard.Tenant.New.ResourcePools.EnterTheLogDiskSize": "Enter the log disk size.", + "Dashboard.pages.Tenant.TenantsList.ReplicaDistribution": "Replica distribution", + "Dashboard.components.TopoComponent.constants.EditResourcePool": "Edit resource pool", + "Dashboard.components.TopoComponent.constants.DeleteAResourcePool": "Delete a resource pool", + "Dashboard.components.TopoComponent.constants.AddAResourcePool": "Add a resource pool", + "Dashboard.components.TopoComponent.AreYouSureYouWant": "Are you sure you want to delete the tenant's resource pool on {chooseZoneNameCurrent}?", + "Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit": "Zone cannot create a Unit because the available CPU is too small.", + "Dashboard.Tenant.New.ResourcePools.IfTheAvailableMemorySize": "If the available Memory size is too small, the Zone cannot create a Unit.", + "Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit.1": "Zone cannot create a Unit because the available log disk space is too small.", + "Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit": "Zone cannot create a Unit because the available CPU is too small.", + "Dashboard.components.customModal.ModifyUnitDetailModal.IfTheAvailableMemorySize": "If the available Memory size is too small, the Zone cannot create a Unit.", + "Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit.1": "Zone cannot create a Unit because the available log disk space is too small.", + "Dashboard.src.constants.RegularMode": "Regular mode", + "Dashboard.src.constants.MonomerMode": "Monomer mode", + "Dashboard.src.constants.RequiredKernelVersion": "Required kernel version> = 4.2.0.0", + "Dashboard.src.constants.ServiceMode": "Service mode", + "Dashboard.src.constants.RequiredKernelVersion.1": "Required kernel version> = 4.2.3.0" } diff --git a/ui/src/i18n/strings/zh-CN.json b/ui/src/i18n/strings/zh-CN.json index e5692fc00..fb2aa5dae 100644 --- a/ui/src/i18n/strings/zh-CN.json +++ b/ui/src/i18n/strings/zh-CN.json @@ -205,7 +205,7 @@ "OBDashboard.Cluster.New.Monitor.EnterMemory": "请输入内存", "OBDashboard.Cluster.New.Monitor.Memory": "内存", "OBDashboard.Cluster.New.Observer.TheImageShouldBeFully": "镜像应写全 registry/image:tag,例如 oceanbase/oceanbase-cloud-native:4.2.0.0-101000032023091319", - "OBDashboard.Cluster.New.Observer.EnterLabel": "请输入{{label}}", + "OBDashboard.Cluster.New.Observer.EnterLabel": "请输入{label}", "OBDashboard.Cluster.New.Observer.Image": "镜像", "OBDashboard.Cluster.New.Observer.Resources": "资源", "OBDashboard.Cluster.New.Observer.Memory": "内存", @@ -303,7 +303,7 @@ "OBDashboard.Detail.Monitor.DataFilter.NearlyHours.2": "近12小时", "OBDashboard.Detail.Monitor.DataFilter.NearlyHours.3": "近24小时", "OBDashboard.Detail.Monitor.DataFilter.LastDays": "近7天", - "OBDashboard.Detail.Monitor.DataFilter.UpdateTimeRealtime": "更新时间:{{realTime}}", + "OBDashboard.Detail.Monitor.DataFilter.UpdateTimeRealtime": "更新时间:{realTime}", "OBDashboard.Detail.Monitor.DataFilter.UpdateFrequency": "更新频率:", "OBDashboard.Detail.Monitor.DataFilter.Seconds": "秒", "OBDashboard.Detail.Monitor.DataFilter.AutoRefresh": "自动刷新:", @@ -657,5 +657,27 @@ "Dashboard.Detail.Overview.BasicInfo.ReplicaDistribution": "副本分布", "Dashboard.Tenant.New.ResourcePools.SelectTheZoneToDeploy": "选择要部署资源池的 Zone", "Dashboard.Tenant.New.ResourcePools.ResourceUnitSpecifications": "资源单元规格", - "Dashboard.Tenant.New.SelectAtLeastOneZone": "至少选择一个Zone" + "Dashboard.Tenant.New.SelectAtLeastOneZone": "至少选择一个Zone", + "Dashboard.pages.Cluster.ClusterList.ResourceName": "资源名", + "Dashboard.Detail.Overview.ZoneTable.ZoneResourceName": "Zone 资源名", + "Dashboard.Detail.Overview.ZoneTable.ZoneName": "Zone 名", + "Dashboard.Cluster.New.Observer.EnterLabel": "请输入{label}", + "Dashboard.Cluster.New.Observer.EnterAnImage": "请输入镜像", + "Dashboard.Tenant.New.ResourcePools.EnterTheLogDiskSize": "请输入日志磁盘大小", + "Dashboard.pages.Tenant.TenantsList.ReplicaDistribution": "副本分布", + "Dashboard.components.TopoComponent.constants.EditResourcePool": "编辑资源池", + "Dashboard.components.TopoComponent.constants.DeleteAResourcePool": "删除资源池", + "Dashboard.components.TopoComponent.constants.AddAResourcePool": "新增资源池", + "Dashboard.components.TopoComponent.AreYouSureYouWant": "确定要删除该租户在{chooseZoneNameCurrent}上的资源池吗?", + "Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit": "可用 CPU 过小, Zone 无法创建 Unit", + "Dashboard.Tenant.New.ResourcePools.IfTheAvailableMemorySize": "可用 Memory size 过小,Zone 将无法创建 Unit", + "Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit.1": "可用日志磁盘空间过小, Zone 无法创建 Unit", + "Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit": "可用 CPU 过小, Zone 无法创建 Unit", + "Dashboard.components.customModal.ModifyUnitDetailModal.IfTheAvailableMemorySize": "可用 Memory size 过小,Zone 将无法创建 Unit", + "Dashboard.components.customModal.ModifyUnitDetailModal.ZoneCannotCreateAUnit.1": "可用日志磁盘空间过小, Zone 无法创建 Unit", + "Dashboard.src.constants.RegularMode": "常规模式", + "Dashboard.src.constants.MonomerMode": "单体模式", + "Dashboard.src.constants.RequiredKernelVersion": "要求内核版本 >= 4.2.0.0", + "Dashboard.src.constants.ServiceMode": "Service模式", + "Dashboard.src.constants.RequiredKernelVersion.1": "要求内核版本 >= 4.2.3.0" } diff --git a/ui/src/pages/Cluster/ClusterList.tsx b/ui/src/pages/Cluster/ClusterList.tsx index 7aaae6226..c3f43b194 100644 --- a/ui/src/pages/Cluster/ClusterList.tsx +++ b/ui/src/pages/Cluster/ClusterList.tsx @@ -1,7 +1,7 @@ import { intl } from '@/utils/intl'; import { Pie } from '@antv/g2plot'; import { Link } from '@umijs/max'; -import { Button, Col, Table, Tag, Card } from 'antd'; +import { Button, Card, Col, Table, Tag } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { COLOR_MAP } from '@/constants'; @@ -95,12 +95,24 @@ const columns: ColumnsType = [ id: 'OBDashboard.pages.Cluster.ClusterList.ClusterName', defaultMessage: '集群名', }), - dataIndex: 'name', - key: 'name', + dataIndex: 'clusterName', + key: 'clusterName', render: (value, record) => ( - {value} + + {value} + ), }, + { + title: intl.formatMessage({ + id: 'Dashboard.pages.Cluster.ClusterList.ResourceName', + defaultMessage: '资源名', + }), + dataIndex: 'name', + key: 'name', + }, { title: intl.formatMessage({ id: 'OBDashboard.pages.Cluster.ClusterList.Namespace', @@ -185,7 +197,7 @@ export default function ClusterList({ columns={columns} dataSource={clusterList} scroll={{ x: 1200 }} - pagination={{simple:true}} + pagination={{ simple: true }} rowKey="name" bordered sticky diff --git a/ui/src/pages/Cluster/Detail/Overview/BasicInfo.tsx b/ui/src/pages/Cluster/Detail/Overview/BasicInfo.tsx index f6782d35b..fa2c8a32f 100644 --- a/ui/src/pages/Cluster/Detail/Overview/BasicInfo.tsx +++ b/ui/src/pages/Cluster/Detail/Overview/BasicInfo.tsx @@ -1,4 +1,4 @@ -import { COLOR_MAP } from '@/constants'; +import { COLOR_MAP,MODE_MAP } from '@/constants'; import { intl } from '@/utils/intl'; import { Card,Col,Descriptions,Switch,Tag } from 'antd'; import { useState } from 'react'; @@ -17,9 +17,10 @@ export default function BasicInfo({ backupVolume, monitor, parameters, + clusterName, extra = true, style, -}: API.ClusterInfo & { style?: React.CSSProperties,extra?: boolean }) { +}: API.ClusterInfo & { style?: React.CSSProperties; extra?: boolean }) { const [checked, setChecked] = useState(false); const OBServerConfig = extra ? [ @@ -75,12 +76,12 @@ export default function BasicInfo({ }, ] : []; - + return ( + {clusterName} + + {name} @@ -108,7 +117,7 @@ export default function BasicInfo({ defaultMessage: '集群模式', })} > - {mode || '-'} + {MODE_MAP.get(mode)?.text || '-'} )} + {checked && extra && (
; typeRef: React.MutableRefObject; setChooseServerNum: React.Dispatch>; - clusterStatus:'running' | 'failed' | 'operating' + clusterStatus: 'running' | 'failed' | 'operating'; } export default function ZoneTable({ @@ -22,14 +22,14 @@ export default function ZoneTable({ chooseZoneRef, typeRef, setChooseServerNum, - clusterStatus + clusterStatus, }: ZoneTableProps) { const getZoneColumns = (remove, clickScale) => { const columns: ColumnType = [ { title: intl.formatMessage({ - id: 'OBDashboard.Detail.Overview.ZoneTable.ZoneName', - defaultMessage: 'Zone名', + id: 'Dashboard.Detail.Overview.ZoneTable.ZoneResourceName', + defaultMessage: 'Zone 资源名', }), dataIndex: 'name', key: 'name', @@ -44,7 +44,10 @@ export default function ZoneTable({ key: 'namespace', }, { - title: 'zone', + title: intl.formatMessage({ + id: 'Dashboard.Detail.Overview.ZoneTable.ZoneName', + defaultMessage: 'Zone 名', + }), dataIndex: 'zone', key: 'zone', }, @@ -129,7 +132,7 @@ export default function ZoneTable({ setVisible(true); }; //删除的ns和name是集群的 - const handleDelete = async (zoneName:string) => { + const handleDelete = async (zoneName: string) => { const [ns, name] = getNSName(); const res = await deleteObzone({ ns, @@ -150,7 +153,7 @@ export default function ZoneTable({ diff --git a/ui/src/pages/Cluster/Detail/Overview/index.tsx b/ui/src/pages/Cluster/Detail/Overview/index.tsx index 87e1f9744..3abd6452b 100644 --- a/ui/src/pages/Cluster/Detail/Overview/index.tsx +++ b/ui/src/pages/Cluster/Detail/Overview/index.tsx @@ -65,7 +65,7 @@ const ClusterOverview: React.FC = () => { setOperateModalVisible(true); }; const handleUpgrade = () => { - modalType.current = 'upgrade'; + modalType.current = 'upgradeCluster'; setOperateModalVisible(true); }; @@ -170,8 +170,10 @@ const ClusterOverview: React.FC = () => { visible={operateModalVisible} setVisible={setOperateModalVisible} successCallback={operateSuccess} - zoneName={chooseZoneName.current} - defaultValue={chooseServerNum} + params={{ + zoneName:chooseZoneName.current, + defaultValue:chooseServerNum + }} /> ); diff --git a/ui/src/pages/Cluster/New/BasicInfo.tsx b/ui/src/pages/Cluster/New/BasicInfo.tsx index 58800ef58..1a4474d51 100644 --- a/ui/src/pages/Cluster/New/BasicInfo.tsx +++ b/ui/src/pages/Cluster/New/BasicInfo.tsx @@ -1,14 +1,15 @@ import { intl } from '@/utils/intl'; import { PlusOutlined } from '@ant-design/icons'; import { useRequest } from 'ahooks'; -import { Card, Col, Divider, Form, Input, Row, Select, Tooltip } from 'antd'; +import { Card,Col,Divider,Form,Input,Row,Select,Tooltip } from 'antd'; import type { FormInstance } from 'antd/lib/form'; import PasswordInput from '@/components/PasswordInput'; import AddNSModal from '@/components/customModal/AddNSModal'; +import { MODE_MAP } from '@/constants'; +import { resourceNameRule } from '@/constants/rules'; import { getNameSpaces } from '@/services'; import { useState } from 'react'; -import { resourceNameRule } from '@/constants/rules'; import styles from './index.less'; interface BasicInfoProps { @@ -219,7 +220,21 @@ export default function BasicInfo({ id: 'Dashboard.Cluster.New.BasicInfo.PleaseSelect', defaultMessage: '请选择', })} - options={CLUSTER_MODE} + optionLabelProp="value" + options={Array.from(MODE_MAP.keys()).map((key) => ({ + value: MODE_MAP.get(key)?.text, + label: ( +
+ {MODE_MAP.get(key)?.text} + {MODE_MAP.get(key)?.limit} +
+ ), + }))} /> diff --git a/ui/src/pages/Cluster/New/Observer.tsx b/ui/src/pages/Cluster/New/Observer.tsx index b7bcc4f15..597cce0c5 100644 --- a/ui/src/pages/Cluster/New/Observer.tsx +++ b/ui/src/pages/Cluster/New/Observer.tsx @@ -1,17 +1,17 @@ import { intl } from '@/utils/intl'; import { -Button, -Card, -Col, -Form, -Input, -InputNumber, -Row, -Tooltip, + Button, + Card, + Col, + Form, + Input, + InputNumber, + Row, + Tooltip, } from 'antd'; import SelectWithTooltip from '@/components/SelectWithTooltip'; -import { MINIMAL_CONFIG,SUFFIX_UNIT } from '@/constants'; +import { MINIMAL_CONFIG, SUFFIX_UNIT } from '@/constants'; import { MIRROR_SERVER } from '@/constants/doc'; import { clone } from 'lodash'; import styles from './index.less'; @@ -62,23 +62,24 @@ export const TooltipItemContent = ({ item }) => { ); }; - export default function Observer({ storageClasses, form }: any) { const CustomItem = (prop: any) => { - const { label } = prop; + const { label, message } = prop; return ( @@ -113,7 +114,6 @@ export default function Observer({ storageClasses, form }: any) { }); }; - return ( {intl.formatMessage({ diff --git a/ui/src/pages/Cluster/New/index.tsx b/ui/src/pages/Cluster/New/index.tsx index 4d56d273e..1bfc4372f 100644 --- a/ui/src/pages/Cluster/New/index.tsx +++ b/ui/src/pages/Cluster/New/index.tsx @@ -1,12 +1,12 @@ -import { getStorageClasses } from '@/services'; +import { createObclusterReq,getStorageClasses } from '@/services'; import { intl } from '@/utils/intl'; import { PageContainer } from '@ant-design/pro-components'; import { useNavigate } from '@umijs/max'; import { useRequest } from 'ahooks'; -import { Button, Form, Row, message } from 'antd'; +import { Button,Form,Row,message } from 'antd'; import { useState } from 'react'; -import { createObclusterReq } from '@/services'; +import { MODE_MAP } from '@/constants'; import { encryptText,usePublicKey } from '@/hook/usePublicKey'; import BackUp from './BackUp'; import BasicInfo from './BasicInfo'; @@ -52,7 +52,7 @@ export default function New() { } }; const initialValues = { - mode:'NORMAL', + mode: MODE_MAP.get('NORMAL')?.text, topology: [ { zone: 'zone1', @@ -86,7 +86,7 @@ export default function New() { defaultMessage: '取消', })} , - diff --git a/ui/src/pages/Tenant/New/ResourcePools.tsx b/ui/src/pages/Tenant/New/ResourcePools.tsx index b697924f3..885048c54 100644 --- a/ui/src/pages/Tenant/New/ResourcePools.tsx +++ b/ui/src/pages/Tenant/New/ResourcePools.tsx @@ -1,5 +1,5 @@ import InputNumber from '@/components/InputNumber'; -import { SUFFIX_UNIT } from '@/constants'; +import { SUFFIX_UNIT, getMinResource } from '@/constants'; import { intl } from '@/utils/intl'; import { Card, Col, Form, Row, Tooltip } from 'antd'; import { FormInstance } from 'antd/lib/form'; @@ -21,6 +21,14 @@ export type MaxResourceType = { maxMemory?: number; }; +export type MinResourceConfig = { + minCPU: number; + minMemory: number; + minLogDisk: number; + minIops: number; + maxIops: number; +}; + export default function ResourcePools({ selectClusterId, clusterList, @@ -28,7 +36,9 @@ export default function ResourcePools({ setClusterList, form, }: ResourcePoolsProps) { - const [minMemory, setMinMemory] = useState(2); + const [minResource, setMinResource] = useState( + getMinResource(), + ); const [maxResource, setMaxResource] = useState({}); const [selectZones, setSelectZones] = useState([]); @@ -51,21 +61,35 @@ export default function ResourcePools({ .filter((cluster) => cluster.clusterId === selectClusterId)[0] ?.topology.map((zone) => ({ zone: zone.zone, checked: zone.checked })); - useEffect(() => { - if (essentialParameter) { - setMinMemory(essentialParameter.minPoolMemory / (1 << 30)); - } - }, [essentialParameter]); - useEffect(() => { if (essentialParameter) { if (selectZones.length === 0) { setMaxResource({}); return; } - setMaxResource(findMinParameter(selectZones, essentialParameter)); + const maxResource = findMinParameter(selectZones, essentialParameter); + if (maxResource.maxCPU < minResource.minCPU) { + maxResource.maxCPU = minResource.minCPU; + } + if (maxResource.maxLogDisk < minResource.minLogDisk) { + maxResource.maxLogDisk = minResource.minLogDisk; + } + if (maxResource.maxMemory < minResource.minMemory) { + maxResource.maxMemory = minResource.minMemory; + } + setMaxResource(maxResource); } - }, [selectZones]); + }, [selectZones, essentialParameter]); + + useEffect(() => { + if (essentialParameter) { + setMinResource( + getMinResource({ + minMemory: essentialParameter?.minPoolMemory, + }), + ); + } + }, [essentialParameter]); return ( ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter) + .maxCPU < minResource.minCPU + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit', + defaultMessage: + '可用 CPU 过小, Zone 无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), ]} label="CPU" > @@ -127,7 +171,7 @@ export default function ResourcePools({ })} } - min={1} + min={minResource.minCPU} max={maxResource.maxCPU} placeholder={intl.formatMessage({ id: 'Dashboard.Tenant.New.ResourcePools.PleaseEnter', @@ -145,19 +189,37 @@ export default function ResourcePools({ required: true, message: intl.formatMessage({ id: 'Dashboard.Tenant.New.ResourcePools.EnterMemorySize', - defaultMessage: '请输入Memory size', + defaultMessage: '请输入 Memory size', }), }, + () => ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter) + .maxMemory < minResource.minMemory + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.IfTheAvailableMemorySize', + defaultMessage: + '可用 Memory size 过小,Zone 将无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), ]} label="Memory" > ({ + validator() { + if ( + essentialParameter && + findMinParameter(selectZones, essentialParameter) + .maxLogDisk < minResource.minLogDisk + ) { + return Promise.reject( + new Error( + intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.ZoneCannotCreateAUnit.1', + defaultMessage: + '可用日志磁盘空间过小, Zone 无法创建 Unit', + }), + ), + ); + } + return Promise.resolve(); + }, + }), + ]} label={ @@ -212,7 +301,7 @@ export default function ResourcePools({ diff --git a/ui/src/pages/Tenant/New/TenantSource.tsx b/ui/src/pages/Tenant/New/TenantSource.tsx index 39e097b9a..71a086c68 100644 --- a/ui/src/pages/Tenant/New/TenantSource.tsx +++ b/ui/src/pages/Tenant/New/TenantSource.tsx @@ -1,6 +1,10 @@ import { getAllTenants } from '@/services/tenant'; import { intl } from '@/utils/intl'; import { useRequest } from 'ahooks'; +import type { RangePickerProps } from 'antd/es/date-picker'; +import type { Dayjs } from 'dayjs'; +import moment from 'moment'; +import dayjs from 'dayjs'; import { Card, Col, @@ -36,7 +40,6 @@ export default function TenantSource({ form, clusterName }: TenantSourceProps) { marginBottom: 24, }; const h3Style = { marginBottom: 0, marginRight: 12 }; - //主租户和备租户 const tenantRole = [ { label: 'PRIMARY', @@ -73,6 +76,43 @@ export default function TenantSource({ form, clusterName }: TenantSourceProps) { value: tenant.name, })); + const range = (start: number, end: number) => { + const result = []; + for (let i = start; i < end; i++) { + result.push(i); + } + return result; + }; + + const disabledDateTime: RangePickerProps['disabledTime'] = (_) => { + const isToday = _?.date() === moment().date(); + if (!isToday) + return { + disabledHours: () => [], + disabledMinutes: () => [], + disabledSeconds: () => [], + }; + return { + disabledHours: () => range(0, 24).splice(moment().hour() + 1, 24), + disabledMinutes: (hour) => { + if (hour === moment().hour()) { + return range(0, 60).splice(moment().minute() + 1, 60); + } + return []; + }, + disabledSeconds: (hour, minute) => { + if (hour === moment().hour() && minute === moment().minute()) { + return range(0, 60).splice(moment().second(), 60); + } + return []; + }, + }; + }; + + const disabledDate: RangePickerProps['disabledDate'] = (current) => { + return current && current > dayjs().endOf('day'); + }; + useEffect(() => { if (synchronizeChecked && clusterName) { getTenants(clusterName); @@ -394,7 +434,9 @@ export default function TenantSource({ form, clusterName }: TenantSourceProps) { ]} name={['source', 'restore', 'until', 'date']} > - + @@ -416,7 +458,7 @@ export default function TenantSource({ form, clusterName }: TenantSourceProps) { })} name={['source', 'restore', 'until', 'time']} > - + diff --git a/ui/src/pages/Tenant/New/index.less b/ui/src/pages/Tenant/New/index.less index 3da2f223e..5c7f2cf0a 100644 --- a/ui/src/pages/Tenant/New/index.less +++ b/ui/src/pages/Tenant/New/index.less @@ -5,3 +5,8 @@ } } } +:global { + .ant-picker-time-panel-column::after { + height: 96px !important; + } +} diff --git a/ui/src/pages/Tenant/New/index.tsx b/ui/src/pages/Tenant/New/index.tsx index 8b49c29fe..c73dea7a2 100644 --- a/ui/src/pages/Tenant/New/index.tsx +++ b/ui/src/pages/Tenant/New/index.tsx @@ -56,12 +56,13 @@ export default function New() { ); return; } + const ns = clusterList.filter( (cluster) => cluster.clusterId === selectClusterId, )[0]?.namespace; const res = await createTenant({ namespace: ns, - ...formatNewTenantForm(values, clusterName, publicKey), + ...reqData, }); if (res.successful) { message.success( diff --git a/ui/src/pages/Tenant/TenantsList.tsx b/ui/src/pages/Tenant/TenantsList.tsx index c8301b9c5..f56b8c25d 100644 --- a/ui/src/pages/Tenant/TenantsList.tsx +++ b/ui/src/pages/Tenant/TenantsList.tsx @@ -1,7 +1,7 @@ import { COLOR_MAP } from '@/constants'; import { intl } from '@/utils/intl'; import { Link } from '@umijs/max'; -import { Button,Card,Col,Table,Tag } from 'antd'; +import { Button, Card, Col, Table, Tag } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import styles from './index.less'; @@ -61,7 +61,10 @@ const columns: ColumnsType = [ key: 'tenantRole', }, { - title: 'locality', + title: intl.formatMessage({ + id: 'Dashboard.pages.Tenant.TenantsList.ReplicaDistribution', + defaultMessage: '副本分布', + }), dataIndex: 'locality', key: 'locality', }, diff --git a/ui/src/pages/Tenant/ZoneItem/index.tsx b/ui/src/pages/Tenant/ZoneItem/index.tsx index 5ae1b8dda..a77ef88a6 100644 --- a/ui/src/pages/Tenant/ZoneItem/index.tsx +++ b/ui/src/pages/Tenant/ZoneItem/index.tsx @@ -38,7 +38,7 @@ export default function ZoneItem({ checkBoxOnChange(e.target.checked, name)} /> @@ -46,7 +46,7 @@ export default function ZoneItem({ ) : ( checkBoxOnChange(e.target.checked, name)} /> @@ -74,14 +74,14 @@ export default function ZoneItem({ CPU {obZoneResource['availableCPU']} - Memory {obZoneResource['availableMemory'] / (1 << 30)}GB + Memory {obZoneResource['availableMemory'] }GB {intl.formatMessage({ id: 'Dashboard.Tenant.New.ResourcePools.LogDiskSize', defaultMessage: '日志磁盘大小', })} - {obZoneResource['availableLogDisk'] / (1 << 30)}GB + {obZoneResource['availableLogDisk']}GB )} diff --git a/ui/src/pages/Tenant/helper.ts b/ui/src/pages/Tenant/helper.ts index 5c61e0f25..17f8cf5e0 100644 --- a/ui/src/pages/Tenant/helper.ts +++ b/ui/src/pages/Tenant/helper.ts @@ -1,6 +1,6 @@ import { encryptText } from '@/hook/usePublicKey'; import dayjs from 'dayjs'; -import { clone, cloneDeep } from 'lodash'; +import { clone,cloneDeep } from 'lodash'; import type { MaxResourceType } from './New/ResourcePools'; const isExist = (val: string | number | undefined): boolean => { @@ -56,18 +56,11 @@ export function formatNewTenantForm( } if (originFormData[key]['restore']) { let { until } = originFormData[key]['restore']; + result[key]['restore'] = { ...originFormData[key]['restore'], - ossAccessId: encryptText( - originFormData[key]['restore'].ossAccessId, - publicKey, - ), - ossAccessKey: encryptText( - originFormData[key]['restore'].ossAccessKey, - publicKey, - ), until: - until && until.date && until.time + (until && until.date && until.time) ? { timestamp: dayjs(until.date).format('YYYY-MM-DD') + @@ -76,6 +69,16 @@ export function formatNewTenantForm( } : { unlimited: true }, }; + if(originFormData[key]?.restore?.type === 'OSS'){ + result[key]['restore']['ossAccessId'] = encryptText( + originFormData[key]['restore'].ossAccessId, + publicKey, + ); + result[key]['restore']['ossAccessKey'] = encryptText( + originFormData[key]['restore'].ossAccessKey, + publicKey, + ); + } if (originFormData[key]['restore'].bakEncryptionPassword) { result[key]['restore']['bakEncryptionPassword'] = encryptText( originFormData[key]['restore'].bakEncryptionPassword, @@ -93,7 +96,6 @@ export function formatNewTenantForm( result[key] = originFormData[key]; } }); - delete result.source?.restore?.until; return result; } /** @@ -177,6 +179,7 @@ function findMinValue( key: 'availableCPU' | 'availableLogDisk' | 'availableMemory', resources: API.ServerResource[], ) { + if (!resources.length) return []; return resources.sort((pre, cur) => cur[key] - pre[key])[0][key]; } @@ -184,10 +187,12 @@ export function findMinParameter( zones: string[], essentialParameter: API.EssentialParametersType, ): MaxResourceType { + const { obServerResources } = essentialParameter; const selectResources = obServerResources.filter((resource) => zones.includes(resource.obZone), ); + return { maxCPU: findMinValue('availableCPU', selectResources), maxLogDisk: findMinValue('availableLogDisk', selectResources), @@ -248,14 +253,20 @@ const formatReplicToOption = ( })); }; +const filterAbnormalZone = (zones: API.Topology[]): API.Topology[] => { + return zones.filter( + (zone) => + !!zone.observers.find((server) => server.statusDetail === 'running'), + ); +}; + export const getZonesOptions = ( cluster: API.SimpleCluster | undefined, replicaList: API.ReplicaDetailType[] | undefined, ): API.OptionsType => { if (!replicaList) return []; if (!cluster) return formatReplicToOption(replicaList); - const { topology } = cluster; - const newReplicas = topology.filter((zone) => { + const newReplicas = filterAbnormalZone(cluster.topology).filter((zone) => { if (replicaList.find((replica) => replica.zone === zone.zone)) { return false; } else { diff --git a/ui/src/services/index.ts b/ui/src/services/index.ts index b9253285a..c610df5c8 100644 --- a/ui/src/services/index.ts +++ b/ui/src/services/index.ts @@ -29,13 +29,9 @@ export async function infoReq() { } /** - * 不传参数表示返回所有 + * If no parameters are passed, all events will be returned. */ -export async function getEventsReq(params: { - type?: API.EventType; - objectType?: API.EventObjectType; - name?: string; -}) { +export async function getEventsReq(params: API.EventParams) { const r = await request(`${clusterPrefix}/events`, { method: 'GET', params, @@ -371,7 +367,7 @@ export async function getStorageClasses(): Promise { return r; } -export async function getAllMetrics(type: API.EventObjectType) { +export async function getAllMetrics(type: API.MetricScope) { const r = await request('/api/v1/metrics', { method: 'GET', params: { scope: type }, @@ -456,5 +452,28 @@ export async function getEssentialParameters({ ns, name, }: API.NamespaceAndName): Promise { - return request(`${obClusterPrefix}/${ns}/${name}/resource-usages`); + // return request(`${obClusterPrefix}/${ns}/${name}/resource-usages`); + const r = await request(`${obClusterPrefix}/${ns}/${name}/resource-usages`); + const formatResourceAttr = ['availableDataDisk','availableLogDisk','availableMemory'] + if(r.successful){ + r.data.minPoolMemory = r.data.minPoolMemory / (1 << 30); + r.data.obServerResources.forEach((item)=>{ + for(let attr of formatResourceAttr){ + item[attr] = item[attr] / (1<<30) + // if(attr === 'availableMemory' && item.obZone === 'zone1'){ + // item[attr] = 3 + // } + } + }) + Object.keys(r.data.obZoneResourceMap).forEach((key)=>{ + for(let attr of formatResourceAttr){ + r.data.obZoneResourceMap[key][attr] = r.data.obZoneResourceMap[key][attr] / (1 << 30); + // if(attr === 'availableMemory' && key === 'zone1'){ + // r.data.obZoneResourceMap[key][attr] = 3 + // } + } + }) + return r + } + return r; } \ No newline at end of file diff --git a/ui/src/services/typings.d.ts b/ui/src/services/typings.d.ts index 0f6bf180c..33023dde5 100644 --- a/ui/src/services/typings.d.ts +++ b/ui/src/services/typings.d.ts @@ -13,6 +13,8 @@ declare namespace API { memoryPercent: number; }; + type MetricScope = 'OBCLUSTER' | 'OBTENANT' | 'OBCLUSTER_OVERVIEW'; + type NodeSelector = { key: string; value: string; @@ -26,6 +28,7 @@ declare namespace API { type ClusterInfo = { name: string; namespace: string; + clusterName: string; status: string; image: string; rootPasswordSecret: string; @@ -118,6 +121,13 @@ declare namespace API { value:string; }[] + type EventParams = { + type?: API.EventType; + objectType?: API.EventObjectType; + name?: string; + namespace?: string; + } + interface ClusterListResponse extends CommonResponse { data: ClusterItem[]; } @@ -153,7 +163,9 @@ declare namespace API { | 'switchTenant' | 'upgradeTenant' | 'changeUnitCount' - | 'modifyUnitSpecification' + | 'editResourcePools' + | 'createResourcePools' + | 'deleteResourcePool' | 'deleteCluster' | 'deleteZone' @@ -184,7 +196,7 @@ declare namespace API { type MonitorUseTarget = 'OVERVIEW' | 'DETAIL'; - type EventObjectType = 'OBCLUSTER' | 'OBTENANT' | 'OBCLUSTER_OVERVIEW'; + type EventObjectType = 'OBCLUSTER' | 'OBTENANT' | 'OBBACKUPPOLICY' | EventObjectType[]; type TenantRole = 'PRIMARY' | 'STANDBY'; diff --git a/ui/src/utils/helper.ts b/ui/src/utils/helper.ts index 1532fee30..062cacbe7 100644 --- a/ui/src/utils/helper.ts +++ b/ui/src/utils/helper.ts @@ -56,8 +56,10 @@ export const formatPatchPoolData = (originUnitData: PoolDetailType,type:'edit'|' } if(type === 'edit'){ Object.keys(originUnitData).forEach((key) => { - if (originUnitData[key]?.priority) { + if(key !== 'unitConfig'){ newOriginUnitData.zoneName = key; + } + if (originUnitData[key]?.priority) { newOriginUnitData.priority = originUnitData[key].priority; } });