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