diff --git a/ui/package.json b/ui/package.json index 8066f0353..e954db854 100644 --- a/ui/package.json +++ b/ui/package.json @@ -19,7 +19,7 @@ "ahooks": "^3.7.8", "antd": "^5.4.0", "copy-to-clipboard": "^3.3.3", - "i18next": "^23.10.0", + "i18next": "^23.10.1", "jsencrypt": "^3.3.2", "lodash": "^4.17.21", "moment": "^2.29.4" diff --git a/ui/src/components/TopoComponent/index.tsx b/ui/src/components/TopoComponent/index.tsx index 7c50e1104..78239a7dc 100644 --- a/ui/src/components/TopoComponent/index.tsx +++ b/ui/src/components/TopoComponent/index.tsx @@ -14,6 +14,7 @@ import { deleteObcluster, deleteObzone, getClusterDetailReq } from '@/services'; import { getNSName } from '../../pages/Cluster/Detail/Overview/helper'; import { ReactNode, config } from './G6register'; import type { OperateTypeLabel } from './constants'; +import { RESULT_STATUS } from '@/constants'; import { clusterOperate, clusterOperateOfTenant, @@ -223,7 +224,7 @@ export default function TopoComponent({ useUpdateEffect(() => { let checkStatusTimer: NodeJS.Timer; //polling - if (originTopoData.topoData.status !== 'running') { + if (!RESULT_STATUS.includes(originTopoData.topoData.status)) { if (!operateDisable) setOperateDisable(true); checkStatusTimer = setInterval(() => { getTopoData({ ns, name, useFor: 'topo', tenantReplicas }); diff --git a/ui/src/components/customModal/AddZoneModal.tsx b/ui/src/components/customModal/AddZoneModal.tsx index c52fc6321..017d670de 100644 --- a/ui/src/components/customModal/AddZoneModal.tsx +++ b/ui/src/components/customModal/AddZoneModal.tsx @@ -1,6 +1,7 @@ import { intl } from '@/utils/intl'; import { Form, Input, InputNumber, message } from 'antd'; +import { TZ_NAME_REG } from '@/constants'; import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; import { addObzone } from '@/services'; import type { CommonModalType } from '.'; @@ -71,6 +72,13 @@ export default function AddZoneModal({ defaultMessage: '请输入zone名称!', }), }, + { + pattern: TZ_NAME_REG, + message: intl.formatMessage({ + id: 'Dashboard.components.customModal.AddZoneModal.TheFirstCharacterMustBe', + defaultMessage: '首字符必须是字母或者下划线,不能包含 -', + }), + }, ]} > {/* 如果不能为空的话应该需要添加默认值 */} {/*

node-selector:

- - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }) => ( - - - - - : - - - - remove(name)} /> - - ))} - - - - - )} - */} + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + + : + + + + remove(name)} /> + + ))} + + + + + )} + */} { try { @@ -30,7 +32,7 @@ export default function ModifyPasswordModal({ ns: namespace, name, User: 'root', - Password: values.password, + Password: encryptText(values.password,publicKey) as string, }); if (res.successful) { message.success(res.message); diff --git a/ui/src/components/customModal/ModifyUnitDetailModal.tsx b/ui/src/components/customModal/ModifyUnitDetailModal.tsx index f8503dd3a..1c702a3ba 100644 --- a/ui/src/components/customModal/ModifyUnitDetailModal.tsx +++ b/ui/src/components/customModal/ModifyUnitDetailModal.tsx @@ -1,17 +1,52 @@ import { SUFFIX_UNIT } from '@/constants'; import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; +import type { MaxResourceType } from '@/pages/Tenant/New/ResourcePools'; +import ZoneItem from '@/pages/Tenant/ZoneItem'; +import { findMinParameter,getNewClusterList } from '@/pages/Tenant/helper'; import { patchTenantConfiguration } from '@/services/tenant'; +import { formatUnitDetailData } from '@/utils/helper'; import { intl } from '@/utils/intl'; +import { useEffect,useState } from 'react'; +import InputNumber from '@/components/InputNumber'; -import { Col, Form, InputNumber, Row, message } from 'antd'; +import { Col, Form, Row, message } from 'antd'; import type { CommonModalType } from '.'; import CustomModal from '.'; + +export type UnitDetailType = { + unitConfig: { + unitConfig: { + cpuCount: number|string; + iopsWeight: number; + logDiskSize: number | string; + maxIops: number; + memorySize: number | string; + minIops: number; + }; + pools:any; + }; +}; + +type UnitConfigType = { + clusterList?: API.SimpleClusterList; + clusterName?: string; + essentialParameter?: API.EssentialParametersType; + setClusterList: React.Dispatch>; +}; + export default function ModifyUnitDetailModal({ visible, setVisible, successCallback, -}: CommonModalType) { - const [form] = Form.useForm(); + clusterList = [], + setClusterList, + essentialParameter = {}, + clusterName = '', +}: CommonModalType & UnitConfigType) { + const [form] = Form.useForm(); + const [minMemory, setMinMemory] = useState(2); + const [maxResource, setMaxResource] = useState({}); + const [selectZones, setSelectZones] = useState([]); const handleSubmit = async () => { try { await form.validateFields(); @@ -20,8 +55,12 @@ export default function ModifyUnitDetailModal({ }; const handleCancel = () => setVisible(false); const onFinish = async (values: any) => { - const [namespace, name] = getNSName(); - const res = await patchTenantConfiguration({ namespace, name, ...values }); + const [ns, name] = getNSName(); + const res = await patchTenantConfiguration({ + ns, + name, + ...formatUnitDetailData(values), + }); if (res.successful) { message.success(res.message); successCallback(); @@ -29,8 +68,41 @@ export default function ModifyUnitDetailModal({ setVisible(false); } }; + const checkBoxOnChange = (checked: boolean, name: string) => { + if (!checked) { + form.setFieldValue(['pools', name, 'priority'], undefined); + setSelectZones(selectZones.filter((zone) => zone !== name)); + } else { + setSelectZones([...selectZones, name]); + } + setClusterList( + getNewClusterList(clusterList, name, checked, { name: clusterName }), + ); + }; + + const targetZoneList = clusterList + .filter((cluster) => cluster.clusterName === clusterName)[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)); + } + }, [selectZones]); + return ( - - - - - - - - - - - - + {targetZoneList && essentialParameter && ( + +

+ {intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.ZonePriority', + defaultMessage: 'Zone优先级', + })} +

+ {targetZoneList.map((item, index) => ( + + ))} +
+ )} + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - -
); diff --git a/ui/src/components/customModal/OperateModal.tsx b/ui/src/components/customModal/OperateModal.tsx index fe7b1f4b1..9538d0c98 100644 --- a/ui/src/components/customModal/OperateModal.tsx +++ b/ui/src/components/customModal/OperateModal.tsx @@ -13,12 +13,14 @@ import ModifyUnitDetailModal from './ModifyUnitDetailModal'; interface OperateModalProps { type: API.ModalType; zoneName?: any; - defaultValue?: number; + defaultValue?: any; disabled?: boolean; + defaultValueForUnitDetail?: any; } export default function OperateModal({ type, + defaultValueForUnitDetail, ...props }: OperateModalProps & CommonModalType) { if (type === 'addZone') { @@ -52,7 +54,7 @@ export default function OperateModal({ } if(type === 'modifyUnitSpecification'){ - return + return } if(type === 'changeUnitCount'){ diff --git a/ui/src/constants/index.ts b/ui/src/constants/index.ts index 79d483642..3677e365c 100644 --- a/ui/src/constants/index.ts +++ b/ui/src/constants/index.ts @@ -58,6 +58,12 @@ const MINIMAL_CONFIG = { redoLog: 30, }; +const RESULT_STATUS = ['running','failed']; + +const RESOURCE_NAME_REG = /^[a-z\-]+$/; +// use for tenant name or zone name +const TZ_NAME_REG = /^[_a-zA-Z][^-\n]*$/; + export { BADGE_IMG_MAP, CLUSTER_IMG_MAP, @@ -71,4 +77,7 @@ export { STATUS, SUFFIX_UNIT, ZONE_IMG_MAP, + RESULT_STATUS, + RESOURCE_NAME_REG, + TZ_NAME_REG }; diff --git a/ui/src/i18n/strings/en-US.json b/ui/src/i18n/strings/en-US.json index c91e7de45..311b5050a 100644 --- a/ui/src/i18n/strings/en-US.json +++ b/ui/src/i18n/strings/en-US.json @@ -579,5 +579,14 @@ "Dashboard.Detail.Overview.AdjustTheNumberOfUnits": "Adjust the number of units", "Dashboard.Tenant.New.ResourcePools.AvailableResources": "Available resources:", "Dashboard.Tenant.New.ResourcePools.LogDiskSize": "Log disk size", - "Dashboard.Tenant.New.ResourcePools.ThisRefersToTheTenant": "This refers to the tenant's clog disk space." + "Dashboard.Tenant.New.ResourcePools.ThisRefersToTheTenant": "This refers to the tenant's clog disk space.", + "Dashboard.Cluster.New.BasicInfo.RegularMode": "Regular mode", + "Dashboard.Cluster.New.BasicInfo.MonomerMode": "Monomer mode", + "Dashboard.Cluster.New.BasicInfo.ServiceMode": "Service mode", + "Dashboard.Cluster.New.BasicInfo.ClusterMode": "Cluster mode", + "Dashboard.Cluster.New.BasicInfo.SelectClusterMode": "Select cluster mode", + "Dashboard.Cluster.New.BasicInfo.PleaseSelect": "Please select", + "Dashboard.Tenant.New.BasicInfo.ResourceNamesCanOnlyConsist": "Resource names can only consist of lowercase letters and-", + "Dashboard.Tenant.New.BasicInfo.TheFirstCharacterMustBe": "The first character must be a letter or an underscore and cannot contain-", + "Dashboard.components.customModal.AddZoneModal.TheFirstCharacterMustBe": "The first character must be a letter or an underscore and cannot contain-" } diff --git a/ui/src/i18n/strings/zh-CN.json b/ui/src/i18n/strings/zh-CN.json index 25f8ebcab..a5e1c6cd6 100644 --- a/ui/src/i18n/strings/zh-CN.json +++ b/ui/src/i18n/strings/zh-CN.json @@ -579,5 +579,14 @@ "Dashboard.Detail.Overview.AdjustTheNumberOfUnits": "调整 Unit 数量", "Dashboard.Tenant.New.ResourcePools.AvailableResources": "可用资源:", "Dashboard.Tenant.New.ResourcePools.LogDiskSize": "日志磁盘大小", - "Dashboard.Tenant.New.ResourcePools.ThisRefersToTheTenant": "此处指的是租户的 clog 磁盘空间" + "Dashboard.Tenant.New.ResourcePools.ThisRefersToTheTenant": "此处指的是租户的 clog 磁盘空间", + "Dashboard.Cluster.New.BasicInfo.RegularMode": "常规模式", + "Dashboard.Cluster.New.BasicInfo.MonomerMode": "单体模式", + "Dashboard.Cluster.New.BasicInfo.ServiceMode": "Service模式", + "Dashboard.Cluster.New.BasicInfo.ClusterMode": "集群模式", + "Dashboard.Cluster.New.BasicInfo.SelectClusterMode": "请选择集群模式", + "Dashboard.Cluster.New.BasicInfo.PleaseSelect": "请选择", + "Dashboard.Tenant.New.BasicInfo.ResourceNamesCanOnlyConsist": "资源名只能由小写字母和 - 组成", + "Dashboard.Tenant.New.BasicInfo.TheFirstCharacterMustBe": "首字符必须是字母或者下划线,不能包含 -", + "Dashboard.components.customModal.AddZoneModal.TheFirstCharacterMustBe": "首字符必须是字母或者下划线,不能包含 -" } diff --git a/ui/src/pages/Cluster/New/BasicInfo.tsx b/ui/src/pages/Cluster/New/BasicInfo.tsx index 8c21910a5..2e20f6ad3 100644 --- a/ui/src/pages/Cluster/New/BasicInfo.tsx +++ b/ui/src/pages/Cluster/New/BasicInfo.tsx @@ -1,30 +1,14 @@ import { intl } from '@/utils/intl'; import { PlusOutlined } from '@ant-design/icons'; -import { useRequest, useUpdateEffect } from 'ahooks'; -import { - Button, - Card, - Col, - Divider, - Form, - Input, - Row, - Select, - Tooltip, - message, -} from 'antd'; +import { useRequest } from 'ahooks'; +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 { getNameSpaces } from '@/services'; -import copy from 'copy-to-clipboard'; import { useState } from 'react'; -import PasswordInput from '@/components/PasswordInput'; -import { - generateRandomPassword, - passwordRules, - resourceNameRule, -} from './helper'; +import { resourceNameRule } from './helper'; import styles from './index.less'; interface BasicInfoProps { @@ -41,13 +25,35 @@ export default function BasicInfo({ // control the modal for adding a new namespace const [visible, setVisible] = useState(false); const { data, run: getNS } = useRequest(getNameSpaces); + const CLUSTER_MODE = [ + { + value: 'NORMAL', + label: intl.formatMessage({ + id: 'Dashboard.Cluster.New.BasicInfo.RegularMode', + defaultMessage: '常规模式', + }), + }, + { + value: 'STANDLONE', + label: intl.formatMessage({ + id: 'Dashboard.Cluster.New.BasicInfo.MonomerMode', + defaultMessage: '单体模式', + }), + }, + { + value: 'SERVICE', + label: intl.formatMessage({ + id: 'Dashboard.Cluster.New.BasicInfo.ServiceMode', + defaultMessage: 'Service模式', + }), + }, + ]; const filterOption = ( input: string, option: { label: string; value: string }, ) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()); - const DropDownComponent = (menu: any) => { return (
@@ -69,11 +75,11 @@ export default function BasicInfo({ ); }; - const addNSCallback = (newNS:string)=>{ - form.setFieldValue('namespace',newNS) - form.validateFields(['namespace']) - getNS() - } + const addNSCallback = (newNS: string) => { + form.setFieldValue('namespace', newNS); + form.validateFields(['namespace']); + getNS(); + }; return ( @@ -119,12 +125,12 @@ export default function BasicInfo({ - + + + + {/* - - + + */} ); diff --git a/ui/src/pages/Tenant/New/ResourcePools.tsx b/ui/src/pages/Tenant/New/ResourcePools.tsx index 85e977bee..7afe1267e 100644 --- a/ui/src/pages/Tenant/New/ResourcePools.tsx +++ b/ui/src/pages/Tenant/New/ResourcePools.tsx @@ -1,19 +1,20 @@ import InputNumber from '@/components/InputNumber'; import { SUFFIX_UNIT } from '@/constants'; import { intl } from '@/utils/intl'; -import { Card, Checkbox, Col, Form, Row, Tooltip } from 'antd'; +import { Card,Col,Form,Row,Tooltip } from 'antd'; import { FormInstance } from 'antd/lib/form'; -import { useEffect, useMemo, useState } from 'react'; -import { findMinParameter } from '../helper'; +import { useEffect,useState } from 'react'; +import ZoneItem from '../ZoneItem'; +import { findMinParameter,getNewClusterList } from '../helper'; import styles from './index.less'; interface ResourcePoolsProps { selectClusterId?: number; clusterList: API.SimpleClusterList; form: FormInstance; + setClusterList: React.Dispatch>; essentialParameter?: API.EssentialParametersType; } - export type MaxResourceType = { maxCPU?: number; maxLogDisk?: number; @@ -24,80 +25,27 @@ export default function ResourcePools({ selectClusterId, clusterList, essentialParameter, + setClusterList, form, }: ResourcePoolsProps) { const [minMemory, setMinMemory] = useState(2); const [maxResource, setMaxResource] = useState({}); const [selectZones, setSelectZones] = useState([]); - const ZoneItem = ({ name }: { name: string }) => { - const [isChecked, setIsChecked] = useState(false); - const obZoneResource = essentialParameter?.obZoneResourceMap[name]; - useEffect(() => { - if (!isChecked) { - form.setFieldValue(['pools', name, 'priority'], undefined); - setSelectZones(selectZones.filter((zone) => zone !== name)); - } else { - setSelectZones([...selectZones, name]); - } - }, [isChecked]); - return ( -
- {name} - { - setIsChecked(e.target.checked); - }} - /> - - - - - - {obZoneResource && ( - - - {intl.formatMessage({ - id: 'Dashboard.Tenant.New.ResourcePools.AvailableResources', - defaultMessage: '可用资源:', - })} - - - CPU {obZoneResource['availableCPU']} - - - Memory {obZoneResource['availableMemory'] / (1 << 30)}GB - - - {intl.formatMessage({ - id: 'Dashboard.Tenant.New.ResourcePools.LogDiskSize', - defaultMessage: '日志磁盘大小', - })} - {obZoneResource['availableLogDisk'] / (1 << 30)}GB - - - )} -
+ const checkBoxOnChange = (checked: boolean, name: string) => { + if (!checked) { + form.setFieldValue(['pools', name, 'priority'], undefined); + setSelectZones(selectZones.filter((zone) => zone !== name)); + } else { + setSelectZones([...selectZones, name]); + } + setClusterList( + getNewClusterList(clusterList, name, checked, { id: selectClusterId }), ); }; const targetZoneList = clusterList .filter((cluster) => cluster.clusterId === selectClusterId)[0] - ?.topology.map((zone) => ({ zone: zone.zone })); + ?.topology.map((zone) => ({ zone: zone.zone, checked: zone.checked })); useEffect(() => { if (essentialParameter) { @@ -123,27 +71,24 @@ export default function ResourcePools({ })} >
- {useMemo( - () => ( -
- {' '} - {targetZoneList && essentialParameter && ( - -

- {intl.formatMessage({ - id: 'Dashboard.Tenant.New.ResourcePools.ZonePriority', - defaultMessage: 'Zone优先级', - })} -

- {targetZoneList.map((item, index) => ( - - ))} -
- )} -
- ), - - [selectClusterId, clusterList, essentialParameter], + {targetZoneList && essentialParameter && ( + +

+ {intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.ZonePriority', + defaultMessage: 'Zone优先级', + })} +

+ {targetZoneList.map((item, index) => ( + + ))} +
)}

Unit config

diff --git a/ui/src/pages/Tenant/New/index.tsx b/ui/src/pages/Tenant/New/index.tsx index df611e06c..e0e595b9a 100644 --- a/ui/src/pages/Tenant/New/index.tsx +++ b/ui/src/pages/Tenant/New/index.tsx @@ -18,8 +18,19 @@ export default function New() { const [form] = Form.useForm(); const [passwordVal, setPasswordVal] = useState(''); const [selectClusterId, setSelectClusterId] = useState(); - - const { data: clusterList = [] } = useRequest(getSimpleClusterList); + const [clusterList, setClusterList] = useState([]); + useRequest(getSimpleClusterList, { + onSuccess: ({ successful, data }) => { + if (successful) { + data.forEach((cluster)=>{ + cluster.topology.forEach((zone)=>{ + zone.checked = false; + }) + }) + setClusterList(data); + } + }, + }); const { data: essentialParameterRes,run: getEssentialParameters} = useRequest( getEssentialParametersReq, { @@ -118,6 +129,7 @@ export default function New() { selectClusterId={selectClusterId} essentialParameter={essentialParameter} clusterList={clusterList} + setClusterList={setClusterList} /> diff --git a/ui/src/pages/Tenant/ZoneItem/index.tsx b/ui/src/pages/Tenant/ZoneItem/index.tsx new file mode 100644 index 000000000..84c786c5e --- /dev/null +++ b/ui/src/pages/Tenant/ZoneItem/index.tsx @@ -0,0 +1,75 @@ +import InputNumber from '@/components/InputNumber'; +import { intl } from '@/utils/intl'; +import { Checkbox, Col, Form } from 'antd'; + +interface ZoneItemProps { + name: string; + checked: boolean; + obZoneResource: any; + checkBoxOnChange: (checked: boolean, name: string) => void; + key: number; + formName?:string[]|string; +} + +export default function ZoneItem({ + name, + key, + checked, + obZoneResource, + checkBoxOnChange, + formName = ['pools', name, 'priority'] +}: ZoneItemProps) { + return ( +
+ {name} + checkBoxOnChange(e.target.checked, name)} + /> + + + + + + + {obZoneResource && ( + + + {intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.AvailableResources', + defaultMessage: '可用资源:', + })} + + + CPU {obZoneResource['availableCPU']} + + + Memory {obZoneResource['availableMemory'] / (1 << 30)}GB + + + {intl.formatMessage({ + id: 'Dashboard.Tenant.New.ResourcePools.LogDiskSize', + defaultMessage: '日志磁盘大小', + })} + {obZoneResource['availableLogDisk'] / (1 << 30)}GB + + + )} +
+ ); +} diff --git a/ui/src/pages/Tenant/helper.ts b/ui/src/pages/Tenant/helper.ts index b077ee6e4..4eb1179f7 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 { encryptText } from '@/hook/usePublicKey'; import dayjs from 'dayjs'; -import { clone } from 'lodash'; +import { clone,cloneDeep } from 'lodash'; import type { MaxResourceType } from './New/ResourcePools'; const isExist = (val: string | number | undefined): boolean => { @@ -37,7 +37,7 @@ export function formatNewTenantForm( priority: originFormData[key]?.[zone]?.priority, type: 'Full', })) - .filter((item) => item.priority); + .filter((item) => item.priority || item.priority === 0); } else if (key === 'source') { if (originFormData[key]['tenant'] || originFormData[key]['restore']) result[key] = {}; @@ -48,16 +48,14 @@ export function formatNewTenantForm( let { until } = originFormData[key]['restore']; result[key]['restore'] = { ...originFormData[key]['restore'], - ossAccessId: originFormData[key]['restore'].ossAccessId, - ossAccessKey: originFormData[key]['restore'].ossAccessKey, - // ossAccessId: encryptText( - // originFormData[key]['restore'].ossAccessId, - // publicKey, - // ), - // ossAccessKey: encryptText( - // originFormData[key]['restore'].ossAccessKey, - // publicKey, - // ), + ossAccessId: encryptText( + originFormData[key]['restore'].ossAccessId, + publicKey, + ), + ossAccessKey: encryptText( + originFormData[key]['restore'].ossAccessKey, + publicKey, + ), until: until && until.date && until.time ? { @@ -69,19 +67,16 @@ export function formatNewTenantForm( : { unlimited: true }, }; if (originFormData[key]['restore'].bakEncryptionPassword) { - result[key]['restore']['bakEncryptionPassword'] = - originFormData[key]['restore'].bakEncryptionPassword; - // result[key]['restore']['bakEncryptionPassword'] = encryptText( - // originFormData[key]['restore'].bakEncryptionPassword, - // publicKey, - // ); + result[key]['restore']['bakEncryptionPassword'] = encryptText( + originFormData[key]['restore'].bakEncryptionPassword, + publicKey, + ); } else { delete result[key]['restore']['bakEncryptionPassword']; } } } else if (key === 'rootPassword') { - result[key] = originFormData[key]; - // result[key] = encryptText(originFormData[key], publicKey); + result[key] = encryptText(originFormData[key], publicKey); } else if (key === 'unitConfig') { result[key] = formatUnitConfig(originFormData[key]); } else { @@ -99,17 +94,19 @@ export function formatNewTenantForm( export function formatBackupForm(originFormData: any, publicKey?: string) { let formData = clone(originFormData); if (formData.bakEncryptionPassword) { - formData.bakEncryptionPassword = originFormData.bakEncryptionPassword; - // formData.bakEncryptionPassword = encryptText( - // originFormData.bakEncryptionPassword, - // publicKey, - // ); + formData.bakEncryptionPassword = publicKey + ? encryptText(originFormData.bakEncryptionPassword, publicKey) + : originFormData.bakEncryptionPassword; + } + if (formData.ossAccessId) { + formData.ossAccessId = publicKey + ? encryptText(originFormData.ossAccessId, publicKey) + : originFormData.ossAccessId; } - if (formData.ossAccessId) formData.ossAccessId = originFormData.ossAccessId; if (formData.ossAccessKey) - formData.ossAccessKey = originFormData.ossAccessKey; - // formData.ossAccessId = encryptText(originFormData.ossAccessId, publicKey); - // formData.ossAccessKey = encryptText(originFormData.ossAccessKey, publicKey); + formData.ossAccessKey = publicKey + ? encryptText(originFormData.ossAccessKey, publicKey) + : originFormData.ossAccessId; formData.scheduleTime = dayjs(formData.scheduleTime).format('HH:mm'); formData.scheduleType = formData.scheduleDates.mode; delete formData.scheduleDates.days; @@ -186,4 +183,29 @@ export function findMinParameter( maxLogDisk: findMinValue('availableLogDisk', selectResources), maxMemory: findMinValue('availableMemory', selectResources), }; -} \ No newline at end of file +} + +export const getNewClusterList = ( + clusterList: API.SimpleClusterList, + zone: string, + checked: boolean, + target: { + id?: number; + name?: string; + }, +) => { + const _clusterList = cloneDeep(clusterList); + for (let cluster of _clusterList) { + if ( + cluster.clusterId === target.id || + cluster.clusterName === target.name + ) { + cluster.topology.forEach((zoneItem) => { + if (zoneItem.zone === zone) { + zoneItem.checked = checked; + } + }); + } + } + return _clusterList; +}; \ No newline at end of file diff --git a/ui/src/pages/Tenant/index.tsx b/ui/src/pages/Tenant/index.tsx index bb68a8158..214ac1d8e 100644 --- a/ui/src/pages/Tenant/index.tsx +++ b/ui/src/pages/Tenant/index.tsx @@ -6,7 +6,7 @@ import { useEffect,useRef,useState } from 'react'; import EventsTable from '@/components/EventsTable'; import MonitorComp from '@/components/MonitorComp'; -import { REFRESH_TENANT_TIME } from '@/constants'; +import { REFRESH_TENANT_TIME, RESULT_STATUS } from '@/constants'; import { getAllTenants } from '@/services/tenant'; import TenantsList from './TenantsList'; @@ -29,7 +29,7 @@ export default function TenantPage() { onSuccess: ({ data, successful }) => { if (successful) { const operatingTenant = data.find( - (tenant) => tenant.status !== 'running', + (tenant) => !RESULT_STATUS.includes(tenant.status), ); if (operatingTenant) { timerRef.current = setTimeout(() => { diff --git a/ui/src/services/index.ts b/ui/src/services/index.ts index bec82737c..d0cef7ffb 100644 --- a/ui/src/services/index.ts +++ b/ui/src/services/index.ts @@ -6,7 +6,8 @@ import { request } from '@umijs/max'; import _ from 'lodash'; import moment from 'moment'; -const clusterPrefix = '/api/v1/obclusters'; +const obClusterPrefix = '/api/v1/obclusters'; +const clusterPrefix = '/api/v1/cluster'; export async function loginReq(body: API.User) { return request('/api/v1/login', { @@ -90,7 +91,7 @@ export async function getNodeLabelsReq() { } export async function getClusterStatisticReq(): Promise { - const r = await request('/api/v1/obclusters/statistic', { + const r = await request(`${obClusterPrefix}/statistic`, { method: 'GET', }); return { @@ -100,7 +101,7 @@ export async function getClusterStatisticReq(): Promise('/api/v1/obclusters', { + const r = await request(obClusterPrefix, { method: 'GET', }); if (r.successful) { @@ -142,19 +143,23 @@ export async function getObclusterListReq() { return []; } -export async function getSimpleClusterList(): Promise { - const r = await request('/api/v1/obclusters', { +export async function getSimpleClusterList(): Promise { + const r = await request(obClusterPrefix, { method: 'GET', }); if (r.successful) { - return r.data.map((clusterDetail) => ({ - clusterId: clusterDetail.clusterId, - name: clusterDetail.name, - namespace: clusterDetail.namespace, - topology: clusterDetail.topology, - })); - } - return []; + return{ + ...r, + data:r.data.map((clusterDetail) => ({ + clusterId: clusterDetail.clusterId, + name: clusterDetail.name, + namespace: clusterDetail.namespace, + topology: clusterDetail.topology, + clusterName: clusterDetail.clusterName + })) + } + }; + return r; } export async function getClusterDetailReq({ @@ -168,7 +173,7 @@ export async function getClusterDetailReq({ useFor?: string; tenantReplicas?: API.ReplicaDetailType[]; }) { - const r = await request(`/api/v1/obclusters/namespace/${ns}/name/${name}`, { + const r = await request(`${obClusterPrefix}/namespace/${ns}/name/${name}`, { method: 'GET', }); if (r.successful) { @@ -186,7 +191,7 @@ export async function upgradeObcluster({ name: string; image: string; }) { - const r = await request(`/api/v1/obclusters/namespace/${ns}/name/${name}`, { + const r = await request(`${obClusterPrefix}/namespace/${ns}/name/${name}`, { method: 'POST', data: { image }, }); @@ -208,7 +213,7 @@ export async function scaleObserver({ replicas: number; }) { const r = await request( - `/api/v1/obclusters/namespace/${namespace}/name/${name}/obzones/${zoneName}/scale`, + `${obClusterPrefix}/namespace/${namespace}/name/${name}/obzones/${zoneName}/scale`, { method: 'POST', data: { replicas }, @@ -237,7 +242,7 @@ export async function addObzone({ nodeSelector: { key: string; value: string }[]; }) { const r = await request( - `/api/v1/obclusters/namespace/${namespace}/name/${name}/obzones`, + `${obClusterPrefix}/namespace/${namespace}/name/${name}/obzones`, { method: 'POST', data: body }, ); if (r.successful && !r.message) @@ -258,7 +263,7 @@ export async function deleteObcluster({ ns: string; name: string; }) { - const r = await request(`/api/v1/obclusters/namespace/${ns}/name/${name}`, { + const r = await request(`${obClusterPrefix}/namespace/${ns}/name/${name}`, { method: 'DELETE', }); return { @@ -278,7 +283,7 @@ export async function deleteObzone({ zoneName: string; }) { const r = await request( - `/api/v1/obclusters/namespace/${ns}/name/${name}/obzones/${zoneName}`, + `${obClusterPrefix}/namespace/${ns}/name/${name}/obzones/${zoneName}`, { method: 'DELETE', }, @@ -420,5 +425,5 @@ export async function getEssentialParameters({ ns, name, }: API.NamespaceAndName): Promise { - return request(`${clusterPrefix}/${ns}/${name}/essential-parameters`); + return request(`${obClusterPrefix}/${ns}/${name}/essential-parameters`); } \ No newline at end of file diff --git a/ui/src/services/typings.d.ts b/ui/src/services/typings.d.ts index 42dd0c0e4..20d2920d6 100644 --- a/ui/src/services/typings.d.ts +++ b/ui/src/services/typings.d.ts @@ -69,6 +69,7 @@ declare namespace API { statusDetail: string; rootService: string; observers: Server[]; + checked?: boolean; }; type ClusterItem = { @@ -81,11 +82,16 @@ declare namespace API { type SimpleClusterList = { name: string; + clusterName: string; clusterId: number; namespace: string; topology: Topology[]; }[]; + interface SimpleClusterListResponse extends CommonResponse { + data: SimpleClusterList; + } + type ClusterList = ClusterItem[]; type ModalType = @@ -380,15 +386,6 @@ declare namespace API { unitNum: number; }; - type UnitConfig = { - iopsWeight: number; - logDiskSize: string; - maxIops: number; - memorySize: string; - cpuCount: number; - minIops: number; - }; - type PatchTenantConfiguration = { unitConfig?: { pools?: { diff --git a/ui/src/utils/helper.ts b/ui/src/utils/helper.ts index cff021458..32460488d 100644 --- a/ui/src/utils/helper.ts +++ b/ui/src/utils/helper.ts @@ -1,4 +1,6 @@ +import type { UnitDetailType } from '@/components/customModal/ModifyUnitDetailModal'; import { intl } from '@/utils/intl'; +import { clone } from 'lodash'; type StatisticStatus = 'running' | 'deleting' | 'operating' | 'failed'; type StatisticDataType = { status: StatisticStatus; count: number }[]; @@ -38,3 +40,24 @@ export const formatStatisticData = ( } return r; }; + +export const formatUnitDetailData = (originUnitData: UnitDetailType) => { + const _originUnitData: UnitDetailType = clone(originUnitData); + _originUnitData.unitConfig.unitConfig.logDiskSize = _originUnitData.unitConfig.unitConfig.logDiskSize + 'Gi'; + _originUnitData.unitConfig.unitConfig.memorySize = _originUnitData.unitConfig.unitConfig.memorySize + 'Gi'; + _originUnitData.unitConfig.unitConfig.cpuCount = String( + _originUnitData.unitConfig.unitConfig.cpuCount, + ); + return { + unitConfig: { + unitConfig: _originUnitData.unitConfig.unitConfig, + pools: Object.keys(_originUnitData.unitConfig.pools) + .map((zone) => ({ + zone, + priority: _originUnitData.unitConfig.pools?.[zone]?.priority, + type: 'Full', + })) + .filter((item) => item.priority || item.priority === 0), + }, + }; +}; \ No newline at end of file