diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index e6a89fd1a..d3c81cefd 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -41,20 +41,23 @@ export const rawDataToServerObject = (data: any): Server => ({ port: data.sftp_details.port, }, description: data.description ? ((data.description.length > 0) ? data.description : null) : null, - allocations: [{ + allocations: [ { ip: data.allocation.ip, alias: null, port: data.allocation.port, default: true, - }], + } ], limits: { ...data.limits }, featureLimits: { ...data.feature_limits }, }); -export default (uuid: string): Promise => { +export default (uuid: string): Promise<[ Server, string[] ]> => { return new Promise((resolve, reject) => { http.get(`/api/client/servers/${uuid}`) - .then(response => resolve(rawDataToServerObject(response.data.attributes))) + .then(({ data }) => resolve([ + rawDataToServerObject(data.attributes), + data.meta?.is_server_owner ? ['*'] : (data.meta?.user_permissions || []), + ])) .catch(reject); }); }; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx new file mode 100644 index 000000000..87f67e0a8 --- /dev/null +++ b/resources/scripts/components/elements/Can.tsx @@ -0,0 +1,44 @@ +import React, { useMemo } from 'react'; +import { ServerContext } from '@/state/server'; + +interface Props { + action: string | string[]; + renderOnError?: React.ReactNode | null; + children: React.ReactNode; +} + +const Can = ({ action, renderOnError, children }: Props) => { + const userPermissions = ServerContext.useStoreState(state => state.server.permissions); + const actions = Array.isArray(action) ? action : [ action ]; + + const missingPermissionCount = useMemo(() => { + if (userPermissions[0] === '*') { + return 0; + } + + return actions.filter(permission => { + return !( + // Allows checking for any permission matching a name, for example files.* + // will return if the user has any permission under the file.XYZ namespace. + ( + permission.endsWith('.*') + && permission !== 'websocket.*' + && userPermissions.filter(p => p.startsWith(permission.split('.')[0])).length > 0 + ) + // Otherwise just check if the entire permission exists in the array or not. + || userPermissions.indexOf(permission) >= 0); + }).length; + }, [ action, userPermissions ]); + + return ( + <> + {missingPermissionCount > 0 ? + renderOnError + : + children + } + + ); +}; + +export default Can; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 8735c1401..ff7fe73ec 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -15,6 +15,7 @@ import SettingsContainer from '@/components/server/settings/SettingsContainer'; import ScheduleContainer from '@/components/server/schedules/ScheduleContainer'; import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer'; import UsersContainer from '@/components/server/users/UsersContainer'; +import Can from '@/components/elements/Can'; const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { const server = ServerContext.useStoreState(state => state.server.data); @@ -34,11 +35,21 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
Console - File Manager - Databases - Schedules - Users - Settings + + File Manager + + + Databases + + + Schedules + + + Users + + + Settings +
diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 8fc23aee5..fb26a7167 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -10,18 +10,30 @@ export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; interface ServerDataStore { data?: Server; + permissions: string[]; + getServer: Thunk>; setServer: Action; + setPermissions: Action; } const server: ServerDataStore = { + permissions: [], + getServer: thunk(async (actions, payload) => { - const server = await getServer(payload); + const [server, permissions] = await getServer(payload); + actions.setServer(server); + actions.setPermissions(permissions); }), + setServer: action((state, payload) => { state.data = payload; }), + + setPermissions: action((state, payload) => { + state.permissions = payload; + }), }; interface ServerStatusStore { @@ -75,9 +87,9 @@ export const ServerContext = createContextStore({ subusers, clearServerState: action(state => { state.server.data = undefined; + state.server.permissions = []; state.databases.items = []; state.subusers.data = []; - state.files.directory = '/'; state.files.contents = [];