portainer/app/react/portainer/access-control/AccessControlPanel/AccessControlPanelDetails.tsx

219 lines
6.2 KiB
TypeScript

import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import _ from 'lodash';
import { Info } from 'lucide-react';
import { ownershipIcon, truncate } from '@/portainer/filters/filters';
import { UserId } from '@/portainer/users/types';
import { TeamId } from '@/react/portainer/users/teams/types';
import { useTeams } from '@/react/portainer/users/teams/queries';
import { useUsers } from '@/portainer/users/queries';
import { Link } from '@@/Link';
import { Tooltip } from '@@/Tip/Tooltip';
import { Icon } from '@@/Icon';
import {
ResourceControlOwnership,
ResourceControlType,
ResourceId,
} from '../types';
import { ResourceControlViewModel } from '../models/ResourceControlViewModel';
interface Props {
resourceControl?: ResourceControlViewModel;
resourceType: ResourceControlType;
}
export function AccessControlPanelDetails({
resourceControl,
resourceType,
}: Props) {
const inheritanceMessage = getInheritanceMessage(
resourceType,
resourceControl
);
const {
Ownership: ownership = ResourceControlOwnership.ADMINISTRATORS,
UserAccesses: restrictedToUsers = [],
TeamAccesses: restrictedToTeams = [],
} = resourceControl || {};
const users = useAuthorizedUsers(restrictedToUsers.map((ra) => ra.UserId));
const teams = useAuthorizedTeams(restrictedToTeams.map((ra) => ra.TeamId));
return (
<table className="table">
<tbody>
<tr data-cy="access-ownership">
<td className="w-1/5">Ownership</td>
<td>
<i
className={clsx(ownershipIcon(ownership), 'space-right')}
aria-hidden="true"
aria-label="ownership-icon"
/>
<span aria-label="ownership">{ownership}</span>
<Tooltip message={getOwnershipTooltip(ownership)} />
</td>
</tr>
{inheritanceMessage}
{restrictedToUsers.length > 0 && (
<tr data-cy="access-authorisedUsers">
<td>Authorized users</td>
<td aria-label="authorized-users">
{users.data && users.data.join(', ')}
</td>
</tr>
)}
{restrictedToTeams.length > 0 && (
<tr data-cy="access-authorisedTeams">
<td>Authorized teams</td>
<td aria-label="authorized-teams">
{teams.data && teams.data.join(', ')}
</td>
</tr>
)}
</tbody>
</table>
);
}
function getOwnershipTooltip(ownership: ResourceControlOwnership) {
switch (ownership) {
case ResourceControlOwnership.PRIVATE:
return 'Management of this resource is restricted to a single user.';
case ResourceControlOwnership.RESTRICTED:
return 'This resource can be managed by a restricted set of users and/or teams.';
case ResourceControlOwnership.PUBLIC:
return 'This resource can be managed by any user with access to this environment.';
case ResourceControlOwnership.ADMINISTRATORS:
default:
return 'This resource can only be managed by administrators.';
}
}
function getInheritanceMessage(
resourceType: ResourceControlType,
resourceControl?: ResourceControlViewModel
) {
if (!resourceControl || resourceControl.Type === resourceType) {
return null;
}
const parentType = resourceControl.Type;
const resourceId = resourceControl.ResourceId;
if (
resourceType === ResourceControlType.Container &&
parentType === ResourceControlType.Service
) {
return (
<InheritanceMessage tooltip="Access control applied on a service is also applied on each container of that service.">
Access control on this resource is inherited from the following service:
<Link to="docker.services.service" params={{ id: resourceId }}>
{truncate(resourceId)}
</Link>
</InheritanceMessage>
);
}
if (
resourceType === ResourceControlType.Volume &&
parentType === ResourceControlType.Container
) {
return (
<InheritanceMessage tooltip="Access control applied on a container created using a template is also applied on each volume associated to the container.">
Access control on this resource is inherited from the following
container:
<Link to="docker.containers.container" params={{ id: resourceId }}>
{truncate(resourceId)}
</Link>
</InheritanceMessage>
);
}
if (parentType === ResourceControlType.Stack) {
return (
<InheritanceMessage tooltip="Access control applied on a stack is also applied on each resource in the stack.">
<span className="space-right">
Access control on this resource is inherited from the following stack:
</span>
{removeEndpointIdFromStackResourceId(resourceId)}
</InheritanceMessage>
);
}
return null;
}
function removeEndpointIdFromStackResourceId(stackName: ResourceId) {
if (!stackName || typeof stackName !== 'string') {
return stackName;
}
const firstUnderlineIndex = stackName.indexOf('_');
if (firstUnderlineIndex < 0) {
return stackName;
}
return stackName.substring(firstUnderlineIndex + 1);
}
interface InheritanceMessageProps {
tooltip: string;
}
function InheritanceMessage({
children,
tooltip,
}: PropsWithChildren<InheritanceMessageProps>) {
return (
<tr>
<td colSpan={2} aria-label="inheritance-message">
<Icon icon={Info} mode="primary" className="mr-1" />
{children}
<Tooltip message={tooltip} />
</td>
</tr>
);
}
function useAuthorizedTeams(authorizedTeamIds: TeamId[]) {
return useTeams(false, 0, {
enabled: authorizedTeamIds.length > 0,
select: (teams) => {
if (authorizedTeamIds.length === 0) {
return [];
}
return _.compact(
authorizedTeamIds.map((id) => {
const team = teams.find((u) => u.Id === id);
return team?.Name;
})
);
},
});
}
function useAuthorizedUsers(authorizedUserIds: UserId[], enabled = true) {
return useUsers(
false,
0,
authorizedUserIds.length > 0 && enabled,
(users) => {
if (authorizedUserIds.length === 0) {
return [];
}
return _.compact(
authorizedUserIds.map((id) => {
const user = users.find((u) => u.Id === id);
return user?.Username;
})
);
}
);
}