Частично реализовано взаимодействите с правами пользователя;

Для компоненов PrivateContent, PrivateMenuItem и PrivateRoute можно указывать требуемые права и/или роли и способ их совмещения;
Добавлена ленивая подгрузка страниц для тестирования;
This commit is contained in:
goodmice 2021-12-07 19:45:13 +05:00
parent 741449fb99
commit c6d3814313
6 changed files with 200 additions and 74 deletions

View File

@ -1,28 +1,13 @@
export type Role = string import React from 'react'
import { Role, Permissions, hasAccess, PermissionMixingType } from '../../utils/permissions'
const admins: Role[] = ['администратор', 'админ', 'admin']
export const isInRole = (roles?: Role[]): boolean => {
// if (localStorage['login'] === 'dev')
// return true
if(!roles?.length)
return true
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 const privateContent = <T extends unknown>(roles?: Role[], children?: T): T | null | undefined => {
return isInRole(roles) ? children : null
}
type PrivateContentProps = { type PrivateContentProps = {
roles?: Role[] permissions?: Permissions
children?: any roles?: Role[]
mixing?: PermissionMixingType
children?: any
} }
export const PrivateContent: React.FC<PrivateContentProps> = ({ roles, children }) => privateContent(roles, children) export const PrivateContent: React.FC<PrivateContentProps> = ({ permissions, roles, mixing, children }) => {
return (hasAccess({ permissions, roles, mixing }) && children) || null
}

View File

@ -1,12 +1,15 @@
import React from 'react' import React from 'react'
import { Menu } from 'antd' import { Menu } from 'antd'
import { isInRole, Role } from './PrivateContent' import { hasAccess, Role, Permissions, PermissionMixingType } from '../../utils/permissions'
type PrivateMenuItemProps = { type PrivateMenuItemProps = {
roles: Role[] roles?: Role[]
[props: string]: any permissions?: Permissions
mixing?: PermissionMixingType
[props: string]: any
} }
export const PrivateMenuItem: React.FC<PrivateMenuItemProps> = ({ roles, ...props }) => { export const PrivateMenuItem: React.FC<PrivateMenuItemProps> = ({ roles, permissions, mixing, ...props }) => {
return isInRole(roles) ? <Menu.Item {...props}/> : null return hasAccess({ permissions, roles, mixing }) ? <Menu.Item {...props}/> : null
} }

View File

@ -1,21 +1,24 @@
import React from 'react' import React from 'react'
import { Route, Redirect } from 'react-router-dom' import { Route, Redirect } from 'react-router-dom'
import { isInRole, Role } from './PrivateContent' import { Role, Permissions, hasAccess } from '../../utils/permissions'
type PrivateRouteProps = { type PrivateRouteProps = {
children: any permissions?: Permissions
roles: Role[] roles: Role[]
[other: string]: any component?: any
children?: any
[other: string]: any
} }
export const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, roles, ...other }) => { export const PrivateRoute: React.FC<PrivateRouteProps> = ({ permissions, roles, component, children, ...other }) => {
const token = localStorage['token'] const available = localStorage['token'] && hasAccess({ permissions, roles })
return (
<Route return (
{...other} <Route {...other}
render={({ location }) => token && isInRole(roles) ? children : ( component={available && component}
<Redirect to={{ pathname: "/login", state: { from: location } }} />) render={({ location }) => available ? children : (
} <Redirect to={{ pathname: '/login', state: { from: location } }} />
/> )}
) />
)
} }

View File

@ -1,3 +1,3 @@
export { PrivateRoute } from './PrivateRoute' export { PrivateRoute } from './PrivateRoute'
export { privateContent, PrivateContent } from './PrivateContent' export { PrivateContent } from './PrivateContent'
export { PrivateMenuItem } from './PrivateMenuItem' export { PrivateMenuItem } from './PrivateMenuItem'

View File

@ -1,11 +1,14 @@
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 { ClusterController } from './ClusterController' import { PermissionNames, PermissionValue } from '../../utils/permissions'
import { CompanyController } from './CompanyController'
import { DepositController } from './DepositController' const ClusterController = React.lazy(() => import('./ClusterController'))
import { UserController } from './UserController' const CompanyController = React.lazy(() => import('./CompanyController'))
import { WellController } from './WellController' const DepositController = React.lazy(() => import('./DepositController'))
const UserController = React.lazy(() => import('./UserController'))
const WellController = React.lazy(() => import('./WellController'))
export const AdminPanel = () => { export const AdminPanel = () => {
const { tab } = useParams() const { tab } = useParams()
@ -18,45 +21,37 @@ export const AdminPanel = () => {
selectable={true} selectable={true}
selectedKeys={[tab]} selectedKeys={[tab]}
> >
<PrivateMenuItem roles={['deposit_admin']} key={'deposit'}> <PrivateMenuItem key={'deposit'} permissions={[[PermissionNames.admin.deposit, PermissionValue.Read]]}>
<Link to={`${rootPath}/deposit`}>Месторождения</Link> <Link to={`${rootPath}/deposit`}>Месторождения</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem roles={['cluster_admin']} key={'cluster'}> <PrivateMenuItem key={'cluster'} permissions={[[PermissionNames.admin.cluster, PermissionValue.Read]]}>
<Link to={`${rootPath}/cluster`}>Кусты</Link> <Link to={`${rootPath}/cluster`}>Кусты</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem roles={['well_admin']} key={'well'}> <PrivateMenuItem key={'well'} permissions={[[PermissionNames.admin.well, PermissionValue.Read]]}>
<Link to={`${rootPath}/well`}>Скважины</Link> <Link to={`${rootPath}/well`}>Скважины</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem roles={['user_admin']} key={'user'}> <PrivateMenuItem key={'user'} permissions={[[PermissionNames.admin.user, PermissionValue.Read]]}>
<Link to={`${rootPath}/user`}>Пользователи</Link> <Link to={`${rootPath}/user`}>Пользователи</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem roles={['company_admin']} key={'company'}> <PrivateMenuItem key={'company'} permissions={[[PermissionNames.admin.company, PermissionValue.Read]]}>
<Link to={`${rootPath}/company`}>Компании</Link> <Link to={`${rootPath}/company`}>Компании</Link>
</PrivateMenuItem> </PrivateMenuItem>
</Menu> </Menu>
<Layout> <Layout>
<Layout.Content className={'site-layout-background'}> <Layout.Content className={'site-layout-background'}>
<Switch> <Suspense fallback={<div>Loading...</div>}>
<PrivateRoute roles={['deposit_admin']} path={`${rootPath}/deposit`}> <Switch>
<DepositController /> <PrivateRoute roles={['deposit_admin']} path={`${rootPath}/deposit`} component={DepositController} />
</PrivateRoute> <PrivateRoute roles={['cluster_admin']} path={`${rootPath}/cluster`} component={ClusterController} />
<PrivateRoute roles={['cluster_admin']} path={`${rootPath}/cluster`}> <PrivateRoute roles={[ 'well_admin']} path={`${rootPath}/well` } component={ WellController} />
<ClusterController /> <PrivateRoute roles={[ 'user_admin']} path={`${rootPath}/user` } component={ UserController} />
</PrivateRoute> <PrivateRoute roles={['company_admin']} path={`${rootPath}/company`} component={CompanyController} />
<PrivateRoute roles={['well_admin']} path={`${rootPath}/well`}> <Route path={'/'}>
<WellController /> <Redirect to={`${rootPath}/deposit`}/>
</PrivateRoute> </Route>
<PrivateRoute roles={['user_admin']} path={`${rootPath}/user`}> </Switch>
<UserController /> </Suspense>
</PrivateRoute>
<PrivateRoute roles={['company_admin']} path={`${rootPath}/company`}>
<CompanyController />
</PrivateRoute>
<Route path={'/'}>
<Redirect to={`${rootPath}/deposit`}/>
</Route>
</Switch>
</Layout.Content> </Layout.Content>
</Layout> </Layout>
</Layout> </Layout>

140
src/utils/permissions.ts Normal file
View File

@ -0,0 +1,140 @@
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)
*/