Init RBAC read only: composer
[osm/UI.git] / skyquake / plugins / composer / src / src / components / ComposerApp.js
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 'use strict';
19
20 window['RIFT_wareLaunchpadComposerVersion'] = `semver 0.0.79`;
21
22 import 'es5-shim'
23 import 'babel-polyfill'
24 import alt from '../alt'
25 import UID from '../libraries/UniqueId'
26 import utils from '../libraries/utils'
27 import React from 'react'
28 import ReactDOM from 'react-dom'
29 import Crouton from 'react-crouton'
30 import ClassNames from 'classnames'
31 import PureRenderMixin from 'react-addons-pure-render-mixin'
32 import DeletionManager from '../libraries/DeletionManager'
33 import SelectionManager from '../libraries/SelectionManager'
34 import ResizableManager from '../libraries/ResizableManager'
35 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
36 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
37 import RiftHeader from './RiftHeader'
38 import CanvasPanel from './CanvasPanel'
39 import CatalogPanel from './CatalogPanel'
40 import DetailsPanel from './DetailsPanel'
41 import ModalOverlay from './ModalOverlay'
42 import ComposerAppToolbar from './ComposerAppToolbar'
43 import PanelResizeAction from '../actions/PanelResizeAction'
44 import ComposerAppActions from '../actions/ComposerAppActions'
45 import ComposerAppStore from '../stores/ComposerAppStore'
46 import CatalogDataStore from '../stores/CatalogDataStore'
47 import TooltipManager from '../libraries/TooltipManager'
48 import CatalogItemsActions from '../actions/CatalogItemsActions'
49 import CommonUtils from 'utils/utils.js'
50 import FileManagerActions from './filemanager/FileManagerActions';
51 import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
52 import ROLES from 'utils/roleConstants.js';
53
54 import 'normalize.css'
55 import '../styles/AppRoot.scss'
56 import 'style/layout.scss'
57
58
59 const resizeManager = new ResizableManager(window);
60
61 const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty('clearLocalStorage');
62
63 const preventDefault = e => e.preventDefault();
64 const clearDragState = () => ComposerAppActions.setDragState(null);
65
66 const PROJECT_ROLES = ROLES.PROJECT;
67 const PLATFORM = ROLES.PLATFORM;
68
69 const ComposerApp = React.createClass({
70 mixins: [PureRenderMixin],
71 getInitialState() {
72 return ComposerAppStore.getState();
73 },
74 getDefaultProps() {
75 return {};
76 },
77 contextTypes: {
78 router: React.PropTypes.object,
79 userProfile: React.PropTypes.object
80 },
81 componentWillMount() {
82 if (clearLocalStorage) {
83 window.localStorage.clear();
84 }
85 if(this.item) {
86 FileManagerActions.openFileManagerSockets();
87 }
88 this.state.isLoading = CatalogDataStore.getState().isLoading;
89 ComposerAppStore.listen(this.onChange);
90 CatalogDataStore.listen(this.onCatalogDataChanged);
91 window.addEventListener('resize', this.resize);
92 window.onbeforeunload = this.onBeforeUnload;
93 // prevent browser from downloading any drop outside of our specific drop zones
94 window.addEventListener('dragover', preventDefault);
95 window.addEventListener('drop', preventDefault);
96 // ensure drags initiated in the app clear the state on drop
97 window.addEventListener('drop', clearDragState);
98 DeletionManager.addEventListeners();
99 },
100 componentWillUnmount() {
101 window.removeEventListener('resize', this.resize);
102 window.removeEventListener('dragover', preventDefault);
103 window.removeEventListener('drop', preventDefault);
104 window.removeEventListener('drop', clearDragState);
105 FileManagerActions.closeFileManagerSockets();
106 // resizeManager automatically registered its event handlers
107 resizeManager.removeAllEventListeners();
108 ComposerAppStore.unlisten(this.onChange);
109 CatalogDataStore.unlisten(this.onCatalogDataChanged);
110 DeletionManager.removeEventListeners();
111 TooltipManager.removeEventListeners();
112 },
113 componentDidMount() {
114 resizeManager.addAllEventListeners();
115 const snapshot = window.localStorage.getItem('composer');
116 if (snapshot) {
117 alt.bootstrap(snapshot);
118 }
119 document.body.addEventListener('keydown', (event) => {
120 // prevent details editor form from blowing up the app
121 const ENTER_KEY = 13;
122 if (event.which === ENTER_KEY) {
123 event.preventDefault();
124 return false;
125 }
126 });
127 const appRootElement = ReactDOM.findDOMNode(this.refs.appRoot);
128 TooltipManager.addEventListeners(appRootElement);
129 SelectionManager.onClearSelection = () => {
130 if (this.state.item) {
131 CatalogItemsActions.catalogItemMetaDataChanged.defer(this.state.item);
132 }
133 };
134 },
135 componentDidUpdate() {
136 if (this.state.fullScreenMode) {
137 document.body.classList.add('-is-full-screen');
138 } else {
139 document.body.classList.remove('-is-full-screen');
140 }
141 SelectionManager.refreshOutline();
142 },
143 resize(e) {
144 PanelResizeAction.resize(e);
145 },
146 getModel() {
147 let html;
148 let self = this;
149 DescriptorModelMetaFactory.init().then(function(){
150
151 self.setState({
152 hasModel: true
153 })
154 });
155 },
156 render() {
157 let html = null;
158 let self = this;
159 const User = this.context.userProfile || {};
160 if(this.state.hasModel) {
161
162 function onClickUpdateSelection(event) {
163 if (event.defaultPrevented) {
164 return
165 }
166 const element = SelectionManager.getClosestElementWithUID(event.target);
167 if (element) {
168 SelectionManager.select(element);
169 SelectionManager.refreshOutline();
170 event.preventDefault();
171 } else {
172 SelectionManager.clearSelectionAndRemoveOutline();
173 }
174 }
175
176
177 let AppHeader = (<div className="AppHeader">
178 <RiftHeader />
179 </div>);
180 // AppHeader = null;
181 const classNames = ClassNames('ComposerApp');
182 const isNew = self.state.item && self.state.item.uiState.isNew;
183 const hasItem = self.state.item && self.state.item.uiState;
184 const isModified = self.state.item && self.state.item.uiState.modified;
185 const isEditingNSD = self.state.item && self.state.item.uiState && /nsd/.test(self.state.item.uiState.type);
186 const isEditingVNFD = self.state.item && self.state.item.uiState && /vnfd/.test(self.state.item.uiState.type);
187 const containers = self.state.containers;
188 const canvasTitle = containers.length ? containers[0].model.name : '';
189 const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
190 const isLoading = self.state.isLoading;
191
192 //Bridge element for Crouton fix. Should eventually put Composer on same flux context
193 const Bridge = this.state.ComponentBridgeElement;
194
195 html = (
196 <div ref="appRoot" id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
197 <Bridge />
198 <i className="corner-accent top left" />
199 <i className="corner-accent top right" />
200 <i className="corner-accent bottom left" />
201 <i className="corner-accent bottom right" />
202 {AppHeader}
203 <div className="AppBody">
204 <div className={classNames}>
205 <CatalogPanel layout={self.state.layout}
206 isLoading={isLoading}
207 hasNoCatalogs={hasNoCatalogs}
208 filterByType={self.state.filterCatalogByTypeValue} />
209 <CanvasPanel layout={self.state.layout}
210 hasNoCatalogs={hasNoCatalogs}
211 showMore={self.state.showMore}
212 containers={containers}
213 title={canvasTitle}
214 zoom={self.state.zoom}
215 panelTabShown={self.state.panelTabShown}
216 files={self.state.files}
217 filesState={self.state.filesState}
218 newPathName={self.state.newPathName}
219 item={self.state.item}
220 type={self.state.filterCatalogByTypeValue}
221 />
222 {
223 (self.state.panelTabShown == 'descriptor') ?
224 <DetailsPanel layout={self.state.layout}
225 hasNoCatalogs={hasNoCatalogs}
226 showMore={self.state.showMore}
227 containers={containers}
228 showJSONViewer={self.state.showJSONViewer} />
229 : null
230 }
231
232 <ComposerAppToolbar layout={self.state.layout}
233 showMore={self.state.showMore}
234 isEditingNSD={isEditingNSD}
235 isEditingVNFD={isEditingVNFD}
236 isModified={isModified}
237 isNew={isNew}
238 disabled={!hasItem || !isRBACValid(User, [PROJECT_ROLES.CAT_ADMIN])}
239 onClick={event => event.stopPropagation()}
240 panelTabShown={self.state.panelTabShown}/>
241 </div>
242 </div>
243 <ModalOverlay />
244 </div>
245 );
246 } else {
247 this.getModel();
248 }
249 return html;
250 },
251 onChange(state) {
252 this.setState(state);
253 },
254 onCatalogDataChanged(catalogDataState) {
255 const catalogs = catalogDataState.catalogs;
256 const unsavedChanges = catalogs.reduce((result, catalog) => {
257 if (result) {
258 return result;
259 }
260 return catalog.descriptors.reduce((result, descriptor) => {
261 if (result) {
262 return result;
263 }
264 return descriptor.uiState.modified;
265 }, false);
266 }, false);
267 this.setState({
268 unsavedChanges: unsavedChanges,
269 isLoading: catalogDataState.isLoading
270 });
271 },
272 onBeforeUnload() {
273 // https://trello.com/c/c8v321Xx/160-prompt-user-to-save-changes
274 //const snapshot = alt.takeSnapshot();
275 //window.localStorage.setItem('composer', snapshot);
276 if (this.state.unsavedChanges) {
277 return 'You have unsaved changes. If you do not onboard (or update) your changes they will be lost.';
278 }
279 }
280
281 });
282
283
284 export default ComposerApp;