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