diff --git a/ui/src/api/errorHandling.ts b/ui/src/api/errorHandling.ts index 6d04a12b1..49e47a877 100644 --- a/ui/src/api/errorHandling.ts +++ b/ui/src/api/errorHandling.ts @@ -6,7 +6,7 @@ export const errorHandling = (error: any) => { message.warning( intl.formatMessage({ id: 'src.api.2CA64FC6', - defaultMessage: '登陆已过期', + defaultMessage: '登录已过期', }), ); location.href = '/#/login'; @@ -18,6 +18,8 @@ export const errorHandling = (error: any) => { response?.data?.message === 'Error BadRequest: password is incorrect' ) { message.error('原密码输入不正确'); + } else if (response?.status === 403) { + message.warning('无权限访问'); } else { message.error(error?.response?.data?.message || error.message); } diff --git a/ui/src/components/MonitorDetail/index.tsx b/ui/src/components/MonitorDetail/index.tsx index 442b110ad..f7927e8d0 100644 --- a/ui/src/components/MonitorDetail/index.tsx +++ b/ui/src/components/MonitorDetail/index.tsx @@ -1,3 +1,4 @@ +import { useAccess } from '@umijs/max'; import { useUpdateEffect } from 'ahooks'; import dayjs from 'dayjs'; import { useEffect, useRef, useState } from 'react'; @@ -34,6 +35,7 @@ export default function MonitorDetail({ }: MonitorDetailProps) { const [isRefresh, setIsRefresh] = useState(false); const [realTime, setRealTime] = useState(getDate()); + const access = useAccess(); const timerRef = useRef(); const updateTimer = useRef(); const [queryRange, setQueryRange] = @@ -79,17 +81,19 @@ export default function MonitorDetail({ return (
{basicInfo} - + {access.obclusterwrite ? ( + + ) : null} ([]); const checkAll = !checkedList.some( (item) => - !(item.checked.includes('READ') && item.checked.includes('WRITE')), + !(item.checked.includes('read') && item.checked.includes('write')), ); const options = [ - { label: '读', value: 'READ' }, - { label: '写', value: 'WRITE' }, + { label: '读', value: 'read' }, + { label: '写', value: 'write' }, ]; const onCheckAllChange: CheckboxProps['onChange'] = (e) => { if (e.target.checked) { setCheckedList((preCheckedList) => - preCheckedList.map((item) => ({ ...item, checked: ['READ', 'WRITE'] })), + preCheckedList.map((item) => ({ ...item, checked: ['read', 'write'] })), ); } else { setCheckedList((preCheckedList) => @@ -72,19 +72,20 @@ function PermissionSelect({ for (const item of defaultValue) { newCheckedList.push({ domain: item.domain, - checked: item.action === 'WRITE' ? ['WRITE', 'READ'] : [item.action], + checked: item.action === 'write' ? ['write', 'read'] : [item.action], }); } setCheckedList(newCheckedList); } - }, []); + }, [defaultValue]); useEffect(() => { const newValue = []; for (const item of checkedList) { - if (!item.checked.length) continue; + if (!item.checked.includes('write') && !item.checked.includes('read')) + continue; newValue.push({ - action: item.checked.includes('WRITE') ? 'WRITE' : 'READ', + action: item.checked.includes('write') ? 'write' : 'read', domain: item.domain, object: '*', }); @@ -144,7 +145,7 @@ export default function HandleRoleModal({ return { ...item, action: '' }; } else { const editItem = editValue?.policies.find( - (item) => item.domain === item.domain, + (policy) => policy.domain === item.domain, ); return editItem ? { ...editItem } : { ...item, action: '' }; } @@ -154,25 +155,24 @@ export default function HandleRoleModal({ type === Type.CREATE ? await access.createRole(formData) : await access.patchRole( - formData.name, + editValue!.name, pick(formData, ['description', 'permissions']), ); if (res.successful) { message.success('操作成功!'); if (successCallback) successCallback(); - form.resetFields(); setVisible(false); } }; useEffect(() => { - if (type === Type.EDIT) { + if (type === Type.EDIT && visible) { form.setFieldsValue({ description: editValue?.description, permissions: editValue?.policies, }); } - }, [type, editValue]); + }, [type, editValue, visible]); return ( -
+ {type === Type.CREATE && ( - + role.name).join(',') || '-' + } `, + }, + { + key:'description', + label:'描述', + children: `${accountInfo?.description || '-'}`, + }, + { + key: 'lastLoginAt', + label: '最近一次登录', + children: `${accountInfo?.lastLoginAt || '-'}`, + }, + ]; + return ( + { + setVisible(false); + }} + footer={ + + } + > + + + ); +} diff --git a/ui/src/components/customModal/ResetPwdModal.tsx b/ui/src/components/customModal/ResetPwdModal.tsx index 1122adf0d..8444273e4 100644 --- a/ui/src/components/customModal/ResetPwdModal.tsx +++ b/ui/src/components/customModal/ResetPwdModal.tsx @@ -2,6 +2,7 @@ import { access } from '@/api'; import type { ParamResetPasswordParam } from '@/api/generated'; import { encryptText, usePublicKey } from '@/hook/usePublicKey'; import { Form, Input, message } from 'antd'; +import { omit } from 'lodash'; import CustomModal from '.'; interface ResetPwdModalProps { @@ -23,10 +24,12 @@ export default function ResetPwdModal({ form.submit(); } catch (err) {} }; - const onFinish = async (values: ParamResetPasswordParam) => { + const onFinish = async ( + values: ParamResetPasswordParam & { confirmPassword: string }, + ) => { values.oldPassword = encryptText(values.oldPassword!, publicKey) as string; values.password = encryptText(values.password!, publicKey) as string; - const res = await access.resetPassword(values); + const res = await access.resetPassword(omit(values, ['confirmPassword'])); if (res.successful) { message.success('操作成功!'); if (successCallback) successCallback(); @@ -36,7 +39,7 @@ export default function ResetPwdModal({ }; return ( { diff --git a/ui/src/components/customModal/index.tsx b/ui/src/components/customModal/index.tsx index f27a854dc..6c9e8893b 100644 --- a/ui/src/components/customModal/index.tsx +++ b/ui/src/components/customModal/index.tsx @@ -4,15 +4,16 @@ import { ReactNode } from 'react'; interface CustomModalProps { isOpen: boolean; - title: string; - handleOk: () => void; - handleCancel: () => void; + title?: string; + handleOk?: () => void; + handleCancel?: () => void; children: ReactNode; + footer?: React.ReactNode; width?: number; } export default function CustomModal(props: CustomModalProps) { - const { isOpen, handleOk, handleCancel, title, width = 520 } = props; + const { isOpen, handleOk, handleCancel, title, width = 520, footer } = props; return ( {props.children} diff --git a/ui/src/i18n/strings/zh-CN.json b/ui/src/i18n/strings/zh-CN.json index 0fbe8b749..aa0f562ef 100644 --- a/ui/src/i18n/strings/zh-CN.json +++ b/ui/src/i18n/strings/zh-CN.json @@ -985,7 +985,7 @@ "src.components.InputLabelComp.6C88A39D": "添加", "src.components.AlertDrawer.95C6A631": "提交", "src.components.AlertDrawer.9B7CD984": "取消", - "src.api.2CA64FC6": "登陆已过期", + "src.api.2CA64FC6": "登录已过期", "src.pages.OBProxy.New.49694AC5": "创建成功!", "src.pages.OBProxy.New.7CAF48E9": "详细配置", "src.pages.OBProxy.New.0C4EFBB0": "资源设置", diff --git a/ui/src/pages/Access/Accounts.tsx b/ui/src/pages/Access/Accounts.tsx index 2fe4e5700..c8bdd0254 100644 --- a/ui/src/pages/Access/Accounts.tsx +++ b/ui/src/pages/Access/Accounts.tsx @@ -82,6 +82,7 @@ export default function Accounts({ title: '最近一次登陆时间', key: 'lastLoginAt', dataIndex: 'lastLoginAt', + render: (value) => {value || '-'}, }, { title: '操作', @@ -98,7 +99,7 @@ export default function Accounts({
diff --git a/ui/src/pages/Alert/Channel/index.tsx b/ui/src/pages/Alert/Channel/index.tsx index 5dabb83e3..4f90386a4 100644 --- a/ui/src/pages/Alert/Channel/index.tsx +++ b/ui/src/pages/Alert/Channel/index.tsx @@ -92,7 +92,7 @@ export default function Channel() { })} - + + - - + } + > + {intl.formatMessage({ + id: 'Dashboard.Detail.Backup.BackupConfiguration.Delete', + defaultMessage: '删除', + })} + + + ) : null } > - + - + @@ -308,7 +314,7 @@ export default function BackupConfiguration({ - {isEdit ? ( + {isEdit || !access.obclusterwrite ? ( Object.keys(DATE_CONFIG).map((key, index) => ( diff --git a/ui/src/pages/Tenant/Detail/Backup/index.tsx b/ui/src/pages/Tenant/Detail/Backup/index.tsx index a6edb2935..991e5d005 100644 --- a/ui/src/pages/Tenant/Detail/Backup/index.tsx +++ b/ui/src/pages/Tenant/Detail/Backup/index.tsx @@ -3,7 +3,7 @@ import { BACKUP_RESULT_STATUS, REFRESH_TENANT_TIME } from '@/constants'; import { getBackupPolicy, getTenant } from '@/services/tenant'; import { intl } from '@/utils/intl'; import { PageContainer } from '@ant-design/pro-components'; -import { history, useParams } from '@umijs/max'; +import { history, useAccess, useParams } from '@umijs/max'; import { useRequest } from 'ahooks'; import { Button, Card, Col, Row } from 'antd'; import { useEffect, useRef, useState } from 'react'; @@ -13,6 +13,7 @@ import BackupJobs from './BackupJobs'; export default function Backup() { const { ns, name, tenantName } = useParams(); + const access = useAccess(); const [backupPolicy, setBackupPolicy] = useState(); const timerRef = useRef(null); @@ -72,24 +73,31 @@ export default function Backup() { alt="empty" style={{ marginBottom: 24, height: 100, width: 110 }} /> - -

- {intl.formatMessage({ - id: 'Dashboard.Detail.Backup.TheTenantHasNotCreated', - defaultMessage: '该租户尚未创建备份策略,是否立即创建?', - })} -

- + {access.obclusterwrite ? ( + <> +

+ {intl.formatMessage({ + id: 'Dashboard.Detail.Backup.TheTenantHasNotCreated', + defaultMessage: '该租户尚未创建备份策略,是否立即创建?', + })} +

+ + + ) : ( +

+ 该租户尚未创建备份策略 +

+ )} ) : ( diff --git a/ui/src/pages/Tenant/Detail/Monitor/index.tsx b/ui/src/pages/Tenant/Detail/Monitor/index.tsx index c9a36fcb6..4dfcc5ebd 100644 --- a/ui/src/pages/Tenant/Detail/Monitor/index.tsx +++ b/ui/src/pages/Tenant/Detail/Monitor/index.tsx @@ -3,18 +3,17 @@ import { getTenant } from '@/services/tenant'; import { PageContainer } from '@ant-design/pro-components'; import { useParams } from '@umijs/max'; import { useRequest } from 'ahooks'; -import { useEffect,useState } from 'react'; +import { useEffect, useState } from 'react'; import BasicInfo from '../Overview/BasicInfo'; import { getFilterData } from '@/components/MonitorDetail/helper'; - export default function Monitor() { const { ns, name, tenantName } = useParams(); const [filterLabel, setFilterLabel] = useState([ { key: 'tenant_name', - value: tenantName, + value: tenantName!, }, ]); const [filterData, setFilterData] = useState({ @@ -34,7 +33,7 @@ export default function Monitor() { ); useEffect(() => { - getTenantDetail({ ns, name }); + getTenantDetail({ ns: ns!, name: name! }); }, []); const tenantDetail = tenantDetailResponse?.data; return ( diff --git a/ui/src/pages/Tenant/Detail/Overview/Replicas.tsx b/ui/src/pages/Tenant/Detail/Overview/Replicas.tsx index a01a68a7a..0b2123441 100644 --- a/ui/src/pages/Tenant/Detail/Overview/Replicas.tsx +++ b/ui/src/pages/Tenant/Detail/Overview/Replicas.tsx @@ -1,8 +1,8 @@ import CollapsibleCard from '@/components/CollapsibleCard'; import showDeleteConfirm from '@/components/customModal/showDeleteConfirm'; -import { useParams } from '@umijs/max'; import { deleteObtenantPool } from '@/services/tenant'; import { intl } from '@/utils/intl'; +import { useAccess, useParams } from '@umijs/max'; import { Button, Col, Descriptions, message } from 'antd'; import styles from './index.less'; @@ -50,9 +50,10 @@ export default function Replicas({ setEditZone, operateType, cluster, - tenantStatus + tenantStatus, }: ReplicasProps) { const { ns, name } = useParams(); + const access = useAccess(); const sortKeys = (keys: string[]) => { const minCpuIdx = keys.findIndex((key) => key === 'minCPU'); const memorySizeIdx = keys.findIndex((key) => key === 'memorySize'); @@ -63,7 +64,7 @@ export default function Replicas({ }; const deleteResourcePool = async (zoneName: string) => { - const res = await deleteObtenantPool({ ns:ns!, name:name!, zoneName }); + const res = await deleteObtenantPool({ ns: ns!, name: name!, zoneName }); if (res.successful) { refreshTenant(); message.success( @@ -100,16 +101,21 @@ export default function Replicas({ } extra={ - + access.obclusterwrite ? ( + + ) : null } collapsible={true} defaultExpand={true} @@ -132,7 +138,9 @@ export default function Replicas({
+ )), + container} + title={} + placement="bottomLeft" + key={4} > - {item.text} - - )), - container} - title={} - placement="bottomLeft" - key={4} - > - - , - ], + + , + ] + : [], }; }; diff --git a/ui/src/pages/Tenant/Detail/Topo/index.tsx b/ui/src/pages/Tenant/Detail/Topo/index.tsx index 910b54938..e2b01ff34 100644 --- a/ui/src/pages/Tenant/Detail/Topo/index.tsx +++ b/ui/src/pages/Tenant/Detail/Topo/index.tsx @@ -1,15 +1,15 @@ import TopoComponent from '@/components/TopoComponent'; -import { useParams } from '@umijs/max'; -import { getTenant } from '@/services/tenant'; +import { REFRESH_TENANT_TIME, RESULT_STATUS } from '@/constants'; import { getEssentialParameters as getEssentialParametersReq, getSimpleClusterList, - } from '@/services'; +} from '@/services'; +import { getTenant } from '@/services/tenant'; +import { useParams } from '@umijs/max'; import { useRequest } from 'ahooks'; -import BasicInfo from '../Overview/BasicInfo'; -import { REFRESH_TENANT_TIME,RESULT_STATUS } from '@/constants'; +import { useEffect, useRef, useState } from 'react'; import { getClusterFromTenant } from '../../helper'; -import { useState, useEffect, useRef } from 'react'; +import BasicInfo from '../Overview/BasicInfo'; export default function Topo() { const { ns, name } = useParams(); @@ -17,9 +17,13 @@ export default function Topo() { const [editZone, setEditZone] = useState(''); const timerRef = useRef(); const [defaultUnitCount, setDefaultUnitCount] = useState(1); - const { data: tenantResponse,refresh: reGetTenantDetail,loading } = useRequest(getTenant, { - defaultParams: [{ ns, name }], - onSuccess:({ data, successful }) => { + const { + data: tenantResponse, + refresh: reGetTenantDetail, + loading, + } = useRequest(getTenant, { + defaultParams: [{ ns: ns!, name: name! }], + onSuccess: ({ data, successful }) => { if (successful) { if (data.info.unitNumber) { setDefaultUnitCount(data.info.unitNumber); @@ -32,7 +36,7 @@ export default function Topo() { clearTimeout(timerRef.current); } } - } + }, }); useRequest(getSimpleClusterList, { @@ -48,9 +52,9 @@ export default function Topo() { }, }); const { data: essentialParameterRes, run: getEssentialParameters } = - useRequest(getEssentialParametersReq, { - manual: true, - }); + useRequest(getEssentialParametersReq, { + manual: true, + }); const tenantTopoData = tenantResponse?.data; const essentialParameter = essentialParameterRes?.data; useEffect(() => { diff --git a/ui/src/pages/Tenant/Detail/index.tsx b/ui/src/pages/Tenant/Detail/index.tsx index c35a48b7a..483c33481 100644 --- a/ui/src/pages/Tenant/Detail/index.tsx +++ b/ui/src/pages/Tenant/Detail/index.tsx @@ -1,12 +1,12 @@ import DetailLayout from '@/pages/Layouts/DetailLayout'; import { intl } from '@/utils/intl'; import type { MenuItem } from '@oceanbase/design/es/BasicLayout'; -import { useParams } from '@umijs/max'; +import { useAccess, useParams } from '@umijs/max'; export default () => { const params = useParams(); const { ns, name, tenantName } = params; - + const access = useAccess(); const menus: MenuItem[] = [ { title: intl.formatMessage({ @@ -44,6 +44,7 @@ export default () => { }), key: 'connection', link: `/tenant/${ns}/${name}/${tenantName}/connection`, + accessible: access.obclusterwrite, }, ]; return ;