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