Merge branch 'main' into 21357-favorites-names
[arvados.git] / services / workbench2 / src / views-components / sharing-dialog / sharing-dialog-component.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 {
7     Dialog,
8     DialogTitle,
9     Button,
10     Grid,
11     DialogContent,
12     CircularProgress,
13     Paper,
14     Tabs,
15     Tab,
16     Checkbox,
17     FormControlLabel,
18     Typography,
19 } from '@material-ui/core';
20 import {
21     StyleRulesCallback,
22     WithStyles,
23     withStyles
24 } from '@material-ui/core/styles';
25 import { DialogActions } from 'components/dialog-actions/dialog-actions';
26 import { SharingURLsContent } from './sharing-urls';
27 import {
28     extractUuidObjectType,
29     ResourceObjectType
30 } from 'models/resource';
31 import { SharingInvitationForm } from './sharing-invitation-form';
32 import { SharingManagementForm } from './sharing-management-form';
33 import {
34     BasePicker,
35     Calendar,
36     MuiPickersUtilsProvider,
37     TimePickerView
38 } from 'material-ui-pickers';
39 import DateFnsUtils from "@date-io/date-fns";
40 import moment from 'moment';
41 import { SharingPublicAccessForm } from './sharing-public-access-form';
42
43 export interface SharingDialogDataProps {
44     open: boolean;
45     loading: boolean;
46     saveEnabled: boolean;
47     sharedResourceUuid: string;
48     sharingURLsNr: number;
49     privateAccess: boolean;
50     sharingURLsDisabled: boolean;
51     permissions: any[];
52 }
53 export interface SharingDialogActionProps {
54     onClose: () => void;
55     onSave: () => void;
56     onCreateSharingToken: (d: Date | undefined) => () => void;
57     refreshPermissions: () => void;
58 }
59 enum SharingDialogTab {
60     PERMISSIONS = 0,
61     URLS = 1,
62 }
63 export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
64
65 export default (props: SharingDialogComponentProps) => {
66     const { open, loading, saveEnabled, sharedResourceUuid,
67         sharingURLsNr, privateAccess, sharingURLsDisabled,
68         onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
69     const showTabs = !sharingURLsDisabled && extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
70     const [tabNr, setTabNr] = React.useState<number>(SharingDialogTab.PERMISSIONS);
71     const [expDate, setExpDate] = React.useState<Date>();
72     const [withExpiration, setWithExpiration] = React.useState<boolean>(false);
73
74     // Sets up the dialog depending on the resource type
75     if (!showTabs && tabNr !== SharingDialogTab.PERMISSIONS) {
76         setTabNr(SharingDialogTab.PERMISSIONS);
77     }
78
79     React.useEffect(() => {
80         if (!withExpiration) {
81             setExpDate(undefined);
82         } else {
83             setExpDate(moment().add(2, 'hour').minutes(0).seconds(0).toDate());
84         }
85     }, [withExpiration]);
86
87     return <Dialog
88         {...{ open, onClose }}
89         className="sharing-dialog"
90         fullWidth
91         maxWidth='sm'
92         disableBackdropClick={saveEnabled}
93         disableEscapeKeyDown={saveEnabled}>
94         <DialogTitle>
95             Sharing settings
96         </DialogTitle>
97         {showTabs &&
98             <Tabs value={tabNr}
99                 onChange={(_, tb) => {
100                     if (tb === SharingDialogTab.PERMISSIONS) {
101                         refreshPermissions();
102                     }
103                     setTabNr(tb)
104                 }
105                 }>
106                 <Tab label="With users/groups" />
107                 <Tab label={`Sharing URLs ${sharingURLsNr > 0 ? '(' + sharingURLsNr + ')' : ''}`} disabled={saveEnabled} />
108             </Tabs>
109         }
110         <DialogContent>
111             {tabNr === SharingDialogTab.PERMISSIONS &&
112                 <Grid container direction='column' spacing={24}>
113                     <Grid item>
114                         <SharingInvitationForm onSave={onSave} saveEnabled={saveEnabled} />
115                     </Grid>
116                     <Grid item>
117                         <SharingManagementForm onSave={onSave} />
118                     </Grid>
119                     <Grid item>
120                         <SharingPublicAccessForm onSave={onSave} />
121                     </Grid>
122                 </Grid>
123             }
124             {tabNr === SharingDialogTab.URLS &&
125                 <SharingURLsContent uuid={sharedResourceUuid} />
126             }
127         </DialogContent>
128         <DialogActions>
129             <Grid container spacing={8}>
130                 {tabNr === SharingDialogTab.URLS && withExpiration && <>
131                     <Grid item container direction='row' md={12}>
132                         <MuiPickersUtilsProvider utils={DateFnsUtils}>
133                             <BasePicker autoOk value={expDate} onChange={setExpDate}>
134                                 {({ date, handleChange }) => (<>
135                                     <Grid item md={6}>
136                                         <Calendar date={date} minDate={new Date()} maxDate={undefined}
137                                             onChange={handleChange} />
138                                     </Grid>
139                                     <Grid item md={6}>
140                                         <TimePickerView type="hours" date={date} ampm={false}
141                                             onMinutesChange={() => { }}
142                                             onSecondsChange={() => { }}
143                                             onHourChange={handleChange}
144                                         />
145                                     </Grid>
146                                 </>)}
147                             </BasePicker>
148                         </MuiPickersUtilsProvider>
149                     </Grid>
150                     <Grid item md={12}>
151                         <Typography variant='caption' align='center'>
152                             Maximum expiration date may be limited by the cluster configuration.
153                         </Typography>
154                     </Grid>
155                 </>
156                 }
157                 {tabNr === SharingDialogTab.PERMISSIONS && !sharingURLsDisabled &&
158                     privateAccess && sharingURLsNr > 0 &&
159                     <Grid item md={12}>
160                         <Typography variant='caption' align='center' color='error'>
161                             Although there aren't specific permissions set, this is publicly accessible via Sharing URL(s).
162                         </Typography>
163                     </Grid>
164                 }
165                 <Grid item xs />
166                 {tabNr === SharingDialogTab.URLS && <>
167                     <Grid item><FormControlLabel
168                         control={<Checkbox color="primary" checked={withExpiration}
169                             onChange={(e) => setWithExpiration(e.target.checked)} />}
170                         label="With expiration" />
171                     </Grid>
172                     <Grid item>
173                         <Button variant="contained" color="primary"
174                             disabled={expDate !== undefined && expDate <= new Date()}
175                             onClick={onCreateSharingToken(expDate)}>
176                             Create sharing URL
177                         </Button>
178                     </Grid>
179                 </>
180                 }
181                 <Grid item>
182                     <Button onClick={() => {
183                         onClose();
184                         setWithExpiration(false);
185                     }}>
186                         Close
187                     </Button>
188                 </Grid>
189             </Grid>
190         </DialogActions>
191         {
192             loading && <LoadingIndicator />
193         }
194     </Dialog>;
195 };
196
197 const loadingIndicatorStyles: StyleRulesCallback<'root'> = theme => ({
198     root: {
199         position: 'absolute',
200         top: 0,
201         right: 0,
202         bottom: 0,
203         left: 0,
204         display: 'flex',
205         alignItems: 'center',
206         justifyContent: 'center',
207         backgroundColor: 'rgba(255, 255, 255, 0.8)',
208     },
209 });
210
211 const LoadingIndicator = withStyles(loadingIndicatorStyles)(
212     (props: WithStyles<'root'>) =>
213         <Paper classes={props.classes}>
214             <CircularProgress />
215         </Paper>
216 );