Skip to content

Commit

Permalink
Feat access control (#500)
Browse files Browse the repository at this point in the history
  • Loading branch information
yang1666204 authored Aug 6, 2024
1 parent 15a4465 commit 15a1c56
Show file tree
Hide file tree
Showing 35 changed files with 608 additions and 427 deletions.
2 changes: 2 additions & 0 deletions ui/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import routes from './routes';

export default defineConfig({
antd: {},
access: {},
model: {},
request: {},
initialState: {},
favicons: ['/logo.png'],
title: 'OceanBase Dashboard',
layout: false,
Expand Down
4 changes: 2 additions & 2 deletions ui/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ export default [
],
},
{
path:'access',
path: 'access',
component: 'Access',
name: '权限控制'
name: '权限控制',
},
{
path: 'overview',
Expand Down
17 changes: 17 additions & 0 deletions ui/src/access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { AcAccount, AcPolicy } from './api/generated';
import { initializeAccess } from './utils/helper';

export type InitialStateType = {
accountInfo: AcAccount;
policies: AcPolicy[];
};

export default function (initialState: InitialStateType): {
[T: string]: boolean;
} {
const accessObj = Object.create(null);
if (initialState) initializeAccess(accessObj, initialState);
return {
...accessObj,
};
}
16 changes: 15 additions & 1 deletion ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { message } from 'antd';
import dayjs from 'dayjs';
import localeData from 'dayjs/plugin/localeData';
import weekday from 'dayjs/plugin/weekday';
import { access } from './api';

dayjs.extend(weekday);
dayjs.extend(localeData);
Expand Down Expand Up @@ -32,7 +33,6 @@ export const request: RequestConfig = {
}
},
},

};
export const rootContainer = (element: JSX.Element) => {
const locale = getLocale() || 'zh-CN';
Expand All @@ -41,3 +41,17 @@ export const rootContainer = (element: JSX.Element) => {
};
return <>{element}</>;
};

export async function getInitialState() {
const res = await Promise.all([
access.getAccountInfo(),
access.listAllPolicies(),
]);
if (res[0].successful && res[1].successful) {
return {
accountInfo: res[0].data,
policies: res[1].data,
};
}
return {};
}
12 changes: 6 additions & 6 deletions ui/src/components/TopoComponent/G6register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function config(width: number, height: number) {
width,
height,
linkCenter: true,
fitViewPadding: [-350,24,24,24],
fitViewPadding: [-350, 24, 24, 24],
fitView: true,
maxZoom: 1.6,
minZoom: 0.2,
Expand Down Expand Up @@ -117,7 +117,7 @@ const reactStyles = {
radius: 5,
};

function ReactNode(handleClick?: any) {
function ReactNode(handleClick?: any, writable?: boolean) {
return ({ cfg }: any) => {
// Zones not included in the tenant will be disabled
const { label, status, typeText, disable } = cfg;
Expand Down Expand Up @@ -190,22 +190,22 @@ function ReactNode(handleClick?: any) {
>
{status}
</Text>
{cfg.type !== 'server' && (
{cfg.type !== 'server' && writable ? (
<Image
onClick={ handleClick}
onClick={handleClick}
id={cfg.label}
style={{
position: 'absolute',
x: 130,
y: 16,
width: 2.5,
height: 16,
cursor:'pointer',
cursor: 'pointer',
img: moreImg,
}}
name="moreImg"
/>
)}
) : null}
</Rect>
</Group>
);
Expand Down
19 changes: 13 additions & 6 deletions ui/src/components/TopoComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import MoreModal from '@/components/moreModal';
import { intl } from '@/utils/intl';
import G6, { IG6GraphEvent } from '@antv/g6';
import { createNodeFromReact } from '@antv/g6-react-node';
import { useParams } from '@umijs/max';
import { useAccess, useParams } from '@umijs/max';
import { useRequest, useUpdateEffect } from 'ahooks';
import { Spin, message } from 'antd';
import _ from 'lodash';
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react';

import showDeleteConfirm from '@/components/customModal/showDeleteConfirm';
import OperateModal from '@/components/customModal/OperateModal';
import showDeleteConfirm from '@/components/customModal/showDeleteConfirm';
import { RESULT_STATUS } from '@/constants';
import BasicInfo from '@/pages/Cluster/Detail/Overview/BasicInfo';
import {
Expand All @@ -23,8 +23,8 @@ import {
deleteObzoneReportWrap,
} from '@/services/reportRequest/clusterReportReq';
import { deleteObtenantPool } from '@/services/tenant';
import { ReactNode, config } from './G6register';
import type { Topo } from '@/type/topo';
import { ReactNode, config } from './G6register';
import {
clusterOperate,
clusterOperateOfTenant,
Expand Down Expand Up @@ -52,7 +52,6 @@ interface TopoProps {
loading?: boolean;
}

//Cluster topology diagram component
export default function TopoComponent({
tenantReplicas,
header,
Expand All @@ -65,6 +64,7 @@ export default function TopoComponent({
loading,
}: TopoProps) {
const { ns: urlNs, name: urlName } = useParams();
const access = useAccess();
const clusterOperateList = tenantReplicas
? clusterOperateOfTenant
: clusterOperate;
Expand Down Expand Up @@ -199,8 +199,15 @@ export default function TopoComponent({
const height = container?.scrollHeight || 500;

graph.current = new G6.TreeGraph(config(width, height));
G6.registerNode('cluster', createNodeFromReact(ReactNode(handleClick)));
G6.registerNode('zone', createNodeFromReact(ReactNode(handleClick)));
const writable = tenantReplicas ? true : access.obclusterwrite;
G6.registerNode(
'cluster',
createNodeFromReact(ReactNode(handleClick, writable)),
);
G6.registerNode(
'zone',
createNodeFromReact(ReactNode(handleClick, writable)),
);
G6.registerNode('server', createNodeFromReact(ReactNode()));
G6.registerEdge('flow-line', {
draw(cfg, group) {
Expand Down
16 changes: 14 additions & 2 deletions ui/src/components/customModal/HandleAccountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { access } from '@/api';
import type { AcCreateAccountParam } from '@/api/generated';
import type { AcAccount, AcCreateAccountParam } from '@/api/generated';
import { Type } from '@/pages/Access/type';
import { useRequest } from 'ahooks';
import { Form, Input, Select, message } from 'antd';
import { omit } from 'lodash';
import { useMemo } from 'react';
import { useEffect, useMemo } from 'react';
import CustomModal from '.';

interface HandleRoleModalProps {
visible: boolean;
setVisible: (visible: boolean) => void;
successCallback?: () => void;
editValue?: AcAccount;
type: Type;
}

export default function HandleAccountModal({
visible,
setVisible,
successCallback,
editValue,
type,
}: HandleRoleModalProps) {
const [form] = Form.useForm();
Expand Down Expand Up @@ -53,6 +55,16 @@ export default function HandleAccountModal({
setVisible(false);
}
};

useEffect(() => {
if (type === Type.EDIT) {
form.setFieldsValue({
description: editValue?.description,
nickname: editValue?.nickname,
roles: editValue?.roles,
});
}
}, [type, editValue]);
return (
<CustomModal
title={`${type === Type.EDIT ? '编辑' : '创建'}用户`}
Expand Down
7 changes: 3 additions & 4 deletions ui/src/components/customModal/HandleRoleModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { access } from '@/api';
import type { AcCreateRoleParam, AcPolicy, AcRole } from '@/api/generated';
import { Type } from '@/pages/Access/type';
import { useRequest } from 'ahooks';
import { useModel } from '@umijs/max';
import type { CheckboxProps } from 'antd';
import { Checkbox, Col, Form, Input, Row, message } from 'antd';
import { pick, uniqBy } from 'lodash';
Expand Down Expand Up @@ -131,15 +131,14 @@ export default function HandleRoleModal({
type,
}: HandleRoleModalProps) {
const [form] = Form.useForm();
const { initialState } = useModel('@@initialState');
const handleSubmit = async () => {
try {
await form.validateFields();
form.submit();
} catch (err) {}
};
const { data: allPoliciesRes } = useRequest(access.listAllPolicies);

const allPolicies = uniqBy(allPoliciesRes?.data, 'domain') || [];
const allPolicies = uniqBy(initialState?.policies, 'domain') || [];
const defaultValue = allPolicies.map((item) => {
if (type === Type.CREATE) {
return { ...item, action: '' };
Expand Down
34 changes: 27 additions & 7 deletions ui/src/pages/Access/Accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { access } from '@/api';
import { access as accessReq } from '@/api';
import type { AcAccount, AcRole } from '@/api/generated';
import HandleAccountModal from '@/components/customModal/HandleAccountModal';
import showDeleteConfirm from '@/components/customModal/showDeleteConfirm';
import { useAccess } from '@umijs/max';
import { useRequest } from 'ahooks';
import type { TableProps } from 'antd';
import { Button, Space, Table, message } from 'antd';
import { useState } from 'react';
import { Type } from './type';

export default function Accounts() {
const { data: allAccountsRes } = useRequest(access.listAllAccounts);
const allAccounts = allAccountsRes?.data;
interface AccountsProps {
allAccounts: AcAccount[] | undefined;
refreshAccounts: () => void;
}

export default function Accounts({
allAccounts,
refreshAccounts,
}: AccountsProps) {
const access = useAccess();
const [editData, setEditData] = useState<AcAccount>();
const [modalVisible, setModalVisible] = useState<boolean>(false);
const { run: deleteAccount } = useRequest(access.deleteAccount, {
const { run: deleteAccount } = useRequest(accessReq.deleteAccount, {
manual: true,
onSuccess: ({ successful }) => {
if (successful) {
message.success('删除成功');
refreshAccounts();
}
},
});
const handleEdit = (editData: AcAccount) => {
setEditData(editData);
setModalVisible(true);
};
const columns: TableProps<AcAccount>['columns'] = [
{
title: '用户名',
Expand Down Expand Up @@ -53,13 +67,18 @@ export default function Accounts() {
title: '操作',
key: 'actinon',
render: (_, record) => {
const disabled = record.roles.some((role) => role.name === 'admin');
const disabled =
record.roles.some((role) => role.name === 'admin') || !access.acwrite;
return (
<Space>
<Button disabled={disabled} type="link">
重置
</Button>
<Button disabled={disabled} type="link">
<Button
onClick={() => handleEdit(record)}
disabled={disabled}
type="link"
>
编辑
</Button>
<Button
Expand All @@ -85,6 +104,7 @@ export default function Accounts() {
<Table dataSource={allAccounts} columns={columns} />
<HandleAccountModal
setVisible={setModalVisible}
editValue={editData}
visible={modalVisible}
type={Type.EDIT}
/>
Expand Down
19 changes: 13 additions & 6 deletions ui/src/pages/Access/Roles.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { access } from '@/api';
import { access as accessReq } from '@/api';
import type { AcPolicy, AcRole } from '@/api/generated';
import HandleRoleModal from '@/components/customModal/HandleRoleModal';
import showDeleteConfirm from '@/components/customModal/showDeleteConfirm';
import { useAccess } from '@umijs/max';
import { useRequest } from 'ahooks';
import type { TableProps } from 'antd';
import { Button, Space, Table, message } from 'antd';
import { useState } from 'react';
import { Type } from './type';

export default function Roles() {
interface RolesProps {
allRoles: AcRole[] | undefined;
refreshRoles: () => void;
}

export default function Roles({ allRoles, refreshRoles }: RolesProps) {
const [modalVisible, setModalVisible] = useState<boolean>(false);
const access = useAccess();
const [editData, setEditData] = useState<AcRole>();
const { data: allRolesRes } = useRequest(access.listAllRoles);
const allRoles = allRolesRes?.data;
const { run: deleteRole } = useRequest(access.deleteRole, {

const { run: deleteRole } = useRequest(accessReq.deleteRole, {
manual: true,
onSuccess: ({ successful }) => {
if (successful) {
message.success('删除成功!');
refreshRoles();
}
},
});
Expand Down Expand Up @@ -52,7 +59,7 @@ export default function Roles() {
title: '操作',
key: 'action',
render: (_, record) => {
const disabled = record.name === 'admin';
const disabled = record.name === 'admin' || !access.acwrite;
return (
<Space>
<Button
Expand Down
Loading

0 comments on commit 15a1c56

Please sign in to comment.