Merge branch '22788-ansible-key-fix'
[arvados.git] / services / workbench2 / src / common / resource-to-menu-kind.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Dispatch } from 'redux';
6 import { RootState } from 'store/store';
7 import { AuthState } from 'store/auth/auth-reducer';
8 import { getResource } from 'store/resources/resources';
9 import { Resource, ResourceKind } from 'models/resource';
10 import { resourceIsFrozen } from 'common/frozen-resources';
11 import { GroupResource, GroupClass, isGroupResource, isUserGroup } from 'models/group';
12 import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
13 import { getProcess, isProcessCancelable } from 'store/processes/process';
14 import { isCollectionResource } from 'models/collection';
15 import { ResourcesState } from 'store/resources/resources';
16
17 type ProjectToMenuArgs = {
18     isAdmin: boolean;
19     readonly: boolean;
20     isFrozen: boolean;
21     canManage: boolean;
22     canWrite: boolean;
23     isFilterGroup: boolean;
24     unfreezeRequiresAdmin: boolean;
25     isEditable: boolean;
26 };
27
28 type CollectionToMenuArgs = {
29     isAdmin: boolean;
30     isEditable: boolean;
31     isOnlyWriteable: boolean;
32     isOldVersion: boolean;
33     isTrashed: boolean;
34 };
35
36 type ProcessToMenuArgs = {
37     isAdmin: boolean;
38     isRunning: boolean;
39     canWriteProcess: boolean;
40 };
41
42 type ProjectMenuKind = ContextMenuKind.PROJECT
43     | ContextMenuKind.PROJECT_ADMIN
44     | ContextMenuKind.FROZEN_PROJECT
45     | ContextMenuKind.FROZEN_PROJECT_ADMIN
46     | ContextMenuKind.FROZEN_MANAGEABLE_PROJECT
47     | ContextMenuKind.MANAGEABLE_PROJECT
48     | ContextMenuKind.READONLY_PROJECT
49     | ContextMenuKind.WRITEABLE_PROJECT
50     | ContextMenuKind.FILTER_GROUP
51     | ContextMenuKind.FILTER_GROUP_ADMIN;
52
53 type CollectionMenuKind = ContextMenuKind.COLLECTION
54     | ContextMenuKind.READONLY_COLLECTION
55     | ContextMenuKind.WRITEABLE_COLLECTION
56     | ContextMenuKind.OLD_VERSION_COLLECTION
57     | ContextMenuKind.TRASHED_COLLECTION
58     | ContextMenuKind.COLLECTION_ADMIN;
59
60 type ProcessMenuKind = ContextMenuKind.PROCESS_RESOURCE
61     | ContextMenuKind.PROCESS_ADMIN
62     | ContextMenuKind.RUNNING_PROCESS_RESOURCE
63     | ContextMenuKind.RUNNING_PROCESS_ADMIN
64     | ContextMenuKind.READONLY_PROCESS_RESOURCE;
65
66 export const resourceToMenuKind = (uuid: string, readonly = false) =>
67     (dispatch: Dispatch, getState: () => RootState): ContextMenuKind | undefined => {
68         const { auth, resources } = getState();
69         const resource = getResource<Resource>(uuid)(resources);
70         if (!resource) return;
71         const isAdmin = auth.user?.isAdmin || false;
72         const isFrozen = resourceIsFrozen(resource, resources);
73         const isEditable = getIsEditable(isAdmin, resource, resources, readonly, isFrozen);
74
75         if (isUserGroup(resource)) {
76             return ContextMenuKind.GROUPS
77         }
78         if (isGroupResource(resource)) {
79             const { canManage = false, canWrite = false } = resource;
80             const unfreezeRequiresAdmin = getUnfreezeRequiresAdmin(auth);
81             const isFilterGroup = resource.groupClass === GroupClass.FILTER;
82             return getProjectMenuKind({ isAdmin, isFrozen, isEditable, canManage, canWrite, unfreezeRequiresAdmin, isFilterGroup, readonly });
83         }
84         if (isCollectionResource(resource)){
85             const collectionParent = getResource<GroupResource>(resource.ownerUuid)(resources);
86             const isOnlyWriteable = collectionParent?.canWrite === true && collectionParent.canManage === false;
87             const isOldVersion = resource.uuid !== resource.currentVersionUuid;
88             const isTrashed = resource.isTrashed || false;
89             return getCollectionMenuKind({ isAdmin, isEditable, isOldVersion, isTrashed, isOnlyWriteable });
90         }
91         switch (resource.kind) {
92             case ResourceKind.PROCESS:
93                 const process = getProcess(uuid)(resources);
94                 const canWriteProcess = !!(process && getResource<GroupResource>(process.containerRequest.ownerUuid)(resources)?.canWrite);
95                 const isRunning = process ? isProcessCancelable(process) : false;
96                 return getProcessMenuKind({ isAdmin, isRunning, canWriteProcess });
97             case ResourceKind.USER:
98                 return ContextMenuKind.USER_DETAILS;
99             case ResourceKind.LINK:
100                 return ContextMenuKind.LINK;
101             case ResourceKind.WORKFLOW:
102                 return isEditable ? ContextMenuKind.WORKFLOW : ContextMenuKind.READONLY_WORKFLOW;
103             default:
104                 return;
105         }
106     };
107
108 const getProjectMenuKind = ({ isAdmin, readonly, isFrozen, canManage, canWrite, unfreezeRequiresAdmin, isEditable, isFilterGroup }: ProjectToMenuArgs): ProjectMenuKind => {
109     if (isFrozen) {
110         if (isAdmin) {
111             return ContextMenuKind.FROZEN_PROJECT_ADMIN;
112         }
113         if (canManage) {
114             if (unfreezeRequiresAdmin) return ContextMenuKind.MANAGEABLE_PROJECT;
115             return ContextMenuKind.FROZEN_MANAGEABLE_PROJECT;
116         }
117         if (isEditable) {
118             return ContextMenuKind.FROZEN_PROJECT;
119         }
120         return ContextMenuKind.READONLY_PROJECT;
121     }
122
123     if (isAdmin && !readonly) {
124         if (isFilterGroup) return ContextMenuKind.FILTER_GROUP_ADMIN;
125         return ContextMenuKind.PROJECT_ADMIN;
126     }
127
128     if (canManage === false && canWrite === true) {
129         return ContextMenuKind.WRITEABLE_PROJECT;
130     }
131
132     if (!isEditable) {
133         return ContextMenuKind.READONLY_PROJECT;
134     }
135
136     if (isFilterGroup) return ContextMenuKind.FILTER_GROUP;
137
138     return ContextMenuKind.PROJECT;
139 };
140
141 const getCollectionMenuKind = ({ isAdmin, isEditable, isOnlyWriteable, isOldVersion, isTrashed }: CollectionToMenuArgs): CollectionMenuKind => {
142     if (isOldVersion) {
143         return ContextMenuKind.OLD_VERSION_COLLECTION;
144     }
145
146     if (isTrashed && isEditable) {
147         return ContextMenuKind.TRASHED_COLLECTION;
148     }
149
150     if (isAdmin && isEditable) {
151         return ContextMenuKind.COLLECTION_ADMIN;
152     }
153
154     if (!isEditable) {
155         return ContextMenuKind.READONLY_COLLECTION;
156     }
157
158     return isOnlyWriteable ? ContextMenuKind.WRITEABLE_COLLECTION : ContextMenuKind.COLLECTION;
159 };
160
161 const getProcessMenuKind = ({ isAdmin, isRunning, canWriteProcess }: ProcessToMenuArgs): ProcessMenuKind => {
162     if (isAdmin) {
163         return isRunning ? ContextMenuKind.RUNNING_PROCESS_ADMIN : ContextMenuKind.PROCESS_ADMIN;
164     }
165
166     if (isRunning) {
167         return ContextMenuKind.RUNNING_PROCESS_RESOURCE;
168     }
169
170     return canWriteProcess ? ContextMenuKind.PROCESS_RESOURCE : ContextMenuKind.READONLY_PROCESS_RESOURCE;
171 };
172
173 //Utils--------------------------------------------------------------
174 const getUnfreezeRequiresAdmin = (auth: AuthState) => {
175     const { remoteHostsConfig } = auth;
176     if (!remoteHostsConfig) return false;
177     return Object.keys(remoteHostsConfig).some((k) => remoteHostsConfig[k].clusterConfig.API.UnfreezeProjectRequiresAdmin);
178 };
179
180 const getIsEditable = (isAdmin: boolean, resource: Resource, resources: ResourcesState, readonly: boolean, isFrozen: boolean) => {
181     const isEditable = (resources[resource.ownerUuid] as GroupResource)?.canWrite || (isGroupResource(resource) && resource.canWrite);
182     return (isAdmin || isEditable) && !readonly && !isFrozen;
183 };