update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / components / filemanager / FileManager.jsx
1 /*
2  *
3  *   Copyright 2016 RIFT.IO Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  *
17  */
18
19
20 //https://raw.githubusercontent.com/RIFTIO/RIFT.ware/master/rift-shell
21 import _cloneDeep from 'lodash/cloneDeep'
22 import _findIndex from 'lodash/findIndex'
23 import _uniqueId from 'lodash/uniqueId'
24 import React from 'react';
25 import ReactDOM from 'react-dom';
26 import TreeView from 'react-treeview';
27 import TextInput from 'widgets/form_controls/textInput.jsx';
28 import Button from '../Button';
29 import './FileMananger.scss';
30 import FileManagerActions from './FileManagerActions.js';
31 import imgSave from '../../../../node_modules/open-iconic/svg/data-transfer-upload.svg'
32 import { Panel, PanelWrapper } from 'widgets/panel/panel';
33 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
34 import LoadingIndicator from 'widgets/loading-indicator/loadingIndicator.jsx';
35
36 import DropZone from 'dropzone'
37 import Utils from '../../libraries/utils'
38 import FileManagerUploadDropZone from '../../libraries/FileManagerUploadDropZone';
39 let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
40
41 const ASSET_TYPE = {
42     'nsd': [
43         { id: 'ICONS', folder: 'icons', title: "Icons", allowFolders: false },
44         { id: 'SCRIPTS', folder: 'scripts', title: "scripts", allowFolders: true },
45         { id: 'NS_CONFIG', folder: 'ns_config', title: "NS Config", allowFolders: false },
46         { id: 'VNF_CONFIG', folder: 'vnf_config', title: "VNF Config", allowFolders: false },
47         { id: 'DOC', folder: 'doc', title: "Doc", allowFolders: false },
48         { id: 'TEST', folder: 'test', title: "Test", allowFolders: false }
49     ],
50     'vnfd': [
51         { id: 'ICONS', folder: 'icons', title: "Icons", allowFolders: false },
52         { id: 'CHARMS', folder: 'charms', title: "charms", allowFolders: true },
53         { id: 'SCRIPTS', folder: 'scripts', title: "scripts", allowFolders: true },
54         { id: 'CLOUD_INIT', folder: 'cloud_init', title: "cloud_init", allowFolders: false },
55         { id: 'DOC', folder: 'doc', title: "Doc", allowFolders: false },
56         { id: 'TEST', folder: 'test', title: "Test", allowFolders: false },
57         { id: 'README', folder: '.', title: "readme", allowFolders: false }
58     ]
59 }
60
61 const createDropZone = function (action, clickable, getUploadPropsCallback, dropTarget) {
62     const dropZone = new FileManagerUploadDropZone(ReactDOM.findDOMNode(dropTarget), [clickable], action, getUploadPropsCallback);
63     // dropZone.on('dragover', this.onDragOver);
64     // dropZone.on('dragend', this.onDragEnd);
65     // dropZone.on('addedfile', this.onFileAdded);
66     return dropZone;
67 };
68
69 function normalizeAssets(packageType, assetInfo, filesStatus) {
70     let assets = {};
71     let assetTypes = ASSET_TYPE[packageType];
72     assetTypes.forEach(assetGroup => {
73         const typeFolder = assetGroup.folder;
74         let folders = assetInfo.id.filter(name => name.startsWith(typeFolder));
75         if (folders.length) {
76             folders.reverse();
77             assets[assetGroup.id] = folders.map(fullName => {
78                 let path = fullName.slice(typeFolder.length + 1);
79                 let files = assetInfo.data[fullName].reduce((assets, info) =>
80                     {
81                         let name = info.name.startsWith(fullName) ? info.name.slice(fullName.length + 1) : info.name;
82                         let status = filesStatus[info.name];
83                         if (fullName !== '.' || !(name.endsWith('.yaml') || name.startsWith('checksum'))) {
84                             assets.push({ name, status });
85                         }
86                         return assets;
87                     }
88                 , []);
89                 return { path, files };
90             });
91         }
92     });
93     return assets;
94 }
95
96 function sendDeleteFileRequest(assetType, path, name) {
97     path = path ? path + '/' + name : name;
98     FileManagerActions.deletePackageFile({ assetType, path });
99 }
100
101 class FileManager extends React.Component {
102     constructor(props) {
103         super(props)
104         let assests = props.files;
105     }
106     componentWillMount() {
107         // FileManagerActions.openFileManagerSockets()
108     }
109     componentWillUnmount() {
110         // FileManagerActions.closeFileManagerSockets();
111     }
112     generateFolder(data, nesting) {
113         let nestingLevel = nesting || 1;
114     }
115     render() {
116         let { files, filesState, type, item, actions } = this.props;
117         if (!item) {
118             return null;
119         }
120         let assets = normalizeAssets(type, files, filesState);
121         let children = [];
122         const User = this.props.User || {};
123         const ProjectID =  User.projectId;
124         let assetTypes = ASSET_TYPE[type];
125         assetTypes.forEach(assetGroup => {
126             const typeFolder = assetGroup.folder;
127             let folders = assets[assetGroup.id];
128             let rootAssets = { path: '', files: [] };
129             let subFolders = null;
130             if (folders && folders.length) {
131                 rootAssets = folders[0];
132                 subFolders = folders.slice(1);
133             }
134             children.push(
135                 <AssetGroup
136                     key={typeFolder}
137                     packageId={item.id}
138                     packageType={type}
139                     title={assetGroup.title}
140                     assetGroup={assetGroup}
141                     path={rootAssets.path}
142                     files={rootAssets.files}
143                     allowsFolders={assetGroup.allowFolders}
144                     folders={subFolders}
145                     showNotification={actions.showNotification}
146                     ProjectID={ProjectID}
147                 />
148             )
149         }, this);
150
151         let html = (
152             <div className="FileManager">
153                 <PanelWrapper style={{ flexDirection: 'column' }}>
154                     {children}
155                 </PanelWrapper>
156             </div>
157         )
158         return html;
159     }
160
161 }
162
163 function NewHierachy(props) {
164     return (
165         <Panel className="addFileSection" style={{ backgroundColor: 'transparent' }} no-corners>
166             <div className="inputSection">
167                 <TextInput placeholder="some/path" label="create a folder hierarchy" onChange={FileManagerActions.newPathNameUpdated} />
168                 <Button label="Create" onClick={e => FileManagerActions.createDirectory(props.assetGroup.id)} />
169             </div>
170         </Panel>
171     );
172 }
173
174 class AssetGroup extends React.Component {
175     constructor(props) {
176         super(props);
177         this.state = { downloadPath: "" };
178     }
179
180     render() {
181         let { title, packageType, packageId, assetGroup, files, allowsFolders, folders, path, inputState, showNotification } = this.props;
182         let children = [];
183         if (folders && folders.length) {
184             folders.map(function (folder, i) {
185                 children.push(
186                     <AssetGroup
187                         key={folder.path}
188                         packageId={packageId}
189                         title={folder.path}
190                         packageType={packageType}
191                         files={folder.files}
192                         assetGroup={assetGroup}
193                         path={folder.path}
194                         showNotification={showNotification}
195                     />
196                 )
197             });
198         }
199         let folderCreateComponent = allowsFolders ? <NewHierachy assetGroup={assetGroup} /> : null;
200         let entries = null
201
202         function uploadFileFromUrl(url) {
203             if (!url || url == "") {
204                 return;
205             }
206             let splitUrl = url.split('/');
207             let fileName = splitUrl[splitUrl.length - 1];
208             if (files.findIndex(f => f.name === fileName) > -1) {
209                 showNotification('It seems you\'re attempting to upload a file with a duplicate file name');
210             } else {
211                 FileManagerActions.sendDownloadFileRequest({ url, assetType: assetGroup.id, path: path });
212             }
213         }
214
215         return (
216             <Panel title={title} itemClassName="nested" no-corners>
217                 {folderCreateComponent}
218                 <div className="folder">
219                     <FileAssetList files={files} path={path} packageId={packageId} packageType={packageType} assetGroup={assetGroup} ProjectID={this.props.ProjectID} />
220                     <Panel className="addFileSection" no-corners>
221                         <ItemUpload packageType={packageType} packageId={packageId} path={path} assetGroup={assetGroup} />
222                         <div style={{ marginLeft: '0.5rem' }}>
223                             OR
224                     </div>
225                         <div className="inputSection">
226                             <TextInput placeholder="URL" className="" label="External URL" value={this.state.downloadPath} onChange={e => this.setState({ downloadPath: e.target.value })} />
227                             <Button className='ComposerAppSave' label="DOWNLOAD" onClick={e => uploadFileFromUrl(this.state.downloadPath)} />
228                         </div>
229                     </Panel>
230                     <div>
231                         {children}
232                     </div>
233                 </div>
234             </Panel>
235         );
236     }
237 }
238
239 function FileAssetList(props) {
240     let { packageType, packageId, assetGroup, files, path } = props;
241     let children = null;
242     if (files) {
243         children = files.map(function (file, i) {
244             if (!file.hasOwnProperty('contents')) {
245                 return <FileAsset ProjectID={props.ProjectID} key={file.name} file={file} path={path} id={packageId} type={packageType} assetGroup={assetGroup} />
246             }
247         })
248     }
249     return (
250         <div className='file-list'>
251             {children}
252         </div>
253     );
254
255 }
256
257 function FileAsset(props) {
258     let { file, path, type, assetGroup, id, ProjectID } = props;
259     const name = file.name;
260     const downloadHost = API_SERVER.match('localhost') || API_SERVER.match('127.0.0.1') ? `${window.location.protocol}//${window.location.hostname}` : API_SERVER;
261     //{`${window.location.protocol}//${API_SERVER}:4567/api/package${type}/${id}/${path}/${name}`}
262     return (
263         <div className="file">
264             <div className="file-section">
265                 <div className="file-info">
266                     <div className="file-status"
267                         style={{ display: (file.status && file.status.toLowerCase() != 'completed') ? 'inherit' : 'none', color: (file.status == 'FAILED' ? 'red' : 'inherit') }}>
268                         {file.status && (file.status == 'IN_PROGRESS' || file.status == 'DOWNLOADING') ? <LoadingIndicator size={2} /> : file.status}
269                     </div>
270                     <div className="file-name">
271                         <a target="_blank" href={`${downloadHost}:8008/mano/api/package/${type}/${ProjectID}/${id}/${assetGroup.folder}${path}/${name}`}>{name}</a>
272                     </div>
273                 </div>
274                 <div className="file-action"
275                     style={{ display: (!file.status || (file && file.status.toLowerCase() != 'loading...')) ? 'inherit' : 'none', cursor: 'pointer' }}
276                     onClick={e => sendDeleteFileRequest(assetGroup.id, path, name)}>
277                     X
278                 </div>
279             </div>
280         </div>
281     )
282 }
283
284 class ItemUpload extends React.Component {
285     constructor(props) {
286         super(props);
287         this.state = { dropzoneIdClass: 'DZ-' + _uniqueId() };
288     }
289     componentDidMount() {
290         createDropZone(
291             FileManagerUploadDropZone.ACTIONS.onboard,
292             '.ComposerAppAddFile.' + this.state.dropzoneIdClass,
293             () => {
294             let theCode = 'crap';
295             return ({
296                 packageType: this.props.packageType,
297                 packageId: this.props.packageId,
298                 assetGroup: this.props.assetGroup,
299                 path: this.props.path
300             })},
301             this);
302     }
303
304     render() {
305         let { dropzoneIdClass } = this.props;
306         return (
307             <div className="inputSection">
308                 <label className="sqTextInput" style={{ flexDirection: 'row', alignItems: 'center' }}>
309                     <span>Upload File</span>
310                     <Button className={'ComposerAppAddFile ' + this.state.dropzoneIdClass} label="BROWSE" />
311                 </label>
312             </div>
313         )
314     }
315 }
316
317 function stripPath(name, path, returnPath) {
318     let stripSlash = (name.indexOf('/') > -1) ? '/' : '';
319     // return name.split(path + stripSlash)[1].replace('/', '');
320     let split = name.split(path + stripSlash)[returnPath ? 0 : 1];
321     return split ? split.replace('/', '') : name;
322 }
323
324
325
326 export default SkyquakeComponent(FileManager)
327 /**
328  * Sample Data
329  */
330 // let files = {
331 //   "name": ".",
332 //   "contents": [
333 //     {
334 //       "name": "pong_vnfd",
335 //       "contents": [
336 //         {
337 //           "name": "pong_vnfd/checksums.txt",
338 //           "last_modified_time": 1474458399.6218443,
339 //           "byte_size": 168
340 //         },
341 //         {
342 //           "name": "pong_vnfd/pong_vnfd.yaml",
343 //           "last_modified_time": 1474458399.6258445,
344 //           "byte_size": 3514
345 //         },
346 //         {
347 //           "name": "pong_vnfd/icons",
348 //           "contents": [
349 //             {
350 //               "name": "pong_vnfd/icons/rift_logo.png",
351 //               "last_modified_time": 1474458399.6218443,
352 //               "byte_size": 1658
353 //             }
354 //           ],
355 //           "last_modified_time": 1474458399.6218443,
356 //           "byte_size": 3
357 //         },
358 //         {
359 //           "name": "pong_vnfd/cloud_init",
360 //           "contents": [
361 //             {
362 //               "name": "pong_vnfd/cloud_init/pong_cloud_init.cfg",
363 //               "last_modified_time": 1474458399.6258445,
364 //               "byte_size": 227
365 //             }
366 //           ],
367 //           "last_modified_time": 1474458399.6258445,
368 //           "byte_size": 3
369 //         }
370 //       ],
371 //       "last_modified_time": 1474458399.6258445,
372 //       "byte_size": 6
373 //     }
374 //   ],
375 //   "last_modified_time": 1474458399.6218443,
376 //   "byte_size": 3
377 // };