import { intl } from '@/utils/intl'; import { useEffect, useState, useRef } from 'react'; import { useModel } from 'umi'; import { Space, Button, Form, Tag, Table, Alert, Tooltip, Select, Modal, Spin, message, } from 'antd'; import { ProCard, ProForm, ProFormText } from '@ant-design/pro-components'; import { CloseOutlined, SafetyCertificateFilled, InfoOutlined, InfoCircleOutlined, CopyOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import useRequest from '@/utils/useRequest'; import { queryAllComponentVersions } from '@/services/ob-deploy-web/Components'; import { getDeployment, destroyDeployment, } from '@/services/ob-deploy-web/Deployments'; import { listRemoteMirrors } from '@/services/ob-deploy-web/Mirror'; import { handleQuit, checkLowVersion, getErrorInfo } from '@/utils'; import NP from 'number-precision'; import copy from 'copy-to-clipboard'; import DeployType from './DeployType'; import DeleteDeployModal from './DeleteDeployModal'; import { commonStyle, allComponentsName, oceanbaseComponent, obproxyComponent, ocpexpressComponent, obagentComponent, } from '../constants'; import { getLocale } from 'umi'; import EnStyles from './indexEn.less'; import ZhStyles from './indexZh.less'; const locale = getLocale(); const styles = locale === 'zh-CN' ? ZhStyles : EnStyles; interface FormValues { type?: string; } const appnameReg = /^[a-zA-Z]([a-zA-Z0-9]{0,19})$/; const componentsGroupInfo = [ { group: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Database', defaultMessage: '数据库', }), key: 'database', content: [ { key: oceanbaseComponent, name: 'OceanBase Database', onlyAll: false, desc: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.ItIsAFinancialLevel', defaultMessage: '是金融级分布式数据库,具备数据强一致、高扩展、高可用、高性价比、稳定可靠等特征。', }), doc: 'https://www.oceanbase.com/docs/oceanbase-database-cn', }, ], }, { group: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Proxy', defaultMessage: '代理', }), key: 'agency', onlyAll: true, content: [ { key: obproxyComponent, name: 'OBProxy', onlyAll: true, desc: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.ItIsAProxyServer', defaultMessage: '是 OceanBase 数据库专用的代理服务器,可以将用户 SQL 请求转发至最佳目标 OBServer 。', }), doc: 'https://www.oceanbase.com/docs/odp-enterprise-cn', }, ], }, { group: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Tools', defaultMessage: '工具', }), key: 'tool', onlyAll: true, content: [ { key: ocpexpressComponent, name: 'OCP Express', onlyAll: true, desc: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.ItIsAManagementAnd', defaultMessage: '是专为 OceanBase 设计的管控平台,可实现对集群、租户的监控管理、诊断等核心能力。', }), doc: 'https://www.oceanbase.com/docs/common-oceanbase-database-cn-0000000001626262', }, { key: obagentComponent, name: 'OBAgent', onlyAll: true, desc: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.IsAMonitoringAndCollection', defaultMessage: '是一个监控采集框架。OBAgent 支持推、拉两种数据采集模式,可以满足不同的应用场景。', }), doc: 'https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001576872', }, ], }, ]; const mirrors = ['oceanbase.community.stable', 'oceanbase.development-kit']; export default function InstallConfig() { const { initAppName, setCurrentStep, configData, setConfigData, currentType, setCurrentType, lowVersion, isFirstTime, setIsFirstTime, isDraft, setIsDraft, componentsVersionInfo, setComponentsVersionInfo, handleQuitProgress, getInfoByName, setLowVersion, setErrorVisible, errorsList, setErrorsList, } = useModel('global'); const { components, home_path } = configData || {}; const { oceanbase } = components || {}; const [existNoVersion, setExistNoVersion] = useState(false); const [obVersionValue, setOBVersionValue] = useState( undefined, ); const [hasDraft, setHasDraft] = useState(false); const [deleteLoadingVisible, setDeleteLoadingVisible] = useState(false); const [deleteName, setDeleteName] = useState(''); const [installMemory, setInstallMemory] = useState(0); const [form] = ProForm.useForm(); const [unavailableList, setUnavailableList] = useState([]); const [componentLoading, setComponentLoading] = useState(false); const draftNameRef = useRef(); const { run: fetchDeploymentInfo, loading } = useRequest(getDeployment, { onError: (e: any) => { const errorInfo = getErrorInfo(e); setErrorVisible(true); setErrorsList([...errorsList, errorInfo]); }, }); const { run: handleDeleteDeployment } = useRequest(destroyDeployment, { onError: (e: any) => { const errorInfo = getErrorInfo(e); setErrorVisible(true); setErrorsList([...errorsList, errorInfo]); }, }); const { run: fetchListRemoteMirrors } = useRequest(listRemoteMirrors, { onSuccess: () => { setComponentLoading(false); }, onError: ({ response, data, type }: any) => { if (response?.status === 503) { setTimeout(() => { fetchListRemoteMirrors(); }, 1000); } else { const errorInfo = getErrorInfo({ response, data, type }); setErrorVisible(true); setErrorsList([...errorsList, errorInfo]); setComponentLoading(false); } }, }); const judgVersions = (type: string, source: API.ComponentsVersionInfo) => { if (type === 'all') { if (Object.keys(source).length !== allComponentsName.length) { setExistNoVersion(true); } else { setExistNoVersion(false); } } else { if ( !(source?.[oceanbaseComponent] && source?.[oceanbaseComponent]?.version) ) { setExistNoVersion(true); } else { setExistNoVersion(false); } } }; const { run: fetchAllComponentVersions, loading: versionLoading } = useRequest(queryAllComponentVersions, { onSuccess: async ({ success, data, }: API.OBResponseDataListComponent_) => { if (success) { const newComponentsVersionInfo = {}; data?.items?.forEach((item) => { if (allComponentsName.includes(item.name)) { if (item?.info?.length) { const initVersionInfo = item?.info[0] || {}; if (item.name === oceanbaseComponent) { const newSelectedVersionInfo = item.info.filter( (item) => item.md5 === oceanbase?.package_hash, )?.[0]; const currentSelectedVersionInfo = newSelectedVersionInfo || initVersionInfo; setOBVersionValue( `${currentSelectedVersionInfo?.version}-${currentSelectedVersionInfo?.release}-${currentSelectedVersionInfo?.md5}`, ); newComponentsVersionInfo[item.name] = { ...currentSelectedVersionInfo, dataSource: item.info || [], }; } else { newComponentsVersionInfo[item.name] = { ...initVersionInfo, dataSource: item.info || [], }; } } } }); const noVersion = Object.keys(newComponentsVersionInfo).length !== allComponentsName.length; judgVersions(currentType, newComponentsVersionInfo); setComponentsVersionInfo(newComponentsVersionInfo); if (noVersion) { const { success: mirrorSuccess, data: mirrorData } = await fetchListRemoteMirrors(); if (mirrorSuccess) { const nameList: string[] = []; if (mirrorData?.total < 2) { const mirrorName = mirrorData?.items?.map( (item: API.Mirror) => item.section_name, ); const noDataName = [...mirrorName, ...mirrors].filter( (name) => mirrors.includes(name) && !mirrorName.includes(name), ); noDataName.forEach((name) => { nameList.push(name); }); } if (mirrorData?.total) { mirrorData?.items?.forEach((item: API.Mirror) => { if (!item.available) { nameList.push(item.section_name); } }); } setUnavailableList(nameList); } } else { setComponentLoading(false); } } }, onError: ({ response, data, type }: any) => { if (response?.status === 503) { setTimeout(() => { fetchAllComponentVersions(); }, 1000); } else { const errorInfo = getErrorInfo({ response, data, type }); setErrorVisible(true); setErrorsList([...errorsList, errorInfo]); setComponentLoading(false); } }, }); const onValuesChange = (values: FormValues) => { if (values?.type) { setCurrentType(values?.type); judgVersions(values?.type, componentsVersionInfo); } }; const nameValidator = async (_: any, value: string) => { if (value) { if (hasDraft || isDraft) { return Promise.resolve(); } if (!appnameReg.test(value)) { return Promise.reject( new Error( intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.TheInitialLetterIsEnglish', defaultMessage: '首字母英文且仅支持英文、数字,长度不超过20', }), ), ); } try { const { success, data } = await getInfoByName({ name: value }); if (success) { if (['CONFIGURED', 'DESTROYED'].includes(data?.status)) { return Promise.resolve(); } return Promise.reject( new Error( intl.formatMessage( { id: 'OBD.pages.components.InstallConfig.ADeploymentNameWithValue', defaultMessage: '已存在为 {value} 的部署名称,请指定新名称', }, { value: value }, ), ), ); } return Promise.resolve(); } catch ({ response, data, type }: any) { if (response?.status === 404) { return Promise.resolve(); } else { const errorInfo = getErrorInfo({ response, data, type }); setErrorVisible(true); setErrorsList([...errorsList, errorInfo]); } } } }; const nextStep = () => { form.validateFields().then((values) => { const lastAppName = oceanbase?.appname || initAppName; let newHomePath = home_path; if (values?.appname !== lastAppName && home_path) { const firstHalfHomePath = home_path.split(`/${lastAppName}`)[0]; newHomePath = `${firstHalfHomePath}/${values?.appname}`; } let newComponents: API.Components = { oceanbase: { ...(components?.oceanbase || {}), component: componentsVersionInfo?.[oceanbaseComponent]?.version_type === 'ce' ? 'oceanbase-ce' : 'oceanbase', appname: values?.appname, version: componentsVersionInfo?.[oceanbaseComponent]?.version, release: componentsVersionInfo?.[oceanbaseComponent]?.release, package_hash: componentsVersionInfo?.[oceanbaseComponent]?.md5, }, }; if (currentType === 'all') { newComponents.obproxy = { ...(components?.obproxy || {}), component: componentsVersionInfo?.[obproxyComponent]?.version_type === 'ce' ? 'obproxy-ce' : 'obproxy', version: componentsVersionInfo?.[obproxyComponent]?.version, release: componentsVersionInfo?.[obproxyComponent]?.release, package_hash: componentsVersionInfo?.[obproxyComponent]?.md5, }; if (!lowVersion) { newComponents.ocpexpress = { ...(components?.ocpexpress || {}), component: ocpexpressComponent, version: componentsVersionInfo?.[ocpexpressComponent]?.version, release: componentsVersionInfo?.[ocpexpressComponent]?.release, package_hash: componentsVersionInfo?.[ocpexpressComponent]?.md5, }; } newComponents.obagent = { ...(components?.obagent || {}), component: obagentComponent, version: componentsVersionInfo?.[obagentComponent]?.version, release: componentsVersionInfo?.[obagentComponent]?.release, package_hash: componentsVersionInfo?.[obagentComponent]?.md5, }; } setConfigData({ ...configData, components: newComponents, home_path: newHomePath, }); setCurrentStep(2); setIsFirstTime(false); setErrorVisible(false); setErrorsList([]); window.scrollTo(0, 0); }); }; const onVersionChange = ( value: string, dataSource: API.service_model_components_ComponentInfo[], ) => { const md5 = value.split('-')[2]; setOBVersionValue(value); const newSelectedVersionInfo = dataSource.filter( (item) => item.md5 === md5, )[0]; setComponentsVersionInfo({ ...componentsVersionInfo, [oceanbaseComponent]: { ...componentsVersionInfo[oceanbaseComponent], ...newSelectedVersionInfo, }, }); setLowVersion( !!( newSelectedVersionInfo.version && checkLowVersion(newSelectedVersionInfo.version.split('')[0]) ), ); }; const directTo = (url: string) => { // 在新的标签页中打开 const blankWindow = window.open('about:blank'); if (blankWindow) { blankWindow.location.href = url; } else { // 兜底逻辑,在当前标签页打开 window.location.href = url; } }; const getColumns = (group: string) => { const columns: ColumnsType = [ { title: group, dataIndex: 'name', width: 195, render: (text, record) => { if (currentType === 'all') { return ( <> {text} {record.key === ocpexpressComponent && lowVersion ? ( ) : !componentsVersionInfo[record.key]?.version ? ( ) : null} ); } return text; }, }, { title: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Version', defaultMessage: '版本', }), dataIndex: 'version', width: locale === 'zh-CN' ? 130 : 154, render: (_, record) => { const versionInfo = componentsVersionInfo[record.key] || {}; if (record?.key === oceanbaseComponent) { return ( ); } else { return versionInfo?.version ? ( <> {versionInfo?.version} {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Latest', defaultMessage: '最新', })} ) : ( '-' ); } }, }, { title: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Description', defaultMessage: '描述', }), dataIndex: 'desc', render: (text, record) => { let disabled = false; if ( (record.key === ocpexpressComponent && lowVersion) || (currentType === 'ob' && record.onlyAll) ) { disabled = true; } return ( <> {text || '-'} { if (!disabled) directTo(record.doc); }} target="_blank" > {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.LearnMore', defaultMessage: '了解更多', })} ); }, }, ]; return columns; }; const handleCopy = (content: string) => { copy(content); message.success( intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.CopiedSuccessfully', defaultMessage: '复制成功', }), ); }; useEffect(() => { setComponentLoading(true); if (isFirstTime) { fetchAllComponentVersions(); fetchDeploymentInfo({ task_status: 'DRAFT' }).then( ({ success: draftSuccess, data: draftData }: API.OBResponse) => { if (draftSuccess && draftData?.items?.length) { const defaultValue = draftData?.items[0]?.name; draftNameRef.current = defaultValue; setHasDraft(true); Modal.confirm({ title: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.TheFollowingHistoricalConfigurationsOf', defaultMessage: '检测到系统中存在以下部署失败的历史配置', }), okText: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.ContinueDeployment', defaultMessage: '继续部署', }), cancelText: intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.Ignore', defaultMessage: '忽略', }), closable: true, width: 424, content: (
{intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.ContinuingDeploymentWillCleanUp', defaultMessage: '继续部署将先清理失败的历史部署环境,是否继续历史部署流程?', })}
), onOk: () => { return new Promise(async (resolve) => { try { const { success: deleteSuccess } = await handleDeleteDeployment({ name: draftNameRef.current, }); if (deleteSuccess) { resolve(); setDeleteName(draftNameRef.current); setDeleteLoadingVisible(true); } } catch { setIsDraft(false); resolve(); } }); }, onCancel: () => { setIsDraft(false); setHasDraft(false); }, }); } else { setIsDraft(false); } }, ); } else { fetchAllComponentVersions(); } }, []); useEffect(() => { let newInstallMemory = 0; if (currentType === 'ob') { newInstallMemory = componentsVersionInfo?.[oceanbaseComponent]?.estimated_size; } else { const keys = Object.keys(componentsVersionInfo); keys.forEach((key) => { newInstallMemory = newInstallMemory + componentsVersionInfo[key]?.estimated_size; }); } setInstallMemory(newInstallMemory); }, [componentsVersionInfo, currentType]); useEffect(() => { form.setFieldsValue({ type: currentType }); }, [currentType]); useEffect(() => { form.setFieldsValue({ appname: configData?.components?.oceanbase?.appname || initAppName, }); }, [configData]); const size = NP.divide(NP.divide(installMemory, 1024), 1024).toFixed(2); return ( {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.DeployComponents', defaultMessage: '部署组件', })} {' '} {intl.formatMessage( { id: 'OBD.pages.components.InstallConfig.EstimatedInstallationRequiresSizeMb', defaultMessage: '预计安装需要 {size}MB 空间', }, { size: size }, )} } className="card-header-padding-top-0 card-padding-bottom-24 card-padding-top-0" > {existNoVersion ? ( unavailableList?.length ? ( {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.IfTheCurrentEnvironmentCannot', defaultMessage: '如当前环境无法正常访问外网,建议使用 OceanBase 离线安装包进行安装部署。', })} {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.GoToDownloadOfflineInstallation', defaultMessage: '前往下载离线安装', })} } type="error" showIcon style={{ marginTop: '16px' }} /> ) : ( {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.IfTheCurrentEnvironmentHas', defaultMessage: '如当前环境可正常访问外网,可启动 OceanBase 在线镜像仓库,或联系您的镜像仓库管理员。', })} {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.RunTheCommandOnThe', defaultMessage: '请在主机上执行一下命令启用在线镜像仓库', })}
obd mirror enable oceanbase.community.stable oceanbase.development-kit handleCopy( 'obd mirror enable oceanbase.community.stable oceanbase.development-kit', ) } /> } > {intl.formatMessage({ id: 'OBD.pages.components.InstallConfig.HowToEnableOnlineImage', defaultMessage: '如何启用在线镜像仓库', })}
} type="error" showIcon style={{ marginTop: '16px' }} /> ) ) : null} {componentsGroupInfo.map((info) => ( { if ( (record.key === ocpexpressComponent && lowVersion) || (currentType === 'ob' && record?.onlyAll) ) { return styles.disabledRow; } }} /> ))}
{deleteLoadingVisible && ( setDeleteLoadingVisible(false)} setOBVersionValue={setOBVersionValue} /> )} ); }