18284: Add update vm login dialog, remove default group, update tests
[arvados-workbench2.git] / src / views / virtual-machine-panel / virtual-machine-admin-panel.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { connect } from 'react-redux';
7 import { Grid, Card, Chip, CardContent, TableBody, TableCell, TableHead, TableRow, Table, Tooltip, IconButton } from '@material-ui/core';
8 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
9 import { ArvadosTheme } from 'common/custom-theme';
10 import { compose, Dispatch } from 'redux';
11 import { loadVirtualMachinesAdminData, openAddVirtualMachineLoginDialog, openRemoveVirtualMachineLoginDialog, openEditVirtualMachineLoginDialog } from 'store/virtual-machines/virtual-machines-actions';
12 import { RootState } from 'store/store';
13 import { ListResults } from 'services/common-service/common-service';
14 import { MoreOptionsIcon, AddUserIcon } from 'components/icon/icon';
15 import { VirtualMachineLogins, VirtualMachinesResource } from 'models/virtual-machines';
16 import { openVirtualMachinesContextMenu } from 'store/context-menu/context-menu-actions';
17 import { ResourceUuid, VirtualMachineHostname, VirtualMachineLogin } from 'views-components/data-explorer/renderers';
18
19 type CssRules = 'moreOptionsButton' | 'moreOptions' | 'chipsRoot';
20
21 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
22     moreOptionsButton: {
23         padding: 0
24     },
25     moreOptions: {
26         textAlign: 'right',
27         '&:last-child': {
28             paddingRight: 0
29         }
30     },
31     chipsRoot: {
32         margin: `0px -${theme.spacing.unit / 2}px`,
33     },
34 });
35
36 const mapStateToProps = (state: RootState) => {
37     return {
38         userUuid: state.auth.user!.uuid,
39         ...state.virtualMachines
40     };
41 };
42
43 const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'onOptionsMenuOpen' | 'onAddLogin' | 'onDeleteLogin' | 'onLoginEdit'> => ({
44     loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesAdminData()),
45     onOptionsMenuOpen: (event, virtualMachine) => {
46         dispatch<any>(openVirtualMachinesContextMenu(event, virtualMachine));
47     },
48     onAddLogin: (uuid: string) => {
49         dispatch<any>(openAddVirtualMachineLoginDialog(uuid));
50     },
51     onDeleteLogin: (uuid: string) => {
52         dispatch<any>(openRemoveVirtualMachineLoginDialog(uuid));
53     },
54     onLoginEdit: (uuid: string) => {
55         dispatch<any>(openEditVirtualMachineLoginDialog(uuid));
56     },
57 });
58
59 interface VirtualMachinesPanelDataProps {
60     virtualMachines: ListResults<any>;
61     logins: VirtualMachineLogins;
62     links: ListResults<any>;
63     userUuid: string;
64 }
65
66 interface VirtualMachinesPanelActionProps {
67     loadVirtualMachinesData: () => string;
68     onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, virtualMachine: VirtualMachinesResource) => void;
69     onAddLogin: (uuid: string) => void;
70     onDeleteLogin: (uuid: string) => void;
71     onLoginEdit: (uuid: string) => void;
72 }
73
74 type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
75
76 export const VirtualMachineAdminPanel = compose(
77     withStyles(styles),
78     connect(mapStateToProps, mapDispatchToProps))(
79         class extends React.Component<VirtualMachineProps> {
80             componentDidMount() {
81                 this.props.loadVirtualMachinesData();
82             }
83
84             render() {
85                 const { virtualMachines } = this.props;
86                 return (
87                     <Grid container spacing={16}>
88                         {virtualMachines.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
89                     </Grid>
90                 );
91             }
92         }
93     );
94
95 const CardContentWithVirtualMachines = (props: VirtualMachineProps) =>
96     <Grid item xs={12}>
97         <Card>
98             <CardContent>
99                 {virtualMachinesTable(props)}
100             </CardContent>
101         </Card>
102     </Grid>;
103
104 const virtualMachinesTable = (props: VirtualMachineProps) =>
105     <Table data-cy="vm-admin-table">
106         <TableHead>
107             <TableRow>
108                 <TableCell>Uuid</TableCell>
109                 <TableCell>Host name</TableCell>
110                 <TableCell>Logins</TableCell>
111                 <TableCell />
112                 <TableCell />
113             </TableRow>
114         </TableHead>
115         <TableBody>
116             {props.virtualMachines.items.map((machine, index) =>
117                 <TableRow key={index}>
118                     <TableCell><ResourceUuid uuid={machine.uuid} /></TableCell>
119                     <TableCell><VirtualMachineHostname uuid={machine.uuid} /></TableCell>
120                     <TableCell>
121                         <Grid container spacing={8} className={props.classes.chipsRoot}>
122                             {props.links.items.filter((link) => (link.headUuid === machine.uuid)).map((permission, i) => (
123                                 <Grid item key={i}>
124                                     <Chip label={<VirtualMachineLogin linkUuid={permission.uuid} />} onDelete={event => props.onDeleteLogin(permission.uuid)} onClick={event => props.onLoginEdit(permission.uuid)} />
125                                 </Grid>
126                             ))}
127                         </Grid>
128                     </TableCell>
129                     <TableCell>
130                         <Tooltip title="Add Login Permission" disableFocusListener>
131                             <IconButton onClick={event => props.onAddLogin(machine.uuid)} className={props.classes.moreOptionsButton}>
132                                 <AddUserIcon />
133                             </IconButton>
134                         </Tooltip>
135                     </TableCell>
136                     <TableCell className={props.classes.moreOptions}>
137                         <Tooltip title="More options" disableFocusListener>
138                             <IconButton onClick={event => props.onOptionsMenuOpen(event, machine)} className={props.classes.moreOptionsButton}>
139                                 <MoreOptionsIcon />
140                             </IconButton>
141                         </Tooltip>
142                     </TableCell>
143                 </TableRow>
144             )}
145         </TableBody>
146     </Table>;