17415: Added path for ip based hostnames
[arvados-workbench2.git] / src / views-components / webdav-s3-dialog / webdav-s3-dialog.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from "react";
6 import { Dialog, DialogActions, Button, StyleRulesCallback, WithStyles, withStyles, CardHeader, Tab, Tabs } from '@material-ui/core';
7 import { withDialog } from "~/store/dialog/with-dialog";
8 import { COLLECTION_WEBDAV_S3_DIALOG_NAME, WebDavS3InfoDialogData } from '~/store/collections/collection-info-actions';
9 import { WithDialogProps } from '~/store/dialog/with-dialog';
10 import { compose } from 'redux';
11 import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
12 import { DownloadIcon } from "~/components/icon/icon";
13
14 export type CssRules = 'details' | 'downloadButton';
15
16 const styles: StyleRulesCallback<CssRules> = theme => ({
17     details: {
18         marginLeft: theme.spacing.unit * 3,
19         marginRight: theme.spacing.unit * 3,
20     },
21     downloadButton: {
22         marginTop: theme.spacing.unit * 2,
23     }
24 });
25
26 interface TabPanelData {
27     children: React.ReactElement<any>[];
28     value: number;
29     index: number;
30 }
31
32 function TabPanel(props: TabPanelData) {
33     const { children, value, index } = props;
34
35     return (
36         <div
37             role="tabpanel"
38             hidden={value !== index}
39             id={`simple-tabpanel-${index}`}
40             aria-labelledby={`simple-tab-${index}`}
41         >
42             {value === index && children}
43         </div>
44     );
45 }
46
47 const isValidIpAddress = (ipAddress: string): Boolean => {
48     console.log(ipAddress);
49     if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipAddress)) {
50         return true;
51     }
52
53     return false;
54 };
55
56 const mountainduckTemplate = ({
57     uuid, 
58     username,
59     cyberDavStr,
60     collectionsUrl
61 }: any) => {
62     
63     return `<?xml version="1.0" encoding="UTF-8"?>
64         <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65         <plist version="1.0">
66         <dict>
67             <key>Protocol</key>
68             <string>davs</string>
69             <key>Provider</key>
70             <string>iterate GmbH</string>
71             <key>UUID</key>
72             <string>${uuid}</string>
73             <key>Hostname</key>
74             <string>${collectionsUrl.replace('https://', ``).replace('*', uuid).split(':')[0]}</string>
75             <key>Port</key>
76             <string>${(cyberDavStr.split(':')[2] || '443').split('/')[0]}</string>
77             <key>Username</key>
78             <string>${username}</string>${isValidIpAddress(collectionsUrl.replace('https://', ``).split(':')[0])? 
79             `
80             <key>Path</key>
81             <string>/c=${uuid}</string>` : ''}
82             <key>Labels</key>
83             <array>
84             </array>
85         </dict>
86         </plist>`.split(/\r?\n/).join('\n');
87 };
88
89 const downloadMountainduckFileHandler = (filename: string, text: string) => {
90     const element = document.createElement('a');
91     element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
92     element.setAttribute('download', filename);
93   
94     element.style.display = 'none';
95     document.body.appendChild(element);
96   
97     element.click();
98   
99     document.body.removeChild(element);
100 };
101
102 export const WebDavS3InfoDialog = compose(
103     withDialog(COLLECTION_WEBDAV_S3_DIALOG_NAME),
104     withStyles(styles),
105 )(
106     (props: WithDialogProps<WebDavS3InfoDialogData> & WithStyles<CssRules>) => {
107         if (!props.data.downloadUrl) { return null; }
108
109         let winDav;
110         let cyberDav;
111
112         if (props.data.collectionsUrl.indexOf("*") > -1) {
113             const withuuid = props.data.collectionsUrl.replace("*", props.data.uuid);
114             winDav = new URL(withuuid);
115             cyberDav = new URL(withuuid);
116         } else {
117             winDav = new URL(props.data.downloadUrl);
118             cyberDav = new URL(props.data.downloadUrl);
119             winDav.pathname = `/by_id/${props.data.uuid}`;
120             cyberDav.pathname = `/by_id/${props.data.uuid}`;
121         }
122
123         cyberDav.username = props.data.username;
124         const cyberDavStr = "dav" + cyberDav.toString().slice(4);
125
126         const s3endpoint = new URL(props.data.collectionsUrl.replace(/\/\*(--[^.]+)?\./, "/"));
127
128         const sp = props.data.token.split("/");
129         let tokenUuid;
130         let tokenSecret;
131         if (sp.length === 3 && sp[0] === "v2" && sp[1].slice(0, 5) === props.data.localCluster) {
132             tokenUuid = sp[1];
133             tokenSecret = sp[2];
134         } else {
135             tokenUuid = props.data.token.replace(/\//g, "_");
136             tokenSecret = tokenUuid;
137         }
138
139         const supportsWebdav = (props.data.uuid.indexOf("-4zz18-") === 5);
140
141         let activeTab = props.data.activeTab;
142         if (!supportsWebdav) {
143             activeTab = 2;
144         }
145
146         return <Dialog
147             open={props.open}
148             maxWidth="md"
149             onClose={props.closeDialog}
150             style={{ alignSelf: 'stretch' }}>
151             <CardHeader
152                 title={`Open as Network Folder or S3 Bucket`} />
153             <div className={props.classes.details} >
154                 <Tabs value={activeTab} onChange={props.data.setActiveTab}>
155                     {supportsWebdav && <Tab value={0} key="cyberduck" label="Cyberduck/Mountain Duck or Gnome Files" />}
156                     {supportsWebdav && <Tab value={1} key="windows" label="Windows or MacOS" />}
157                     <Tab value={2} key="s3" label="S3 bucket" />
158                 </Tabs>
159
160                 <TabPanel index={1} value={activeTab}>
161                     <h2>Settings</h2>
162
163                     <DetailsAttribute
164                         label='Internet address'
165                         value={<a href={winDav.toString()} target="_blank">{winDav.toString()}</a>}
166                         copyValue={winDav.toString()} />
167
168                     <DetailsAttribute
169                         label='Username'
170                         value={props.data.username}
171                         copyValue={props.data.username} />
172
173                     <DetailsAttribute
174                         label='Password'
175                         value={props.data.token}
176                         copyValue={props.data.token} />
177
178                     <h3>Windows</h3>
179                     <ol>
180                         <li>Open File Explorer</li>
181                         <li>Click on "This PC", then go to Computer &rarr; Add a Network Location</li>
182                         <li>Click Next, then choose "Add a custom network location", then click Next</li>
183                     </ol>
184
185                     <h3>MacOS</h3>
186                     <ol>
187                         <li>Open Finder</li>
188                         <li>Click Go &rarr; Connect to server</li>
189                     </ol>
190                 </TabPanel>
191
192                 <TabPanel index={0} value={activeTab}>
193                     <DetailsAttribute
194                         label='Server'
195                         value={<a href={cyberDavStr} target="_blank">{cyberDavStr}</a>}
196                         copyValue={cyberDavStr} />
197
198                     <DetailsAttribute
199                         label='Username'
200                         value={props.data.username}
201                         copyValue={props.data.username} />
202
203                     <DetailsAttribute
204                         label='Password'
205                         value={props.data.token}
206                         copyValue={props.data.token} />
207
208                     <Button
209                         data-cy='download-button'
210                         className={props.classes.downloadButton}
211                         onClick={() => downloadMountainduckFileHandler(`${props.data.collectionName || props.data.uuid}.duck`, mountainduckTemplate({ ...props.data, cyberDavStr }))}
212                         variant='contained'
213                         color='primary'
214                         size='small'>
215                         <DownloadIcon />
216                         Download Cyber/Mountain Duck bookmark
217                     </Button>
218
219                     <h3>Gnome</h3>
220                     <ol>
221                         <li>Open Files</li>
222                         <li>Select +Other Locations</li>
223                         <li>Connect to Server &rarr; Enter server address</li>
224                     </ol>
225
226                 </TabPanel>
227
228                 <TabPanel index={2} value={activeTab}>
229                     <DetailsAttribute
230                         label='Endpoint'
231                         value={s3endpoint.host}
232                         copyValue={s3endpoint.host} />
233
234                     <DetailsAttribute
235                         label='Bucket'
236                         value={props.data.uuid}
237                         copyValue={props.data.uuid} />
238
239                     <DetailsAttribute
240                         label='Access Key'
241                         value={tokenUuid}
242                         copyValue={tokenUuid} />
243
244                     <DetailsAttribute
245                         label='Secret Key'
246                         value={tokenSecret}
247                         copyValue={tokenSecret} />
248
249                 </TabPanel>
250
251             </div>
252             <DialogActions>
253                 <Button
254                     variant='text'
255                     color='primary'
256                     onClick={props.closeDialog}>
257                     Close
258                 </Button>
259             </DialogActions>
260
261         </Dialog >;
262     }
263 );