18979: Filter user vm panel request to only fetch current user login permissions
[arvados-workbench2.git] / src / store / virtual-machines / virtual-machines-actions.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 { ServiceRepository } from "services/services";
8 import { navigateToUserVirtualMachines, navigateToAdminVirtualMachines, navigateToRootProject } from "store/navigation/navigation-action";
9 import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-action';
10 import { formatDate } from "common/formatters";
11 import { unionize, ofType, UnionOf } from "common/unionize";
12 import { VirtualMachineLogins } from 'models/virtual-machines';
13 import { FilterBuilder } from "services/api/filter-builder";
14 import { ListResults } from "services/common-service/common-service";
15 import { dialogActions } from 'store/dialog/dialog-actions';
16 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
17 import { PermissionLevel } from "models/permission";
18 import { deleteResources, updateResources } from 'store/resources/resources-actions';
19 import { Participant } from "views-components/sharing-dialog/participant-select";
20 import { initialize, reset } from "redux-form";
21 import { getUserDisplayName, UserResource } from "models/user";
22
23 export const virtualMachinesActions = unionize({
24     SET_REQUESTED_DATE: ofType<string>(),
25     SET_VIRTUAL_MACHINES: ofType<ListResults<any>>(),
26     SET_LOGINS: ofType<VirtualMachineLogins>(),
27     SET_LINKS: ofType<ListResults<any>>()
28 });
29
30 export type VirtualMachineActions = UnionOf<typeof virtualMachinesActions>;
31
32 export const VIRTUAL_MACHINES_PANEL = 'virtualMachinesPanel';
33 export const VIRTUAL_MACHINE_ATTRIBUTES_DIALOG = 'virtualMachineAttributesDialog';
34 export const VIRTUAL_MACHINE_REMOVE_DIALOG = 'virtualMachineRemoveDialog';
35 export const VIRTUAL_MACHINE_ADD_LOGIN_DIALOG = 'virtualMachineAddLoginDialog';
36 export const VIRTUAL_MACHINE_ADD_LOGIN_FORM = 'virtualMachineAddLoginForm';
37 export const VIRTUAL_MACHINE_REMOVE_LOGIN_DIALOG = 'virtualMachineRemoveLoginDialog';
38
39 export const VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD = 'uuid';
40 export const VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD = 'vmUuid';
41 export const VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD = 'user';
42 export const VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD = 'groups';
43 export const VIRTUAL_MACHINE_ADD_LOGIN_EXCLUDE = 'excludedPerticipants';
44
45 export const openUserVirtualMachines = () =>
46     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
47         dispatch<any>(navigateToUserVirtualMachines);
48     };
49
50 export const openAdminVirtualMachines = () =>
51     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
52         const user = getState().auth.user;
53         if (user && user.isAdmin) {
54             dispatch<any>(navigateToAdminVirtualMachines);
55         } else {
56             dispatch<any>(navigateToRootProject);
57             dispatch(snackbarActions.OPEN_SNACKBAR({ message: "You don't have permissions to view this page", hideDuration: 2000, kind: SnackbarKind.ERROR }));
58         }
59     };
60
61 export const openVirtualMachineAttributes = (uuid: string) =>
62     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
63         const virtualMachineData = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
64         dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ATTRIBUTES_DIALOG, data: { virtualMachineData } }));
65     };
66
67 const loadRequestedDate = () =>
68     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
69         const date = services.virtualMachineService.getRequestedDate();
70         dispatch(virtualMachinesActions.SET_REQUESTED_DATE(date));
71     };
72
73 export const loadVirtualMachinesAdminData = () =>
74     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
75         dispatch<any>(loadRequestedDate());
76
77         const virtualMachines = await services.virtualMachineService.list();
78         dispatch(updateResources(virtualMachines.items));
79         dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
80
81
82         const logins = await services.permissionService.list({
83             filters: new FilterBuilder()
84             .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
85             .addEqual('name', PermissionLevel.CAN_LOGIN)
86             .getFilters(),
87             limit: 1000
88         });
89         dispatch(updateResources(logins.items));
90         dispatch(virtualMachinesActions.SET_LINKS(logins));
91
92         const users = await services.userService.list({
93             filters: new FilterBuilder()
94             .addIn('uuid', logins.items.map(item => item.tailUuid))
95             .getFilters(),
96             count: "none", // Necessary for federated queries
97             limit: 1000
98         });
99         dispatch(updateResources(users.items));
100
101         const getAllLogins = await services.virtualMachineService.getAllLogins();
102         dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
103     };
104
105 export const loadVirtualMachinesUserData = () =>
106     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
107         dispatch<any>(loadRequestedDate());
108         const user = getState().auth.user;
109         const virtualMachines = await services.virtualMachineService.list();
110         const virtualMachinesUuids = virtualMachines.items.map(it => it.uuid);
111         const links = await services.linkService.list({
112             filters: new FilterBuilder()
113                 .addIn("head_uuid", virtualMachinesUuids)
114                 .addEqual("tail_uuid", user?.uuid)
115                 .getFilters()
116         });
117         dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
118         dispatch(virtualMachinesActions.SET_LINKS(links));
119     };
120
121 export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
122     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
123         // Get login permissions of vm
124         const virtualMachines = await services.virtualMachineService.list();
125         dispatch(updateResources(virtualMachines.items));
126         const logins = await services.permissionService.list({
127             filters: new FilterBuilder()
128             .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
129             .addEqual('name', PermissionLevel.CAN_LOGIN)
130             .getFilters()
131         });
132         dispatch(updateResources(logins.items));
133
134         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
135                 [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid,
136                 [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: [],
137             }));
138         dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {excludedParticipants: logins.items.map(it => it.tailUuid)}} ));
139     }
140
141 export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
142     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
143         const login = await services.permissionService.get(permissionUuid);
144         const user = await services.userService.get(login.tailUuid);
145         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
146                 [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
147                 [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true, true), uuid: login.tailUuid},
148                 [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
149             }));
150         dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {updating: true}} ));
151     }
152
153 export interface AddLoginFormData {
154     [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: string;
155     [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: string;
156     [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: Participant;
157     [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: string[];
158 }
159
160
161 export const addUpdateVirtualMachineLogin = ({uuid, vmUuid, user, groups}: AddLoginFormData) =>
162     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
163         let userResource: UserResource | undefined = undefined;
164         try {
165             // Get user
166             userResource = await services.userService.get(user.uuid, false);
167         } catch (e) {
168                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Failed to get user details.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
169                 return;
170         }
171         try {
172             if (uuid) {
173                 const permission = await services.permissionService.update(uuid, {
174                     tailUuid: userResource.uuid,
175                     name: PermissionLevel.CAN_LOGIN,
176                     properties: {
177                         username: userResource.username,
178                         groups,
179                     }
180                 });
181                 dispatch(updateResources([permission]));
182             } else {
183                 const permission = await services.permissionService.create({
184                     headUuid: vmUuid,
185                     tailUuid: userResource.uuid,
186                     name: PermissionLevel.CAN_LOGIN,
187                     properties: {
188                         username: userResource.username,
189                         groups,
190                     }
191                 });
192                 dispatch(updateResources([permission]));
193             }
194
195             dispatch(reset(VIRTUAL_MACHINE_ADD_LOGIN_FORM));
196             dispatch(dialogActions.CLOSE_DIALOG({ id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG }));
197             dispatch<any>(loadVirtualMachinesAdminData());
198
199             dispatch(snackbarActions.OPEN_SNACKBAR({
200                 message: `Permission updated`,
201                 kind: SnackbarKind.SUCCESS
202             }));
203         } catch (e) {
204             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
205         }
206     };
207
208 export const openRemoveVirtualMachineLoginDialog = (uuid: string) =>
209     (dispatch: Dispatch, getState: () => RootState) => {
210         dispatch(dialogActions.OPEN_DIALOG({
211             id: VIRTUAL_MACHINE_REMOVE_LOGIN_DIALOG,
212             data: {
213                 title: 'Remove login permission',
214                 text: 'Are you sure you want to remove this permission?',
215                 confirmButtonLabel: 'Remove',
216                 uuid
217             }
218         }));
219     };
220
221 export const removeVirtualMachineLogin = (uuid: string) =>
222     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
223         try {
224             await services.permissionService.delete(uuid);
225             dispatch<any>(deleteResources([uuid]));
226
227             dispatch<any>(loadVirtualMachinesAdminData());
228
229             dispatch(snackbarActions.OPEN_SNACKBAR({
230                 message: `Login permission removed`,
231                 kind: SnackbarKind.SUCCESS
232             }));
233         } catch (e) {
234             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
235         }
236     };
237
238 export const saveRequestedDate = () =>
239     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
240         const date = formatDate((new Date()).toISOString());
241         services.virtualMachineService.saveRequestedDate(date);
242         dispatch<any>(loadRequestedDate());
243     };
244
245 export const openRemoveVirtualMachineDialog = (uuid: string) =>
246     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
247         dispatch(dialogActions.OPEN_DIALOG({
248             id: VIRTUAL_MACHINE_REMOVE_DIALOG,
249             data: {
250                 title: 'Remove virtual machine',
251                 text: 'Are you sure you want to remove this virtual machine?',
252                 confirmButtonLabel: 'Remove',
253                 uuid
254             }
255         }));
256     };
257
258 export const removeVirtualMachine = (uuid: string) =>
259     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
260         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO }));
261         await services.virtualMachineService.delete(uuid);
262         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
263         dispatch<any>(loadVirtualMachinesAdminData());
264     };
265
266 const virtualMachinesBindedActions = bindDataExplorerActions(VIRTUAL_MACHINES_PANEL);
267
268 export const loadVirtualMachinesPanel = () =>
269     (dispatch: Dispatch) => {
270         dispatch(virtualMachinesBindedActions.REQUEST_ITEMS());
271     };