18284: Add update vm login dialog, remove default group, update tests
[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 } 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
44 export const openUserVirtualMachines = () =>
45     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
46         dispatch<any>(navigateToUserVirtualMachines);
47     };
48
49 export const openAdminVirtualMachines = () =>
50     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
51         const user = getState().auth.user;
52         if (user && user.isAdmin) {
53             dispatch<any>(navigateToAdminVirtualMachines);
54         } else {
55             dispatch<any>(navigateToRootProject);
56             dispatch(snackbarActions.OPEN_SNACKBAR({ message: "You don't have permissions to view this page", hideDuration: 2000, kind: SnackbarKind.ERROR }));
57         }
58     };
59
60 export const openVirtualMachineAttributes = (uuid: string) =>
61     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
62         const virtualMachineData = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
63         dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ATTRIBUTES_DIALOG, data: { virtualMachineData } }));
64     };
65
66 const loadRequestedDate = () =>
67     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
68         const date = services.virtualMachineService.getRequestedDate();
69         dispatch(virtualMachinesActions.SET_REQUESTED_DATE(date));
70     };
71
72 export const loadVirtualMachinesAdminData = () =>
73     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
74         dispatch<any>(loadRequestedDate());
75
76         const virtualMachines = await services.virtualMachineService.list();
77         dispatch(updateResources(virtualMachines.items));
78         dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
79
80
81         const logins = await services.permissionService.list({
82             filters: new FilterBuilder()
83             .addIn('head_uuid', virtualMachines.items.map(item => item.uuid))
84             .addEqual('name', PermissionLevel.CAN_LOGIN)
85             .getFilters()
86         });
87         dispatch(updateResources(logins.items));
88         dispatch(virtualMachinesActions.SET_LINKS(logins));
89
90         const users = await services.userService.list({
91             filters: new FilterBuilder()
92             .addIn('uuid', logins.items.map(item => item.tailUuid))
93             .getFilters(),
94             count: "none"
95         });
96         dispatch(updateResources(users.items));
97
98         const getAllLogins = await services.virtualMachineService.getAllLogins();
99         dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
100     };
101
102 export const loadVirtualMachinesUserData = () =>
103     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
104         dispatch<any>(loadRequestedDate());
105         const virtualMachines = await services.virtualMachineService.list();
106         const virtualMachinesUuids = virtualMachines.items.map(it => it.uuid);
107         const links = await services.linkService.list({
108             filters: new FilterBuilder()
109                 .addIn("head_uuid", virtualMachinesUuids)
110                 .getFilters()
111         });
112         dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
113         dispatch(virtualMachinesActions.SET_LINKS(links));
114     };
115
116 export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
117     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
118         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {[VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid, [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: []}));
119         dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {}} ));
120     }
121
122 export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
123     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
124         const login = await services.permissionService.get(permissionUuid);
125         const user = await services.userService.get(login.tailUuid);
126         dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
127                 [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
128                 [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true), uuid: login.tailUuid},
129                 [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
130             }));
131         dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {updating: true}} ));
132     }
133
134 export interface AddLoginFormData {
135     [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: string;
136     [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: string;
137     [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: Participant;
138     [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: string[];
139 }
140
141
142 export const addUpdateVirtualMachineLogin = ({uuid, vmUuid, user, groups}: AddLoginFormData) =>
143     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
144         try {
145             // Get user
146             const userResource = await services.userService.get(user.uuid);
147
148             if (uuid) {
149                 const permission = await services.permissionService.update(uuid, {
150                     tailUuid: userResource.uuid,
151                     name: PermissionLevel.CAN_LOGIN,
152                     properties: {
153                         username: userResource.username,
154                         groups,
155                     }
156                 });
157                 dispatch(updateResources([permission]));
158             } else {
159                 const permission = await services.permissionService.create({
160                 headUuid: vmUuid,
161                     tailUuid: userResource.uuid,
162                     name: PermissionLevel.CAN_LOGIN,
163                     properties: {
164                         username: userResource.username,
165                         groups,
166                     }
167                 });
168                 dispatch(updateResources([permission]));
169             }
170
171             dispatch(reset(VIRTUAL_MACHINE_ADD_LOGIN_FORM));
172             dispatch(dialogActions.CLOSE_DIALOG({ id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG }));
173             dispatch<any>(loadVirtualMachinesAdminData());
174
175             dispatch(snackbarActions.OPEN_SNACKBAR({
176                 message: `Permission updated`,
177                 kind: SnackbarKind.SUCCESS
178             }));
179         } catch (e) {
180             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
181         }
182     };
183
184 export const openRemoveVirtualMachineLoginDialog = (uuid: string) =>
185     (dispatch: Dispatch, getState: () => RootState) => {
186         dispatch(dialogActions.OPEN_DIALOG({
187             id: VIRTUAL_MACHINE_REMOVE_LOGIN_DIALOG,
188             data: {
189                 title: 'Remove login permission',
190                 text: 'Are you sure you want to remove this permission?',
191                 confirmButtonLabel: 'Remove',
192                 uuid
193             }
194         }));
195     };
196
197 export const removeVirtualMachineLogin = (uuid: string) =>
198     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
199         try {
200             await services.permissionService.delete(uuid);
201             dispatch<any>(deleteResources([uuid]));
202
203             dispatch<any>(loadVirtualMachinesAdminData());
204
205             dispatch(snackbarActions.OPEN_SNACKBAR({
206                 message: `Login permission removed`,
207                 kind: SnackbarKind.SUCCESS
208             }));
209         } catch (e) {
210             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
211         }
212     };
213
214 export const saveRequestedDate = () =>
215     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
216         const date = formatDate((new Date()).toISOString());
217         services.virtualMachineService.saveRequestedDate(date);
218         dispatch<any>(loadRequestedDate());
219     };
220
221 export const openRemoveVirtualMachineDialog = (uuid: string) =>
222     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
223         dispatch(dialogActions.OPEN_DIALOG({
224             id: VIRTUAL_MACHINE_REMOVE_DIALOG,
225             data: {
226                 title: 'Remove virtual machine',
227                 text: 'Are you sure you want to remove this virtual machine?',
228                 confirmButtonLabel: 'Remove',
229                 uuid
230             }
231         }));
232     };
233
234 export const removeVirtualMachine = (uuid: string) =>
235     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
236         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO }));
237         await services.virtualMachineService.delete(uuid);
238         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
239         dispatch<any>(loadVirtualMachinesAdminData());
240     };
241
242 const virtualMachinesBindedActions = bindDataExplorerActions(VIRTUAL_MACHINES_PANEL);
243
244 export const loadVirtualMachinesPanel = () =>
245     (dispatch: Dispatch) => {
246         dispatch(virtualMachinesBindedActions.REQUEST_ITEMS());
247     };