18284: Add update vm login dialog, remove default group, update tests
authorStephen Smith <stephen@curii.com>
Tue, 8 Feb 2022 16:43:05 +0000 (11:43 -0500)
committerStephen Smith <stephen@curii.com>
Tue, 8 Feb 2022 16:43:05 +0000 (11:43 -0500)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

cypress/integration/virtual-machine-admin.spec.js
src/store/virtual-machines/virtual-machines-actions.ts
src/views-components/virtual-machines-dialog/add-login-dialog.tsx
src/views/virtual-machine-panel/virtual-machine-admin-panel.tsx

index d057ce4983e2a030dea022d57f5c3e06c3ac5c4c..73804b2030da01a5d50f80d4a75da380b49425d7 100644 (file)
@@ -41,10 +41,10 @@ describe('Virtual machine login manage tests', function() {
                 cy.get('button[title="Add Login Permission"]').click();
             });
         cy.get('[data-cy=form-dialog]')
-            .should('contain', 'Add login permissions')
+            .should('contain', 'Add login permission')
             .within(() => {
                 cy.get('label')
-                  .contains('Search for users')
+                  .contains('Search for user')
                   .parent()
                   .within(() => {
                     cy.get('input').type('VMAdmin');
@@ -52,13 +52,13 @@ describe('Virtual machine login manage tests', function() {
             });
         cy.get('[role=tooltip]').click();
         cy.get('[data-cy=form-dialog]')
-            .should('contain', 'Add login permissions')
+            .should('contain', 'Add login permission')
             .within(() => {
                 cy.get('label')
                   .contains('Add groups')
                   .parent()
                   .within(() => {
-                    cy.get('input').type('sudo{enter}');
+                    cy.get('input').type('docker sudo{enter}');
                   })
             });
         cy.get('[data-cy=form-dialog]').within(() => {
@@ -79,10 +79,10 @@ describe('Virtual machine login manage tests', function() {
                 cy.get('button[title="Add Login Permission"]').click();
             });
         cy.get('[data-cy=form-dialog]')
-            .should('contain', 'Add login permissions')
+            .should('contain', 'Add login permission')
             .within(() => {
                 cy.get('label')
-                  .contains('Search for users')
+                  .contains('Search for user')
                   .parent()
                   .within(() => {
                     cy.get('input').type('VMActive user');
@@ -118,12 +118,100 @@ describe('Virtual machine login manage tests', function() {
         cy.get('header button[title="Account Management"]').click();
         cy.get('#account-menu').contains('Virtual Machines').click();
 
+        cy.get('[data-cy=vm-user-table]')
+            .contains(vmHost)
+            .parents('tr')
+            .within(() => {
+                cy.get('td').contains('user');
+                cy.get('td').should('not.contain', 'docker');
+                cy.get('td').should('not.contain', 'sudo');
+                cy.get('td').contains('ssh user@' + vmHost);
+        });
+
+        // Edit login permissions
+        cy.loginAs(adminUser);
+        cy.get('header button[title="Admin Panel"]').click();
+        cy.get('#admin-menu').contains('Virtual Machines').click();
+
+        cy.get('[data-cy=vm-admin-table]')
+            .contains('admin'); // Wait for page to finish
+
+        cy.get('[data-cy=vm-admin-table]')
+            .contains(vmHost)
+            .parents('tr')
+            .contains('admin')
+            .click();
+
+        cy.get('[data-cy=form-dialog]')
+            .should('contain', 'Update login permission')
+            .within(() => {
+                cy.get('label')
+                    .contains('Add groups')
+                    .parent()
+                    .as('groupInput');
+            });
+
+        cy.get('@groupInput').within(() => {
+            cy.get('div[role=button]').contains('sudo').parent().find('svg').click();
+            cy.get('div[role=button]').contains('docker').parent().find('svg').click();
+        });
+
+        cy.get('[data-cy=form-dialog]').within(() => {
+            cy.get('[data-cy=form-submit-btn]').click();
+        });
+
+        cy.get('[data-cy=vm-admin-table]')
+            .contains('user'); // Wait for page to finish
+
+        cy.get('[data-cy=vm-admin-table]')
+            .contains(vmHost)
+            .parents('tr')
+            .contains('user')
+            .click();
+
+        cy.get('[data-cy=form-dialog]')
+            .should('contain', 'Update login permission')
+            .within(() => {
+                cy.get('label')
+                    .contains('Add groups')
+                    .parent()
+                    .within(() => {
+                        cy.get('input').type('docker{enter}');
+                    })
+            });
+
+        cy.get('[data-cy=form-dialog]').within(() => {
+            cy.get('[data-cy=form-submit-btn]').click();
+        });
+
+        // Verify new login permissions
+        // Check admin's vm page for login
+        cy.get('header button[title="Account Management"]').click();
+        cy.get('#account-menu').contains('Virtual Machines').click();
+
+        cy.get('[data-cy=vm-user-table]')
+            .contains(vmHost)
+            .parents('tr')
+            .within(() => {
+                cy.get('td').contains('admin');
+                cy.get('td').should('not.contain', 'docker');
+                cy.get('td').should('not.contain', 'sudo');
+                cy.get('td').contains('ssh admin@' + vmHost);
+        });
+
+        // Verify new login permissions
+        // Check activeUser's vm page for login
+        cy.loginAs(activeUser);
+        cy.get('header button[title="Account Management"]').click();
+        cy.get('#account-menu').contains('Virtual Machines').click();
+
         cy.get('[data-cy=vm-user-table]')
             .contains(vmHost)
             .parents('tr')
             .within(() => {
                 cy.get('td').contains('user');
                 cy.get('td').contains('docker');
+                cy.get('td').should('not.contain', 'sudo');
                 cy.get('td').contains('ssh user@' + vmHost);
         });
 
index 08654a4468b6b97e9597bba45315b9f84b7f1199..e2cf6fd4c0e33da5c3da8a901d153c3937a9c0aa 100644 (file)
@@ -18,6 +18,7 @@ import { PermissionLevel } from "models/permission";
 import { deleteResources, updateResources } from 'store/resources/resources-actions';
 import { Participant } from "views-components/sharing-dialog/participant-select";
 import { initialize, reset } from "redux-form";
+import { getUserDisplayName } from "models/user";
 
 export const virtualMachinesActions = unionize({
     SET_REQUESTED_DATE: ofType<string>(),
@@ -35,6 +36,7 @@ export const VIRTUAL_MACHINE_ADD_LOGIN_DIALOG = 'virtualMachineAddLoginDialog';
 export const VIRTUAL_MACHINE_ADD_LOGIN_FORM = 'virtualMachineAddLoginForm';
 export const VIRTUAL_MACHINE_REMOVE_LOGIN_DIALOG = 'virtualMachineRemoveLoginDialog';
 
+export const VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD = 'uuid';
 export const VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD = 'vmUuid';
 export const VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD = 'user';
 export const VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD = 'groups';
@@ -111,42 +113,67 @@ export const loadVirtualMachinesUserData = () =>
         dispatch(virtualMachinesActions.SET_LINKS(links));
     };
 
-export const openAddVirtualMachineLoginDialog = (uuid: string) =>
+export const openAddVirtualMachineLoginDialog = (vmUuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {[VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: uuid, [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: ['docker']}));
+        dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {[VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: vmUuid, [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: []}));
         dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {}} ));
     }
 
+export const openEditVirtualMachineLoginDialog = (permissionUuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const login = await services.permissionService.get(permissionUuid);
+        const user = await services.userService.get(login.tailUuid);
+        dispatch(initialize(VIRTUAL_MACHINE_ADD_LOGIN_FORM, {
+                [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: permissionUuid,
+                [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: {name: getUserDisplayName(user, true), uuid: login.tailUuid},
+                [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: login.properties.groups,
+            }));
+        dispatch(dialogActions.OPEN_DIALOG( {id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, data: {updating: true}} ));
+    }
+
 export interface AddLoginFormData {
+    [VIRTUAL_MACHINE_UPDATE_LOGIN_UUID_FIELD]: string;
     [VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD]: string;
     [VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: Participant;
     [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: string[];
 }
 
 
-export const addVirtualMachineLogin = ({vmUuid, user, groups}: AddLoginFormData) =>
+export const addUpdateVirtualMachineLogin = ({uuid, vmUuid, user, groups}: AddLoginFormData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         try {
             // Get user
             const userResource = await services.userService.get(user.uuid);
 
-            const permission = await services.permissionService.create({
+            if (uuid) {
+                const permission = await services.permissionService.update(uuid, {
+                    tailUuid: userResource.uuid,
+                    name: PermissionLevel.CAN_LOGIN,
+                    properties: {
+                        username: userResource.username,
+                        groups,
+                    }
+                });
+                dispatch(updateResources([permission]));
+            } else {
+                const permission = await services.permissionService.create({
                 headUuid: vmUuid,
-                tailUuid: userResource.uuid,
-                name: PermissionLevel.CAN_LOGIN,
-                properties: {
-                    username: userResource.username,
-                    groups,
-                }
-            });
-            dispatch(updateResources([permission]));
+                    tailUuid: userResource.uuid,
+                    name: PermissionLevel.CAN_LOGIN,
+                    properties: {
+                        username: userResource.username,
+                        groups,
+                    }
+                });
+                dispatch(updateResources([permission]));
+            }
 
             dispatch(reset(VIRTUAL_MACHINE_ADD_LOGIN_FORM));
             dispatch(dialogActions.CLOSE_DIALOG({ id: VIRTUAL_MACHINE_ADD_LOGIN_DIALOG }));
             dispatch<any>(loadVirtualMachinesAdminData());
 
             dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: `Permissions updated`,
+                message: `Permission updated`,
                 kind: SnackbarKind.SUCCESS
             }));
         } catch (e) {
index 8d5434060602ce2a7cc24093d7eb91ffad5627c1..bfc047164a4bc8d00bdb15f82c8db19b422c6521 100644 (file)
@@ -7,7 +7,7 @@ import { compose } from "redux";
 import { reduxForm, InjectedFormProps, WrappedFieldProps, Field } from 'redux-form';
 import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
 import { FormDialog } from 'components/form-dialog/form-dialog';
-import { VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, VIRTUAL_MACHINE_ADD_LOGIN_FORM, addVirtualMachineLogin, AddLoginFormData, VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD } from 'store/virtual-machines/virtual-machines-actions';
+import { VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, VIRTUAL_MACHINE_ADD_LOGIN_FORM, addUpdateVirtualMachineLogin, AddLoginFormData, VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD } from 'store/virtual-machines/virtual-machines-actions';
 import { ParticipantSelect } from 'views-components/sharing-dialog/participant-select';
 import { GroupArrayInput } from 'views-components/virtual-machines-dialog/group-array-input';
 
@@ -16,27 +16,27 @@ export const VirtualMachineAddLoginDialog = compose(
     reduxForm<AddLoginFormData>({
         form: VIRTUAL_MACHINE_ADD_LOGIN_FORM,
         onSubmit: (data, dispatch) => {
-            dispatch(addVirtualMachineLogin(data));
+            dispatch(addUpdateVirtualMachineLogin(data));
         }
     })
 )(
     (props: CreateGroupDialogComponentProps) =>
         <FormDialog
-            dialogTitle='Add login permissions'
+            dialogTitle={props.data.updating ? "Update login permission" : "Add login permission"}
             formFields={AddLoginFormFields}
-            submitLabel='Add'
+            submitLabel={props.data.updating ? "Update" : "Add"}
             {...props}
         />
 );
 
-type CreateGroupDialogComponentProps = WithDialogProps<{}> & InjectedFormProps<AddLoginFormData>;
+type CreateGroupDialogComponentProps = WithDialogProps<{updating: boolean}> & InjectedFormProps<AddLoginFormData>;
 
 const AddLoginFormFields = () =>
     <>
         <UserField />
         <GroupArrayInput
             name={VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD}
-            input={{id:"Add groups to VM login", disabled:false}}
+            input={{id:"Add groups to VM login (eg: docker, sudo)", disabled:false}}
             required={false}
         />
     </>;
@@ -50,7 +50,7 @@ const UserField = () =>
 const UserSelect = ({ input, meta }: WrappedFieldProps) =>
     <ParticipantSelect
         onlyPeople
-        label='Search for users to grant login permission'
+        label='Search for user to grant login permission'
         items={input.value ? [input.value] : []}
         onSelect={input.onChange}
         onDelete={() => (input.onChange(''))} />;
index bfa6be2657a197a246434525df61d889068ef4f1..468ef35a984da4f7a1198d886c576e4d9a9e33ba 100644 (file)
@@ -8,7 +8,7 @@ import { Grid, Card, Chip, CardContent, TableBody, TableCell, TableHead, TableRo
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 import { ArvadosTheme } from 'common/custom-theme';
 import { compose, Dispatch } from 'redux';
-import { loadVirtualMachinesAdminData, openAddVirtualMachineLoginDialog, openRemoveVirtualMachineLoginDialog } from 'store/virtual-machines/virtual-machines-actions';
+import { loadVirtualMachinesAdminData, openAddVirtualMachineLoginDialog, openRemoveVirtualMachineLoginDialog, openEditVirtualMachineLoginDialog } from 'store/virtual-machines/virtual-machines-actions';
 import { RootState } from 'store/store';
 import { ListResults } from 'services/common-service/common-service';
 import { MoreOptionsIcon, AddUserIcon } from 'components/icon/icon';
@@ -40,7 +40,7 @@ const mapStateToProps = (state: RootState) => {
     };
 };
 
-const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'onOptionsMenuOpen' | 'onAddLogin' | 'onDeleteLogin'> => ({
+const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'onOptionsMenuOpen' | 'onAddLogin' | 'onDeleteLogin' | 'onLoginEdit'> => ({
     loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesAdminData()),
     onOptionsMenuOpen: (event, virtualMachine) => {
         dispatch<any>(openVirtualMachinesContextMenu(event, virtualMachine));
@@ -51,6 +51,9 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelAction
     onDeleteLogin: (uuid: string) => {
         dispatch<any>(openRemoveVirtualMachineLoginDialog(uuid));
     },
+    onLoginEdit: (uuid: string) => {
+        dispatch<any>(openEditVirtualMachineLoginDialog(uuid));
+    },
 });
 
 interface VirtualMachinesPanelDataProps {
@@ -65,6 +68,7 @@ interface VirtualMachinesPanelActionProps {
     onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, virtualMachine: VirtualMachinesResource) => void;
     onAddLogin: (uuid: string) => void;
     onDeleteLogin: (uuid: string) => void;
+    onLoginEdit: (uuid: string) => void;
 }
 
 type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
@@ -117,7 +121,7 @@ const virtualMachinesTable = (props: VirtualMachineProps) =>
                         <Grid container spacing={8} className={props.classes.chipsRoot}>
                             {props.links.items.filter((link) => (link.headUuid === machine.uuid)).map((permission, i) => (
                                 <Grid item key={i}>
-                                    <Chip label={<VirtualMachineLogin linkUuid={permission.uuid} />} onDelete={event => props.onDeleteLogin(permission.uuid)} />
+                                    <Chip label={<VirtualMachineLogin linkUuid={permission.uuid} />} onDelete={event => props.onDeleteLogin(permission.uuid)} onClick={event => props.onLoginEdit(permission.uuid)} />
                                 </Grid>
                             ))}
                         </Grid>