mirror of https://github.com/portainer/portainer
200 lines
6.7 KiB
TypeScript
200 lines
6.7 KiB
TypeScript
import { Form, FormikProps } from 'formik';
|
|
import { Plus } from 'lucide-react';
|
|
import { useMemo } from 'react';
|
|
|
|
import { useEnvironment } from '@/react/portainer/environments/queries';
|
|
import { useGroup } from '@/react/portainer/environments/environment-groups/queries';
|
|
import { useUsers } from '@/portainer/users/queries';
|
|
import { useTeams } from '@/react/portainer/users/teams/queries/useTeams';
|
|
import { User } from '@/portainer/users/types';
|
|
import { Environment } from '@/react/portainer/environments/types';
|
|
import { Team } from '@/react/portainer/users/teams/types';
|
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|
import { EnvironmentGroup } from '@/react/portainer/environments/environment-groups/types';
|
|
import { useIsEdgeAdmin } from '@/react/hooks/useUser';
|
|
|
|
import { LoadingButton } from '@@/buttons';
|
|
import { FormControl } from '@@/form-components/FormControl';
|
|
import { Link } from '@@/Link';
|
|
|
|
import { NamespaceAccessUsersSelector } from '../NamespaceAccessUsersSelector';
|
|
import { EnvironmentAccess, NamespaceAccess } from '../types';
|
|
|
|
import { CreateAccessValues } from './types';
|
|
|
|
export function CreateAccessInnerForm({
|
|
values,
|
|
handleSubmit,
|
|
setFieldValue,
|
|
isSubmitting,
|
|
isValid,
|
|
dirty,
|
|
namespaceAccessesGranted,
|
|
}: FormikProps<CreateAccessValues> & {
|
|
namespaceAccessesGranted: NamespaceAccess[];
|
|
}) {
|
|
const environmentId = useEnvironmentId();
|
|
const environmentQuery = useEnvironment(environmentId);
|
|
const groupQuery = useGroup(environmentQuery.data?.GroupId);
|
|
const usersQuery = useUsers(false, environmentId);
|
|
const teamsQuery = useTeams();
|
|
const availableTeamOrUserOptions: EnvironmentAccess[] =
|
|
useAvailableTeamOrUserOptions(
|
|
values.selectedUsersAndTeams,
|
|
namespaceAccessesGranted,
|
|
environmentQuery.data,
|
|
groupQuery.data,
|
|
usersQuery.data,
|
|
teamsQuery.data
|
|
);
|
|
const isAdminQuery = useIsEdgeAdmin();
|
|
return (
|
|
<Form className="form-horizontal" onSubmit={handleSubmit} noValidate>
|
|
<FormControl label="Select user(s) and/or team(s)">
|
|
{availableTeamOrUserOptions.length > 0 ||
|
|
values.selectedUsersAndTeams.length > 0 ? (
|
|
<NamespaceAccessUsersSelector
|
|
inputId="users-selector"
|
|
options={availableTeamOrUserOptions}
|
|
onChange={(opts) => setFieldValue('selectedUsersAndTeams', opts)}
|
|
value={values.selectedUsersAndTeams}
|
|
dataCy="namespaceAccess-usersSelector"
|
|
/>
|
|
) : (
|
|
<span className="small text-muted pt-2">
|
|
No user or team access has been set on the environment.
|
|
{isAdminQuery.isAdmin && (
|
|
<>
|
|
{' '}
|
|
Head over to the{' '}
|
|
<Link
|
|
to="portainer.endpoints"
|
|
data-cy="namespaceAccess-environmentsLink"
|
|
>
|
|
Environments view
|
|
</Link>{' '}
|
|
to manage them.
|
|
</>
|
|
)}
|
|
</span>
|
|
)}
|
|
</FormControl>
|
|
<div className="form-group mt-5">
|
|
<div className="col-sm-12">
|
|
<LoadingButton
|
|
disabled={!isValid || !dirty}
|
|
data-cy="namespaceAccess-createAccessButton"
|
|
isLoading={isSubmitting}
|
|
loadingText="Creating access..."
|
|
icon={Plus}
|
|
className="!ml-0"
|
|
>
|
|
Create access
|
|
</LoadingButton>
|
|
</div>
|
|
</div>
|
|
</Form>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the team and user options that can be added to the namespace, excluding the ones that already have access.
|
|
*/
|
|
function useAvailableTeamOrUserOptions(
|
|
selectedAccesses: EnvironmentAccess[],
|
|
namespaceAccessesGranted: NamespaceAccess[],
|
|
environment?: Environment,
|
|
group?: EnvironmentGroup,
|
|
users?: User[],
|
|
teams?: Team[]
|
|
) {
|
|
return useMemo(() => {
|
|
// get unique users and teams from environment accesses (the keys are the IDs)
|
|
const environmentAccessPolicies = environment?.UserAccessPolicies ?? {};
|
|
const environmentTeamAccessPolicies = environment?.TeamAccessPolicies ?? {};
|
|
const environmentGroupAccessPolicies = group?.UserAccessPolicies ?? {};
|
|
const environmentGroupTeamAccessPolicies = group?.TeamAccessPolicies ?? {};
|
|
|
|
// get all users that have access to the environment
|
|
const userAccessPolicies = {
|
|
...environmentAccessPolicies,
|
|
...environmentGroupAccessPolicies,
|
|
};
|
|
const uniqueUserIds = new Set(Object.keys(userAccessPolicies));
|
|
const userAccessOptions: EnvironmentAccess[] = Array.from(uniqueUserIds)
|
|
.map((id) => {
|
|
const userId = parseInt(id, 10);
|
|
const user = users?.find((u) => u.Id === userId);
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
// role from the userAccessPolicies is used by default, if not found, role from the environmentTeamAccessPolicies is used
|
|
const userAccessPolicy =
|
|
environmentAccessPolicies[userId] ??
|
|
environmentGroupAccessPolicies[userId];
|
|
const userAccess: EnvironmentAccess = {
|
|
id: user?.Id,
|
|
name: user?.Username,
|
|
type: 'user',
|
|
role: {
|
|
name: 'Standard user',
|
|
id: userAccessPolicy?.RoleId,
|
|
},
|
|
};
|
|
return userAccess;
|
|
})
|
|
.filter((u) => u !== null);
|
|
|
|
// get all teams that have access to the environment
|
|
const teamAccessPolicies = {
|
|
...environmentTeamAccessPolicies,
|
|
...environmentGroupTeamAccessPolicies,
|
|
};
|
|
const uniqueTeamIds = new Set(Object.keys(teamAccessPolicies));
|
|
const teamAccessOptions: EnvironmentAccess[] = Array.from(uniqueTeamIds)
|
|
.map((id) => {
|
|
const teamId = parseInt(id, 10);
|
|
const team = teams?.find((t) => t.Id === teamId);
|
|
if (!team) {
|
|
return null;
|
|
}
|
|
const teamAccessPolicy =
|
|
environmentTeamAccessPolicies[teamId] ??
|
|
environmentGroupTeamAccessPolicies[teamId];
|
|
const teamAccess: EnvironmentAccess = {
|
|
id: team?.Id,
|
|
name: team?.Name,
|
|
type: 'team',
|
|
role: {
|
|
name: 'Standard user',
|
|
id: teamAccessPolicy?.RoleId,
|
|
},
|
|
};
|
|
return teamAccess;
|
|
})
|
|
.filter((t) => t !== null);
|
|
|
|
// filter out users and teams that already have access to the namespace
|
|
const userAndTeamEnvironmentAccesses = [
|
|
...userAccessOptions,
|
|
...teamAccessOptions,
|
|
];
|
|
const filteredAccessOptions = userAndTeamEnvironmentAccesses.filter(
|
|
(t) =>
|
|
!selectedAccesses.some((e) => e.id === t.id && e.type === t.type) &&
|
|
!namespaceAccessesGranted.some(
|
|
(e) => e.id === t.id && e.type === t.type
|
|
)
|
|
);
|
|
|
|
return filteredAccessOptions;
|
|
}, [
|
|
namespaceAccessesGranted,
|
|
selectedAccesses,
|
|
environment,
|
|
group,
|
|
users,
|
|
teams,
|
|
]);
|
|
}
|