forked from ddrilling/asb_cloud_front
Методы работы с правами переработаны, стили исправлены
This commit is contained in:
parent
6ce6ba04ea
commit
d9abe87ba4
@ -1,13 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Role, Permissions, hasAccess, PermissionMixingType } from '../../utils/permissions'
|
import { Role, Permission, hasPermission, isInRole } from '../../utils/PermissionService'
|
||||||
|
|
||||||
type PrivateContentProps = {
|
type PrivateContentProps = {
|
||||||
permissions?: Permissions
|
roles?: Role[] | Role
|
||||||
roles?: Role[]
|
permission?: Permission
|
||||||
mixing?: PermissionMixingType
|
children?: React.ReactElement<any, any>
|
||||||
children?: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivateContent: React.FC<PrivateContentProps> = ({ permissions, roles, mixing, children }) => {
|
export const PrivateContent: React.FC<PrivateContentProps> = ({ permission, roles, children = null }) =>
|
||||||
return (hasAccess({ permissions, roles, mixing }) && children) || null
|
hasPermission(permission) || isInRole(roles) ? children : null
|
||||||
}
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Menu } from 'antd'
|
import { Menu } from 'antd'
|
||||||
import { hasAccess, Role, Permissions, PermissionMixingType } from '../../utils/permissions'
|
import { Role, Permission, hasPermission, isInRole } from '../../utils/PermissionService'
|
||||||
|
|
||||||
|
|
||||||
type PrivateMenuItemProps = {
|
type PrivateMenuItemProps = {
|
||||||
roles?: Role[]
|
roles?: Role[] | Role
|
||||||
permissions?: Permissions
|
permission?: Permission
|
||||||
mixing?: PermissionMixingType
|
|
||||||
[props: string]: any
|
[props: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivateMenuItem: React.FC<PrivateMenuItemProps> = ({ roles, permissions, mixing, ...props }) => {
|
export const PrivateMenuItem: React.FC<PrivateMenuItemProps> = ({ roles, permission, mixing, ...props }) =>
|
||||||
return hasAccess({ permissions, roles, mixing }) ? <Menu.Item {...props}/> : null
|
hasPermission(permission) || isInRole(roles) ? <Menu.Item {...props}/> : null
|
||||||
}
|
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Route, Redirect } from 'react-router-dom'
|
import { StaticContext } from 'react-router'
|
||||||
import { Role, Permissions, hasAccess } from '../../utils/permissions'
|
import { Route, Redirect, RouteComponentProps } from 'react-router-dom'
|
||||||
|
import { Role, Permission, hasPermission, isInRole } from '../../utils/PermissionService'
|
||||||
|
|
||||||
type PrivateRouteProps = {
|
type PrivateRouteProps = {
|
||||||
permissions?: Permissions
|
roles: Role[] | Role
|
||||||
roles: Role[]
|
permission?: Permission
|
||||||
component?: any
|
component?: React.ComponentType<any> | React.ComponentType<RouteComponentProps<any, StaticContext, unknown>>
|
||||||
children?: any
|
children?: React.ReactNode
|
||||||
[other: string]: any
|
[other: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivateRoute: React.FC<PrivateRouteProps> = ({ permissions, roles, component, children, ...other }) => {
|
export const PrivateRoute: React.FC<PrivateRouteProps> = ({ permission, roles, component, children, ...other }) => {
|
||||||
const available = localStorage['token'] && hasAccess({ permissions, roles })
|
const available = localStorage.getItem('token') && (hasPermission(permission) || isInRole(roles))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route {...other}
|
<Route {...other}
|
||||||
component={available && component}
|
component={available ? component : undefined}
|
||||||
render={({ location }) => available ? children : (
|
render={({ location }) => available ? children : (
|
||||||
<Redirect to={{ pathname: '/login', state: { from: location } }} />
|
<Redirect to={{ pathname: '/login', state: { from: location } }} />
|
||||||
)}
|
)}
|
||||||
|
@ -38,8 +38,8 @@ export const EditableTable = ({
|
|||||||
dataSource,
|
dataSource,
|
||||||
onChange, // Метод вызывается со всем dataSource с измененными элементами после любого действия
|
onChange, // Метод вызывается со всем dataSource с измененными элементами после любого действия
|
||||||
onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается
|
onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается
|
||||||
onRowEdit,// Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается
|
onRowEdit, // Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается
|
||||||
onRowDelete,// Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается
|
onRowDelete, // Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается
|
||||||
...otherTableProps
|
...otherTableProps
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
@ -73,10 +73,6 @@ export const EditableTable = ({
|
|||||||
...form.initialValues,
|
...form.initialValues,
|
||||||
key:newRowKeyValue
|
key:newRowKeyValue
|
||||||
}
|
}
|
||||||
// const newRow = { key: newRowKeyValue }
|
|
||||||
|
|
||||||
// for (let column of columns)
|
|
||||||
// newRow[column.dataIndex] = form.initialValues?.[column.dataIndex] ?? column.initialValue
|
|
||||||
|
|
||||||
const newData = [newRow, ...data]
|
const newData = [newRow, ...data]
|
||||||
setData(newData)
|
setData(newData)
|
||||||
|
@ -27,15 +27,15 @@ export const makeNumericRender = (fixed?: number) => (value: any, row: object):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string ) => ({
|
export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string) => ({
|
||||||
editable: true,
|
editable: true,
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
width:100,
|
width: 100,
|
||||||
sorter: sorterKey? makeNumericSorter(sorterKey) : null,
|
sorter: sorterKey ? makeNumericSorter(sorterKey) : null,
|
||||||
formItemRules: [
|
formItemRules: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: `Введите число`,
|
message: 'Введите число',
|
||||||
pattern: RegExpIsFloat,
|
pattern: RegExpIsFloat,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -58,7 +58,7 @@ interface columnPropsOther {
|
|||||||
// массив правил валидации значений https://ant.design/components/form/#Rule
|
// массив правил валидации значений https://ant.design/components/form/#Rule
|
||||||
formItemRules?: Rule[]
|
formItemRules?: Rule[]
|
||||||
// дефолтное значение при добавлении новой строки
|
// дефолтное значение при добавлении новой строки
|
||||||
initialValue?: string|number
|
initialValue?: string | number
|
||||||
|
|
||||||
render?: (...attributes: any) => any
|
render?: (...attributes: any) => any
|
||||||
}
|
}
|
||||||
@ -71,13 +71,13 @@ export const makeColumn = (title: string | ReactNode, key: string, other?: colum
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const makeColumnsPlanFact = (
|
export const makeColumnsPlanFact = (
|
||||||
title:string | ReactNode,
|
title: string | ReactNode,
|
||||||
key:string | string[],
|
key: string | string[],
|
||||||
columsOther?: any | any[],
|
columsOther?: any | any[],
|
||||||
gruopOther?: any
|
gruopOther?: any
|
||||||
) => {
|
) => {
|
||||||
let keyPlanLocal = key
|
let keyPlanLocal: string
|
||||||
let keyFactLocal = key
|
let keyFactLocal: string
|
||||||
|
|
||||||
if (key instanceof Array) {
|
if (key instanceof Array) {
|
||||||
keyPlanLocal = key[0]
|
keyPlanLocal = key[0]
|
||||||
@ -87,29 +87,27 @@ export const makeColumnsPlanFact = (
|
|||||||
keyFactLocal = key + 'Fact'
|
keyFactLocal = key + 'Fact'
|
||||||
}
|
}
|
||||||
|
|
||||||
let columsOtherLoacl : any[2]
|
let columsOtherLocal : any[2]
|
||||||
if (columsOther instanceof Array)
|
if (columsOther instanceof Array)
|
||||||
columsOtherLoacl = [columsOther[0], columsOther[1]]
|
columsOtherLocal = [columsOther[0], columsOther[1]]
|
||||||
else
|
else
|
||||||
columsOtherLoacl = [columsOther, columsOther]
|
columsOtherLocal = [columsOther, columsOther]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: title,
|
title: title,
|
||||||
...gruopOther,
|
...gruopOther,
|
||||||
children: [
|
children: [
|
||||||
makeColumn('план', keyPlanLocal, columsOtherLoacl[0]),
|
makeColumn('план', keyPlanLocal, columsOtherLocal[0]),
|
||||||
makeColumn('факт', keyFactLocal, columsOtherLoacl[1]),
|
makeColumn('факт', keyFactLocal, columsOtherLocal[1]),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeFilterTextMatch = (key: string | number) => (filterValue: string | number, dataItem: any) =>
|
export const makeFilterTextMatch = (key: string | number) => (
|
||||||
dataItem[key] === filterValue
|
(filterValue: string | number, dataItem: any) => dataItem[key] === filterValue
|
||||||
|
)
|
||||||
|
|
||||||
export const makeGroupColumn = (title: string, children: object[]) => ({
|
export const makeGroupColumn = (title: string, children: object[]) => ({ title, children })
|
||||||
title: title,
|
|
||||||
children: children,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const makeTextColumn = (
|
export const makeTextColumn = (
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
import { Button, Modal, Select } from 'antd'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { EditableTable, makeColumn } from '../../components/Table'
|
|
||||||
|
|
||||||
export const toHexString = (num, size) => '0x' + ('0'.repeat(size) + num.toString(16).toUpperCase()).slice(-size)
|
|
||||||
|
|
||||||
const bitCount = 32
|
|
||||||
const bitOptions = [...Array(bitCount).keys()].map((n) => ({
|
|
||||||
label: toHexString(1 << n, bitCount / 4),
|
|
||||||
value: n,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
makeColumn('Бит', 'bit', {
|
|
||||||
// initialValue: bitOptions[0]?.value ?? '--',
|
|
||||||
input: <Select options={bitOptions} />,
|
|
||||||
render: (value) => bitOptions.find(option => option?.value === value)?.label ?? '--',
|
|
||||||
width: 200,
|
|
||||||
editable: true,
|
|
||||||
formItemRules: [{
|
|
||||||
required: true,
|
|
||||||
message: 'Пожалуйста, выберите бит'
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
makeColumn('Описание', 'description', {
|
|
||||||
width: 400,
|
|
||||||
editable: true,
|
|
||||||
formItemRules: [{
|
|
||||||
required: true,
|
|
||||||
message: 'Пожалуйста, введите описание'
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const PermissionBits = React.memo(({ value, onChange }) => {
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
|
||||||
const [bits, setBits] = useState([])
|
|
||||||
// const [columns, setColumns] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setBits(Object.entries(value ?? {}).map(([bit, description]) => ({ bit, description })))
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const options = bitOptions.filter((option) => !(bits?.length && bits.find((row) => row.bit === option.value)))
|
|
||||||
// const defaultValue = options?.[0]?.label ?? '--'
|
|
||||||
|
|
||||||
// setColumns([
|
|
||||||
// makeColumn('Бит', 'bit', {
|
|
||||||
// initialValue: options?.[0]?.value ?? 0,
|
|
||||||
// input: <Select options={options} />,
|
|
||||||
// render: (value) => bitOptions.find(option => option?.value === value)?.label ?? defaultValue,
|
|
||||||
// width: 200,
|
|
||||||
// editable: true,
|
|
||||||
// formItemRules: [{ required: true, message: 'Пожалуйста, выберите бит' }],
|
|
||||||
// }),
|
|
||||||
// makeColumn('Описание', 'description', {
|
|
||||||
// width: 400,
|
|
||||||
// editable: true,
|
|
||||||
// formItemRules: [{ required: true, message: 'Пожалуйста, введите описание' }],
|
|
||||||
// }),
|
|
||||||
// ])
|
|
||||||
// }, [bits])
|
|
||||||
|
|
||||||
const saveBits = () => {
|
|
||||||
const newValue = {}
|
|
||||||
bits.forEach(({bit, description}) => (newValue[bit] = description))
|
|
||||||
if(!onChange(newValue))
|
|
||||||
setIsModalVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addNewBit = (bit) => {
|
|
||||||
bit.key = Date.now()
|
|
||||||
setBits((prevBits) => [...(prevBits ?? []), bit])
|
|
||||||
}
|
|
||||||
|
|
||||||
const editBit = (bit) => {
|
|
||||||
if (!bit.key) return
|
|
||||||
const idx = bits.findIndex(v => v.key === bit.key)
|
|
||||||
if (idx < 0) return
|
|
||||||
setBits((prevBits) => {
|
|
||||||
prevBits[idx] = bit
|
|
||||||
return prevBits
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeBit = (bit) => {
|
|
||||||
if (!bit.key) return
|
|
||||||
const idx = bits.findIndex(v => v.bit === bit.bit)
|
|
||||||
if (idx < 0) return
|
|
||||||
setBits((prevBits) => {
|
|
||||||
prevBits.splice(idx, 1)
|
|
||||||
return prevBits
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button type={'link'} onClick={() => setIsModalVisible(true)}>Редактировать</Button>
|
|
||||||
<Modal
|
|
||||||
title={'Описание битов'}
|
|
||||||
centered
|
|
||||||
visible={isModalVisible}
|
|
||||||
width={750}
|
|
||||||
onCancel={() => setIsModalVisible(false)}
|
|
||||||
onOk={saveBits}
|
|
||||||
okText={'Сохранить'}
|
|
||||||
>
|
|
||||||
<EditableTable
|
|
||||||
size={'small'}
|
|
||||||
bordered
|
|
||||||
columns={columns}
|
|
||||||
dataSource={bits}
|
|
||||||
onRowAdd={addNewBit}
|
|
||||||
onRowEdit={editBit}
|
|
||||||
onRowDelete={removeBit}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PermissionBits
|
|
@ -1,60 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
|
||||||
import LoaderPortal from '../../components/LoaderPortal'
|
|
||||||
import { EditableTable, makeActionHandler, makeColumn } from '../../components/Table'
|
|
||||||
import { AdminPermissionInfoService } from '../../services/api'
|
|
||||||
import PermissionBits, { toHexString } from './PermissionBits'
|
|
||||||
|
|
||||||
export const PermissionController = () => {
|
|
||||||
const [permissions, setPermissions] = useState([])
|
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
makeColumn('Название', 'name', { width: 200, editable: true }),
|
|
||||||
makeColumn('Описание', 'description', { width: 200, editable: true }),
|
|
||||||
makeColumn('Значения битов', 'bitDescription', {
|
|
||||||
width: 200,
|
|
||||||
editable: true,
|
|
||||||
input: <PermissionBits />, // TODO: Дописать колонку для описания битов права
|
|
||||||
render: (bits) => {
|
|
||||||
if (!bits) return '--'
|
|
||||||
const sum = Object.keys(bits).reduce((sum, key) => sum + (1 << parseInt(key)), 0)
|
|
||||||
return sum && toHexString(sum, 16)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
]
|
|
||||||
|
|
||||||
const updateTable = () => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const permissions = await AdminPermissionInfoService.getAll()
|
|
||||||
setPermissions(permissions)
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
`Не удалось загрузить список прав`
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(updateTable, [])
|
|
||||||
|
|
||||||
const handlerProps = {
|
|
||||||
service: AdminPermissionInfoService,
|
|
||||||
setLoader: setShowLoader,
|
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
|
||||||
onComplete: updateTable
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={showLoader}>
|
|
||||||
<EditableTable
|
|
||||||
size={'small'}
|
|
||||||
bordered
|
|
||||||
columns={columns}
|
|
||||||
dataSource={permissions}
|
|
||||||
onRowAdd={makeActionHandler('insert', handlerProps)}
|
|
||||||
onRowEdit={makeActionHandler('update', handlerProps)}
|
|
||||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
|
||||||
/>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PermissionController
|
|
141
src/pages/AdminPanel/RoleController.jsx
Normal file
141
src/pages/AdminPanel/RoleController.jsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { Button, Modal } from 'antd'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { AdminUserRoleService } from '../../services/api'
|
||||||
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
|
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||||
|
import { EditableTable, makeActionHandler, makeColumn, makeSelectColumn } from '../../components/Table'
|
||||||
|
|
||||||
|
export const toHexString = (num, size) => '0x' + ('0'.repeat(size) + num.toString(16).toUpperCase()).slice(-size)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
makeColumn('Имена прав', 'name', {
|
||||||
|
width: 400,
|
||||||
|
editable: true,
|
||||||
|
formItemRules: [{
|
||||||
|
required: true,
|
||||||
|
message: 'Пожалуйста, введите имя права'
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const RolePermissions = ({ value, onChange }) => {
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||||
|
const [list, setList] = useState([])
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
const newValue = list.map((value) => value.name)
|
||||||
|
if(!onChange(newValue))
|
||||||
|
setIsModalVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = (permission) => {
|
||||||
|
permission.key = Date.now()
|
||||||
|
setList((prevList) => {
|
||||||
|
if (!prevList) prevList = []
|
||||||
|
prevList.push(permission)
|
||||||
|
return prevList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const edit = (permission) => {
|
||||||
|
if (!permission.key) return
|
||||||
|
const idx = list.findIndex(v => v.key === permission.key)
|
||||||
|
if (idx < 0) return
|
||||||
|
setList((prevList) => {
|
||||||
|
prevList[idx] = permission
|
||||||
|
return prevList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = (permission) => {
|
||||||
|
if (!permission.key) return
|
||||||
|
const idx = list.findIndex(v => v.key === permission.key)
|
||||||
|
if (idx < 0) return
|
||||||
|
setList((prevList) => {
|
||||||
|
prevList.splice(idx, 1)
|
||||||
|
return prevList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type={'link'} onClick={() => setIsModalVisible(true)}>Редактировать</Button>
|
||||||
|
<Modal
|
||||||
|
title={'Права доступа'}
|
||||||
|
centered
|
||||||
|
visible={isModalVisible}
|
||||||
|
width={750}
|
||||||
|
onCancel={() => setIsModalVisible(false)}
|
||||||
|
onOk={save}
|
||||||
|
okText={'Сохранить'}
|
||||||
|
>
|
||||||
|
<EditableTable
|
||||||
|
size={'small'}
|
||||||
|
bordered
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
onRowAdd={add}
|
||||||
|
onRowEdit={edit}
|
||||||
|
onRowDelete={remove}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoleController = () => {
|
||||||
|
const [roles, setRoles] = useState([])
|
||||||
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [columns, setColumns] = useState([])
|
||||||
|
|
||||||
|
const updateTable = () => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const roles = await AdminUserRoleService.getAll()
|
||||||
|
setRoles(roles)
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить список прав`
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const options = roles?.map((r) => ({ value: r.id, label: r.caption })) ?? []
|
||||||
|
setColumns([
|
||||||
|
makeColumn('Название', 'caption', { width: 200, editable: true }),
|
||||||
|
makeSelectColumn('Роль-родитель', 'idParent', options, options[0], {
|
||||||
|
width: 200,
|
||||||
|
editable: true
|
||||||
|
}),
|
||||||
|
makeColumn('Права доступа', 'permissions', {
|
||||||
|
width: 200,
|
||||||
|
editable: true,
|
||||||
|
input: <RolePermissions />,
|
||||||
|
render: (permissions) => permissions?.join(', ') ?? '',
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}, [roles])
|
||||||
|
|
||||||
|
useEffect(updateTable, [])
|
||||||
|
|
||||||
|
const handlerProps = {
|
||||||
|
service: AdminUserRoleService,
|
||||||
|
setLoader: setShowLoader,
|
||||||
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
|
onComplete: updateTable
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoaderPortal show={showLoader}>
|
||||||
|
<EditableTable
|
||||||
|
size={'small'}
|
||||||
|
bordered
|
||||||
|
columns={columns}
|
||||||
|
dataSource={roles}
|
||||||
|
onRowAdd={makeActionHandler('insert', handlerProps)}
|
||||||
|
onRowEdit={makeActionHandler('update', handlerProps)}
|
||||||
|
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||||
|
/>
|
||||||
|
</LoaderPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoleController
|
@ -2,14 +2,13 @@ import React, { Suspense } from 'react'
|
|||||||
import { Layout, Menu } from 'antd'
|
import { Layout, Menu } from 'antd'
|
||||||
import { Switch, Link, useParams, Redirect, Route } from 'react-router-dom'
|
import { Switch, Link, useParams, Redirect, Route } from 'react-router-dom'
|
||||||
import { PrivateMenuItem, PrivateRoute } from '../../components/Private'
|
import { PrivateMenuItem, PrivateRoute } from '../../components/Private'
|
||||||
import { PermissionNames, PermissionValue } from '../../utils/permissions'
|
|
||||||
|
|
||||||
const ClusterController = React.lazy(() => import('./ClusterController'))
|
const ClusterController = React.lazy(() => import('./ClusterController'))
|
||||||
const CompanyController = React.lazy(() => import('./CompanyController'))
|
const CompanyController = React.lazy(() => import('./CompanyController'))
|
||||||
const DepositController = React.lazy(() => import('./DepositController'))
|
const DepositController = React.lazy(() => import('./DepositController'))
|
||||||
const UserController = React.lazy(() => import('./UserController'))
|
const UserController = React.lazy(() => import('./UserController'))
|
||||||
const WellController = React.lazy(() => import('./WellController'))
|
const WellController = React.lazy(() => import('./WellController'))
|
||||||
const PermissionController = React.lazy(() => import('./PermissionController'))
|
const RoleController = React.lazy(() => import('./RoleController'))
|
||||||
|
|
||||||
export const AdminPanel = () => {
|
export const AdminPanel = () => {
|
||||||
const { tab } = useParams()
|
const { tab } = useParams()
|
||||||
@ -17,28 +16,24 @@ export const AdminPanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Menu
|
<Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
||||||
mode={'horizontal'}
|
<PrivateMenuItem key={'deposit'} permission={'deposit_editor'}>
|
||||||
selectable={true}
|
|
||||||
selectedKeys={[tab]}
|
|
||||||
>
|
|
||||||
<PrivateMenuItem key={'deposit'} permissions={[[PermissionNames.admin.deposit, PermissionValue.Read]]}>
|
|
||||||
<Link to={`${rootPath}/deposit`}>Месторождения</Link>
|
<Link to={`${rootPath}/deposit`}>Месторождения</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
<PrivateMenuItem key={'cluster'} permissions={[[PermissionNames.admin.cluster, PermissionValue.Read]]}>
|
<PrivateMenuItem key={'cluster'} permission={'cluster_editor'}>
|
||||||
<Link to={`${rootPath}/cluster`}>Кусты</Link>
|
<Link to={`${rootPath}/cluster`}>Кусты</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
<PrivateMenuItem key={'well'} permissions={[[PermissionNames.admin.well, PermissionValue.Read]]}>
|
<PrivateMenuItem key={'well'} permission={'well_editor'}>
|
||||||
<Link to={`${rootPath}/well`}>Скважины</Link>
|
<Link to={`${rootPath}/well`}>Скважины</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
<PrivateMenuItem key={'user'} permissions={[[PermissionNames.admin.user, PermissionValue.Read]]}>
|
<PrivateMenuItem key={'user'} permission={'user_editor'}>
|
||||||
<Link to={`${rootPath}/user`}>Пользователи</Link>
|
<Link to={`${rootPath}/user`}>Пользователи</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
<PrivateMenuItem key={'company'} permissions={[[PermissionNames.admin.company, PermissionValue.Read]]}>
|
<PrivateMenuItem key={'company'} permission={'company_editor'}>
|
||||||
<Link to={`${rootPath}/company`}>Компании</Link>
|
<Link to={`${rootPath}/company`}>Компании</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
<PrivateMenuItem key={'permission'} permissions={[[PermissionNames.admin.permissions, PermissionValue.Read]]}>
|
<PrivateMenuItem key={'role'} permission={'role_editor'}>
|
||||||
<Link to={`${rootPath}/permission`}>Права</Link>
|
<Link to={`${rootPath}/role`}>Права</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
@ -46,12 +41,12 @@ export const AdminPanel = () => {
|
|||||||
<Layout.Content className={'site-layout-background'}>
|
<Layout.Content className={'site-layout-background'}>
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<PrivateRoute roles={['deposit_admin']} path={`${rootPath}/deposit`} component={DepositController} />
|
<PrivateRoute permission={'deposit_editor'} path={`${rootPath}/deposit`} component={DepositController} />
|
||||||
<PrivateRoute roles={['cluster_admin']} path={`${rootPath}/cluster`} component={ClusterController} />
|
<PrivateRoute permission={'cluster_editor'} path={`${rootPath}/cluster`} component={ClusterController} />
|
||||||
<PrivateRoute roles={[ 'well_admin']} path={`${rootPath}/well` } component={ WellController} />
|
<PrivateRoute permission={ 'well_editor'} path={`${rootPath}/well` } component={ WellController} />
|
||||||
<PrivateRoute roles={[ 'user_admin']} path={`${rootPath}/user` } component={ UserController} />
|
<PrivateRoute permission={ 'user_editor'} path={`${rootPath}/user` } component={ UserController} />
|
||||||
<PrivateRoute roles={['company_admin']} path={`${rootPath}/company`} component={CompanyController} />
|
<PrivateRoute permission={'company_editor'} path={`${rootPath}/company`} component={CompanyController} />
|
||||||
<PrivateRoute path={`${rootPath}/permission`} component={PermissionController} />
|
<PrivateRoute permission={ 'role_editor'} path={`${rootPath}/role` } component={ RoleController} />
|
||||||
<Route path={'/'}>
|
<Route path={'/'}>
|
||||||
<Redirect to={`${rootPath}/deposit`}/>
|
<Redirect to={`${rootPath}/deposit`}/>
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -1,59 +1,50 @@
|
|||||||
//import { Menu } from "antd";
|
import path from 'path'
|
||||||
import { FolderOutlined } from "@ant-design/icons";
|
import { FolderOutlined } from '@ant-design/icons'
|
||||||
import { Link, Route} from "react-router-dom";
|
import { Link, Route } from 'react-router-dom'
|
||||||
|
import { PrivateMenuItem } from '../../components/Private'
|
||||||
|
import { getUserRoles, isInRole } from "../../utils/PermissionService";
|
||||||
import DocumentsTemplate from './DocumentsTemplate'
|
import DocumentsTemplate from './DocumentsTemplate'
|
||||||
import {PrivateMenuItem} from '../../components/Private'
|
|
||||||
|
|
||||||
export const documentCategories = [
|
export const documentCategories = [
|
||||||
{id:1, key:'fluidService', title:'Растворный сервис'/*, roles:['ff']*/},
|
{ id: 1, key: 'fluidService', title: 'Растворный сервис' },
|
||||||
{id:2, key:'cementing', title:'Цементирование'},
|
{ id: 2, key: 'cementing', title: 'Цементирование' },
|
||||||
{id:3, key:'nnb', title:'ННБ'},
|
{ id: 3, key: 'nnb', title: 'ННБ' },
|
||||||
{id:4, key:'gti', title:'ГТИ'},
|
{ id: 4, key: 'gti', title: 'ГТИ' },
|
||||||
{id:5, key:'documentsForWell', title:'Документы по скважине'},
|
{ id: 5, key: 'documentsForWell', title: 'Документы по скважине' },
|
||||||
{id:6, key:'supervisor', title:'Супервайзер'},
|
{ id: 6, key: 'supervisor', title: 'Супервайзер' },
|
||||||
{id:7, key:'master', title:'Мастер'},
|
{ id: 7, key: 'master', title: 'Мастер' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const makeMenuItem = (keyValue, rootPath, title, other) => (
|
const makeMenuItem = (keyValue, rootPath, title, other) => (
|
||||||
<PrivateMenuItem className="ant-menu-item"
|
<PrivateMenuItem className={'ant-menu-item'} key={`${keyValue}`} {...other}>
|
||||||
key={`${keyValue}`}
|
|
||||||
{...other}>
|
|
||||||
<Link to={{pathname: `${rootPath}/${keyValue}`}}>{title}</Link>
|
<Link to={{pathname: `${rootPath}/${keyValue}`}}>{title}</Link>
|
||||||
</PrivateMenuItem>)
|
</PrivateMenuItem>
|
||||||
|
)
|
||||||
|
|
||||||
const makeRouteItem = (keyValue, rootPath, other) => (
|
const makeRouteItem = (keyValue, rootPath, other) => (
|
||||||
<Route
|
<Route
|
||||||
path={`${rootPath}/${keyValue}`}
|
path={`${rootPath}/${keyValue}`}
|
||||||
key={`${keyValue}`}>
|
key={`${keyValue}`}
|
||||||
|
>
|
||||||
<DocumentsTemplate {...other}/>
|
<DocumentsTemplate {...other}/>
|
||||||
</Route>)
|
</Route>
|
||||||
|
)
|
||||||
|
|
||||||
const formatRoutePath = (rootPath) =>{
|
const getCategoriesByUserRole = (role) => documentCategories.filter(cat => isInRole(cat.roles))
|
||||||
let root = rootPath.endsWith('/')
|
|
||||||
? rootPath.slice(0,-1)
|
|
||||||
: rootPath
|
|
||||||
|
|
||||||
root = root.endsWith('/document')
|
|
||||||
? root
|
|
||||||
: `${root}/document`
|
|
||||||
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCategoriesByUserRole = (role) => documentCategories
|
|
||||||
.filter(cat => !cat.roles || cat.roles.includes('*') || cat.roles.includes(role))
|
|
||||||
|
|
||||||
export const makeMenuItems = (rootPath) => {
|
export const makeMenuItems = (rootPath) => {
|
||||||
const root = formatRoutePath(rootPath)
|
const root = path.join(rootPath, '/document')
|
||||||
const categories = getCategoriesByUserRole(localStorage['roleName'])
|
const categories = getCategoriesByUserRole(getUserRoles())
|
||||||
return categories.map(category =>
|
return categories.map(category =>
|
||||||
makeMenuItem(category.key, root, category.title, {icon:<FolderOutlined/>}))
|
makeMenuItem(category.key, root, category.title, {icon:<FolderOutlined/>}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeRouteItems = (rootPath, idWell) => {
|
export const makeRouteItems = (rootPath, idWell) => {
|
||||||
const root = formatRoutePath(rootPath)
|
const root = path.join(rootPath, '/document')
|
||||||
const categories = getCategoriesByUserRole(localStorage['roleName'])
|
const categories = getCategoriesByUserRole(getUserRoles())
|
||||||
const routes = categories.map(category =>
|
const routes = categories.map(category => makeRouteItem(category.key, root, {
|
||||||
makeRouteItem(category.key, root, {idCategory: category.id, idWell: idWell}))
|
idCategory: category.id,
|
||||||
return routes;
|
idWell: idWell
|
||||||
|
}))
|
||||||
|
return routes
|
||||||
}
|
}
|
62
src/utils/PermissionService.ts
Normal file
62
src/utils/PermissionService.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { getArrayFromLocalStorage } from './storage'
|
||||||
|
|
||||||
|
export type Role = string
|
||||||
|
export type Permission = string
|
||||||
|
|
||||||
|
export const getUserRoles = (): Role[] => getArrayFromLocalStorage<Role>('roles') ?? []
|
||||||
|
export const getUserPermissions = (): Permission[] =>
|
||||||
|
getArrayFromLocalStorage<Permission>('permissions') ?? []
|
||||||
|
|
||||||
|
export const hasPermission = (permission?: Permission): boolean => {
|
||||||
|
if (typeof permission !== 'string') return true
|
||||||
|
return permission in getUserPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInRole = (roles?: Role[] | Role): boolean => {
|
||||||
|
if (typeof roles === 'string' && !Array.isArray(roles))
|
||||||
|
roles = [roles]
|
||||||
|
if (!roles?.length) return true
|
||||||
|
|
||||||
|
if (localStorage.getItem('login') === 'dev') return true // TODO: Удалить строку
|
||||||
|
|
||||||
|
const user_roles = getUserRoles()
|
||||||
|
return roles.some((role) => role in user_roles)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
deposit
|
||||||
|
cluster
|
||||||
|
well (R)
|
||||||
|
well/archive (R)
|
||||||
|
well/message (R)
|
||||||
|
well/report (R)
|
||||||
|
well/measure (RW)
|
||||||
|
well/drillingProgram (RU согласовать)
|
||||||
|
well/telemetry (R)
|
||||||
|
well/telemetry:status (RW)
|
||||||
|
well/telemetry:rop (RW)
|
||||||
|
well/operations (R)
|
||||||
|
well/operations/tvd (R)
|
||||||
|
well/operations/sections (R)
|
||||||
|
well/operations/plan (RW)
|
||||||
|
well/operations/fact (RW)
|
||||||
|
well/operations/drillProccesFlow (RW)
|
||||||
|
well/operations/params (RW)
|
||||||
|
well/operations/composite (R)
|
||||||
|
well/operations/composite/wells (R)
|
||||||
|
well/operations/composite/sections (RW)
|
||||||
|
well/document (R)
|
||||||
|
well/document/fluidService (RU)
|
||||||
|
well/document/cementing (RU)
|
||||||
|
well/document/nnb (RU)
|
||||||
|
well/document/gti (RU)
|
||||||
|
well/document/documentsForWell (RU)
|
||||||
|
well/document/supervisor (RU)
|
||||||
|
well/document/master (RU)
|
||||||
|
admin (R)
|
||||||
|
admin/deposit (RAED)
|
||||||
|
admin/cluster (RAED)
|
||||||
|
admin/well (RAED)
|
||||||
|
admin/user (RAED)
|
||||||
|
admin/company (RAED)
|
||||||
|
*/
|
@ -1,140 +0,0 @@
|
|||||||
export type Role = string
|
|
||||||
export type PermissionName = string
|
|
||||||
export enum PermissionValue {
|
|
||||||
Nothing = 0,
|
|
||||||
Read = 1 << 0,
|
|
||||||
Write = 1 << 1,
|
|
||||||
Aprove = 1 << 1,
|
|
||||||
Update = 1 << 1,
|
|
||||||
Insert = 1 << 2,
|
|
||||||
Delete = 1 << 3,
|
|
||||||
}
|
|
||||||
export type PermissionMixingType = 'and' | 'or'
|
|
||||||
export type Permissions = [PermissionName, PermissionValue][]
|
|
||||||
|
|
||||||
export const PermissionNames = {
|
|
||||||
deposit: 'deposit',
|
|
||||||
cluster: 'cluster',
|
|
||||||
well: {
|
|
||||||
_self: 'well',
|
|
||||||
archive: 'well/archive',
|
|
||||||
measure: 'well/measure',
|
|
||||||
message: 'well/message',
|
|
||||||
report: 'well/report',
|
|
||||||
drillingProgram: 'well/drillingProgram',
|
|
||||||
telemetry: {
|
|
||||||
_self: 'well/telemetry',
|
|
||||||
status: 'well/telemetry/status',
|
|
||||||
rop: 'well/telemetry/rop',
|
|
||||||
},
|
|
||||||
operations: {
|
|
||||||
_self: 'well/operations',
|
|
||||||
tvd: 'well/operations/tvd',
|
|
||||||
sections: 'well/operations/sections',
|
|
||||||
plan: 'well/operations/plan',
|
|
||||||
fact: 'well/operations/fact',
|
|
||||||
drillProccesFlow: 'well/operations/drillProccesFlow',
|
|
||||||
params: 'well/operations/params',
|
|
||||||
composite: {
|
|
||||||
_self: 'well/operations/composite',
|
|
||||||
wells: 'well/operations/composite/wells',
|
|
||||||
sections: 'well/operations/composite/sections',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
document: {
|
|
||||||
_self: 'well/document',
|
|
||||||
fluidService: 'well/document/fluidService',
|
|
||||||
cementing: 'well/document/cementing',
|
|
||||||
nnb: 'well/document/nnb',
|
|
||||||
gti: 'well/document/gti',
|
|
||||||
documentsForWell: 'well/document/documentsForWell',
|
|
||||||
supervisor: 'well/document/supervisor',
|
|
||||||
master: 'well/document/master'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
_self: 'admin',
|
|
||||||
deposit: 'admin/deposit',
|
|
||||||
cluster: 'admin/cluster',
|
|
||||||
well: 'admin/well',
|
|
||||||
user: 'admin/user',
|
|
||||||
company: 'admin/company',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const admins: Role[] = ['администратор', 'админ', 'admin']
|
|
||||||
|
|
||||||
export const hasPermissions = (permissions?: Permissions): boolean => {
|
|
||||||
if (!permissions?.length)
|
|
||||||
return true
|
|
||||||
return true // TODO: Написать проверку на доступ через права
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isInRole = (roles?: Role[]): boolean => {
|
|
||||||
if (localStorage['login'] === 'dev') return true // TODO: Удалить строку
|
|
||||||
if (!roles?.length)
|
|
||||||
return true
|
|
||||||
// TODO: Переписать проверку на доступ через роли
|
|
||||||
const role: Role = localStorage['roleName']?.toLowerCase()
|
|
||||||
if (admins.indexOf(role) > -1)
|
|
||||||
return true
|
|
||||||
for (const r of roles)
|
|
||||||
if (r.toLowerCase() === role)
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export type hasAccessProps = {
|
|
||||||
permissions?: Permissions
|
|
||||||
roles?: Role[]
|
|
||||||
mixing?: PermissionMixingType
|
|
||||||
}
|
|
||||||
|
|
||||||
export const hasAccess = ({permissions, roles, mixing}: hasAccessProps): boolean => {
|
|
||||||
switch (mixing) {
|
|
||||||
case 'or':
|
|
||||||
return hasPermissions(permissions) || isInRole(roles)
|
|
||||||
case 'and':
|
|
||||||
default:
|
|
||||||
return hasPermissions(permissions) && isInRole(roles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
deposit
|
|
||||||
cluster
|
|
||||||
well (R)
|
|
||||||
well/archive (R)
|
|
||||||
well/message (R)
|
|
||||||
well/report (R)
|
|
||||||
well/measure (RW)
|
|
||||||
well/drillingProgram (RU согласовать)
|
|
||||||
well/telemetry (R)
|
|
||||||
well/telemetry:status (RW)
|
|
||||||
well/telemetry:rop (RW)
|
|
||||||
well/operations (R)
|
|
||||||
well/operations/tvd (R)
|
|
||||||
well/operations/sections (R)
|
|
||||||
well/operations/plan (RW)
|
|
||||||
well/operations/fact (RW)
|
|
||||||
well/operations/drillProccesFlow (RW)
|
|
||||||
well/operations/params (RW)
|
|
||||||
well/operations/composite (R)
|
|
||||||
well/operations/composite/wells (R)
|
|
||||||
well/operations/composite/sections (RW)
|
|
||||||
well/document (R)
|
|
||||||
well/document/fluidService (RU)
|
|
||||||
well/document/cementing (RU)
|
|
||||||
well/document/nnb (RU)
|
|
||||||
well/document/gti (RU)
|
|
||||||
well/document/documentsForWell (RU)
|
|
||||||
well/document/supervisor (RU)
|
|
||||||
well/document/master (RU)
|
|
||||||
admin (R)
|
|
||||||
admin/deposit (RAED)
|
|
||||||
admin/cluster (RAED)
|
|
||||||
admin/well (RAED)
|
|
||||||
admin/user (RAED)
|
|
||||||
admin/company (RAED)
|
|
||||||
*/
|
|
5
src/utils/storage.ts
Normal file
5
src/utils/storage.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const getArrayFromLocalStorage = <T extends string = string>(name: string, sep: string | RegExp = ','): T[] | null => {
|
||||||
|
const raw = localStorage.getItem(name)
|
||||||
|
if (!raw) return null
|
||||||
|
return raw.split(sep).map<T>(elm => elm as T)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user