import clsx from 'clsx'; import { PropsWithChildren } from 'react'; import _ from 'lodash'; import { Info } from 'lucide-react'; import { 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 { pluralize } from '@/portainer/helpers/strings'; import { ownershipIcon } from '@/react/docker/components/datatable/createOwnershipColumn'; 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; isAuthorisedToFetchUsers?: boolean; resourceName?: string; } export function AccessControlPanelDetails({ resourceControl, resourceType, isAuthorisedToFetchUsers = false, resourceName = 'resource', }: Props) { const inheritanceMessage = getInheritanceMessage( resourceType, resourceControl ); const { Ownership: ownership = ResourceControlOwnership.ADMINISTRATORS, UserAccesses: restrictedToUsers = [], TeamAccesses: restrictedToTeams = [], } = resourceControl || {}; const users = useAuthorizedUsers( restrictedToUsers.map((ra) => ra.UserId), isAuthorisedToFetchUsers ); const teams = useAuthorizedTeams(restrictedToTeams.map((ra) => ra.TeamId)); const teamsLength = teams.data ? teams.data.length : 0; const unauthoisedTeams = restrictedToTeams.length - teamsLength; let teamsMessage = teams.data && teams.data.join(', '); if (unauthoisedTeams > 0 && teams.isFetched) { teamsMessage += teamsLength > 0 ? ' and' : ''; teamsMessage += ` ${unauthoisedTeams} ${pluralize( unauthoisedTeams, 'team' )} you are not part of`; } const userMessage = users.data ? users.data.join(', ') : `${restrictedToUsers.length} ${pluralize( restrictedToUsers.length, 'user' )}`; return ( {inheritanceMessage} {restrictedToUsers.length > 0 && ( )} {restrictedToTeams.length > 0 && ( )}
Ownership
Authorized users {userMessage}
Authorized teams {teamsMessage}
); } function getOwnershipTooltip( ownership: ResourceControlOwnership, resourceName: string ) { switch (ownership) { case ResourceControlOwnership.PRIVATE: return `Management of this ${resourceName} is restricted to a single user.`; case ResourceControlOwnership.RESTRICTED: return `This ${resourceName} can be managed by a restricted set of users and/or teams.`; case ResourceControlOwnership.PUBLIC: return `This ${resourceName} can be managed by any user with access to this environment.`; case ResourceControlOwnership.ADMINISTRATORS: default: return `This ${resourceName} can only be managed by administrators.`; } } function getInheritanceMessage( resourceType: ResourceControlType | undefined, 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 ( Access control on this resource is inherited from the following service: {truncate(resourceId)} ); } if ( resourceType === ResourceControlType.Volume && parentType === ResourceControlType.Container ) { return ( Access control on this resource is inherited from the following container: {truncate(resourceId)} ); } if (parentType === ResourceControlType.Stack) { return ( Access control on this resource is inherited from the following stack: {removeEndpointIdFromStackResourceId(resourceId)} ); } 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) { return (
{children}
); } 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; }) ); } ); }