update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / redundancy / src / dashboard / sites.jsx
1 /*
2  * STANDARD_RIFT_IO_COPYRIGHT
3  */
4
5 import React from 'react';
6 import ReactDOM from 'react-dom';
7 import AppHeader from 'widgets/header/header.jsx';
8 import RedundancyStore from './redundancyStore.js';
9 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
10 import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
11 import 'style/layout.scss';
12 import {Panel, PanelWrapper} from 'widgets/panel/panel';
13 import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
14
15 import TextInput from 'widgets/form_controls/textInput.jsx';
16 import Input from 'widgets/form_controls/input.jsx';
17 import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
18 import SelectOption from 'widgets/form_controls/selectOption.jsx';
19 import 'widgets/form_controls/formControls.scss';
20 import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
21 import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
22 import _  from 'lodash';
23 import ROLES from 'utils/roleConstants.js';
24
25 import './redundancy.scss';
26 const PLATFORM = ROLES.PLATFORM;
27
28 class SiteManagementDashboard extends React.Component {
29     constructor(props) {
30         super(props);
31         this.Store = this.props.flux.stores.hasOwnProperty('RedundancyStore') ? this.props.flux.stores.RedundancyStore : this.props.flux.createStore(RedundancyStore, 'RedundancyStore');
32         this.state = this.Store.getState();
33         this.actions = this.state.actions;
34     }
35     componentDidUpdate() {
36         let self = this;
37         ReactDOM.findDOMNode(this.siteList).addEventListener('transitionend', this.onTransitionEnd, false);
38         setTimeout(function() {
39             let element = self[`site-ref-${self.state.activeIndex}`]
40             element && !isElementInView(element) && element.scrollIntoView({block: 'end', behavior: 'smooth'});
41         })
42     }
43     componentWillMount() {
44         this.state = this.Store.getState();
45         this.Store.getRedundancy();
46         this.Store.listen(this.updateState);
47     }
48     componentWillUnmount() {
49         this.Store.unlisten(this.updateState);
50     }
51     updateState = (state) => {
52         this.setState(state);
53     }
54     updateInput = (key, e) => {
55         let property = key;
56         let siteData = this.state.siteData;
57         siteData[property] = e.target.value;
58         this.actions.handleUpdateInput({
59             siteData
60         })
61     }
62     updateServiceTargetInput = (serviceName, key, e) => {
63         let state = this.state;
64         let siteData = this.state.siteData;
65         let value = e.target.value;
66         let index =  _.findIndex(state.siteData['target-endpoint'], function(o) {
67             return o.name == serviceName
68         });
69         if((index == undefined) || index == -1) {
70             index = siteData['target-endpoint'].push({name: serviceName}) - 1
71         }
72         siteData['target-endpoint'][index]['name'] = serviceName;
73         if(value.trim() == '') {
74             delete siteData['target-endpoint'][index][key];
75         } else {
76             siteData['target-endpoint'][index][key] = value
77         }
78         this.actions.handleUpdateInput({
79             siteData
80         })
81     }
82     updateInstanceInput = (index, key, e) => {
83         let siteData = this.state.siteData;
84         siteData['rw-instances'][index][key] = e.target.value;
85         this.actions.handleUpdateInput({
86             siteData
87         })
88     }
89     updateInstanceInputEndpoint = (index, serviceName, key, e) => {
90         let siteData = this.state.siteData;
91         let state = this.state;
92         let value = e.target.value;
93         let listIndex =  _.findIndex(siteData['rw-instances'][index].endpoint, function(o) {
94          return o.name == serviceName
95         });
96         if(!siteData['rw-instances'][index].endpoint) {
97             siteData['rw-instances'][index].endpoint = []
98         }
99         if(listIndex == undefined || listIndex == -1) {
100             listIndex = siteData['rw-instances'][index].endpoint.push({name: serviceName}) - 1;
101         }
102          if(value.trim() == '') {
103             delete siteData['rw-instances'][index].endpoint[listIndex][key];
104         } else {
105             siteData['rw-instances'][index].endpoint[listIndex][key] = value
106         }
107         siteData['rw-instances'][index].endpoint[listIndex]['name'] = serviceName;
108         this.actions.handleUpdateInput({
109             siteData
110         })
111     }
112     addSite = () => {
113         this.actions.handleAddSite();
114     }
115     viewSite = (un, index) => {
116         this.actions.viewSite(un, index, true);
117     }
118     editSite = () => {
119         this.actions.editSite(false);
120     }
121     cancelEditSite = () => {
122         this.actions.editSite(true)
123     }
124     closePanel = () => {
125         this.actions.handleCloseSitePanel();
126     }
127
128     deleteSite = (e) => {
129         e.preventDefault();
130         e.stopPropagation();
131         if (confirm('Are you sure you want to delete this site?')) {
132             this.Store.deleteSite({
133                 'site-name': this.state.siteData['site-name']
134             });
135         }
136     }
137     createSite = (e) => {
138         let self = this;
139         e.preventDefault();
140         e.stopPropagation();
141         let siteData = self.state.siteData;
142         if (this.validateFields(self, siteData)) {
143             this.Store.createSite(siteData);
144         }
145     }
146     updateSite = (e) => {
147         let self = this;
148         e.preventDefault();
149         e.stopPropagation();
150         let siteData = self.state.siteData;
151         if (this.validateFields(self, siteData)) {
152             this.Store.updateSite(siteData);
153         }
154     }
155     validateFields(self, siteData) {
156         if (!siteData['site-name'] || siteData['site-name'].trim() == '') {
157             self.props.flux.actions.global.showNotification("Please enter a site name");
158             return false;
159         }
160         let instanceInvalid = false;
161         siteData['rw-instances'] && siteData['rw-instances'].map(function(rw) {
162             if (!rw['rwinstance-id'] || rw['rwinstance-id'].trim() == '' ) {
163                 instanceInvalid = true;;
164             }
165         });
166         if (instanceInvalid) {
167             self.props.flux.actions.global.showNotification("One or more of your RIFT.WARE Instances is missing it's FQDN/IP Address");
168             return false;
169         }
170         return true;
171     }
172      evaluateSubmit = (e) => {
173         if (e.keyCode == 13) {
174             if (this.props.isEdit) {
175                 this.updateSite(e);
176             } else {
177                 this.createSite(e);
178             }
179             e.preventDefault();
180             e.stopPropagation();
181         }
182     }
183     onTransitionEnd = (e) => {
184         this.actions.handleHideColumns(e);
185         console.log('transition end')
186     }
187     render() {
188         let self = this;
189         let html;
190         let props = this.props;
191         let state = this.state;
192         let passwordSectionHTML = null;
193         let formButtonsHTML = (
194                 <ButtonGroup className="buttonGroup">
195                     <Button label="EDIT" type="submit" onClick={this.editSite} />
196                 </ButtonGroup>
197         );
198         if(!this.state.isReadOnly) {
199             formButtonsHTML = (
200                                 state.isEdit ?
201                                 (
202                                     <ButtonGroup className="buttonGroup">
203                                         <Button label="Update" type="submit" onClick={this.updateSite} />
204                                         <Button label="Delete" onClick={this.deleteSite} />
205                                         <Button label="Cancel" onClick={this.cancelEditSite} />
206                                     </ButtonGroup>
207                                 )
208                                 : (
209                                     <ButtonGroup className="buttonGroup">
210                                         <Button label="Create" type="submit" onClick={this.createSite}  />
211                                     </ButtonGroup>
212                                 )
213                             )
214         }
215
216         html = (
217         <PanelWrapper column>
218              <AppHeader nav={[{ name: 'SITES' }, { name: 'CONFIG', onClick: this.context.router.push.bind(this, { pathname: '/config' }) }, { name: 'STATUS', onClick: this.context.router.push.bind(this, { pathname: '/status' }) }]} />
219             <PanelWrapper className={`row siteManagement ${!this.state.siteOpen ? 'siteList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
220
221                 <PanelWrapper ref={(div) => { this.siteList = div}} className={`column siteList expanded ${this.state.siteOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
222                     <Panel title="Sites" style={{marginBottom: 0}} no-corners>
223                         <div className="tableRow tableRow--header">
224                             <div className="siteName">
225                                 Site Name
226                             </div>
227                             <div>
228                                 # of Instances
229                             </div>
230                         </div>
231                         {state.sites && state.sites.map((u, k) => {
232                             return (
233                                 <div onClick={self.viewSite.bind(null, u, k)} ref={(el) => this[`site-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.siteOpen) ? 'tableRow--data-active' : ''}`} key={k}>
234                                     <div
235                                         className={`siteName siteName-header ${((self.state.activeIndex == k) && self.state.siteOpen) ? 'activeSite' : ''}`}
236                                         >
237                                         {u['site-name']}
238                                     </div>
239                                     <div>
240                                         {u['rw-instances'] && u['rw-instances'].length}
241                                     </div>
242
243                                 </div>
244                             )
245                         })}
246                     </Panel>
247                     <SkyquakeRBAC className="rbacButtonGroup">
248                         <ButtonGroup  className="buttonGroup">
249                             <Button label="Add Site" onClick={this.addSite} />
250                         </ButtonGroup>
251                     </SkyquakeRBAC>
252                 </PanelWrapper>
253                 <PanelWrapper onKeyUp={this.evaluateSubmit}
254                     className={`SiteAdmin column`}>
255                     <Panel
256                         title={state.isEdit ? state.siteData['site-name'] : 'Create Site'}
257                         style={{marginBottom: 0}}
258                         hasCloseButton={this.closePanel}
259                         no-corners>
260                         <FormSection title="SITE INFO">
261                             {
262                                 (state.isEditSite ||  state.isReadOnly) ?
263                                     <Input readonly={state.isReadOnly || this.state.isEdit} required label="Name" value={state.siteData['site-name']} onChange={this.updateInput.bind(null, 'site-name')} />
264                                     : null
265                             }
266                             <TextInput readonly={state.isReadOnly}
267                                 label='FQDN/IP Address'
268                                 pattern={state.siteIdPattern}
269                                 value={state.siteData['site-id']}
270                                 onChange={this.updateInput.bind(null, 'site-id')}
271                             />
272                         </FormSection>
273                         {
274                             <FormSection className="subSection" title="Service Endpoints">
275                                 <TextInput type="text" readonly={state.isReadOnly} onChange={self.updateServiceTargetInput.bind(self, 'ui-service', 'port')} label="UI Port" value={state.siteData['target-endpoint'] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'ui-service' })] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'ui-service' })].port} />
276                                 <TextInput type="text" readonly={state.isReadOnly} onChange={self.updateServiceTargetInput.bind(self, 'rest-service', 'port')} label="REST Port" value={state.siteData['target-endpoint'] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'rest-service' })] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'rest-service' })].port} />
277                             </FormSection>
278                         }
279                         {
280                             <FormSection title="RIFT.WARE INSTANCES">
281                                         {
282                                             state.siteData['rw-instances'] && state.siteData['rw-instances'].map(function(t, i) {
283                                                 return <div key={i} className="rwInstance">
284                                                     <h3>
285                                                         <span className="title">INSTANCE</span>
286                                                         {
287                                                             (state.isReadOnly) ? null :
288                                                             <span
289                                                                 onClick={self.actions.handleRemoveInstance.bind(null, {index: i})}
290                                                                 className="removeInput">
291                                                                     <img
292                                                                         src={imgRemove}
293                                                                         style={{marginBottom: '0px'}}/>
294                                                                     Remove
295                                                             </span>
296                                                         }
297                                                     </h3>
298                                                     <TextInput type="text"
299                                                             label="FQDN/IP Address"
300                                                             required
301                                                             pattern={state.siteIdPattern}
302                                                             readonly={!(t.isNew && (!state.isReadOnly))}
303                                                             onChange={self.updateInstanceInput.bind(self, i, 'rwinstance-id')}
304                                                             value={state.siteData['rw-instances'][i]['rwinstance-id']} />
305                                                     <TextInput type="text"
306                                                         label="Floating IP"
307                                                         readonly={state.isReadOnly}
308                                                         onChange={self.updateInstanceInput.bind(self, i, 'floating-ip')}
309                                                         value={state.siteData['rw-instances'][i]['floating-ip']} />
310                                                     <FormSection className="subSection" title="Service Endpoints">
311                                                         <TextInput type="text"
312                                                             label="UI Port"
313                                                             readonly={state.isReadOnly}
314                                                             onChange={self.updateInstanceInputEndpoint.bind(self, i, 'ui-service', 'port')}
315                                                             value={state.siteData['rw-instances'] && state.siteData['rw-instances'][i] && state.siteData['rw-instances'][i].endpoint && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'ui-service' })
316                                                             ] && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'ui-service' })
317                                                             ].port}
318                                                             />
319                                                         <TextInput type="text"
320                                                             label="REST Port"
321                                                             readonly={state.isReadOnly}
322                                                             onChange={self.updateInstanceInputEndpoint.bind(self, i,'rest-service', 'port')}
323                                                             value={state.siteData['rw-instances'] && state.siteData['rw-instances'][i] && state.siteData['rw-instances'][i].endpoint && state.siteData['rw-instances'][i].endpoint[
324                                                              _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'rest-service' })
325                                                             ] && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'rest-service' })
326                                                             ].port}
327                                                             />
328                                                     </FormSection>
329                                                 </div>
330                                             })
331                                         }
332                                         {
333                                                 (state.isReadOnly) ? null :
334                                                 <span onClick={self.actions.handleAddInstance} className="addInput"  ><img src={imgAdd} />Add Instance</span>
335                                          }
336                             </FormSection>
337                         }
338
339
340                     </Panel>
341                      <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} site={this.state.name} className="rbacButtonGroup">
342                         {formButtonsHTML}
343                      </SkyquakeRBAC>
344                 </PanelWrapper>
345             </PanelWrapper>
346         </PanelWrapper>
347         );
348         return html;
349     }
350 }
351 // onClick={this.Store.update.bind(null, Account)}
352 SiteManagementDashboard.contextTypes = {
353     router: React.PropTypes.object,
354     userProfile: React.PropTypes.object
355 };
356
357 SiteManagementDashboard.defaultProps = {
358     siteList: [],
359     selectedSite: {}
360 }
361
362 export default SkyquakeComponent(SiteManagementDashboard);
363
364
365 function isElementInView(el) {
366     var rect = el && el.getBoundingClientRect() || {};
367
368     return (
369         rect.top >= 0 &&
370         rect.left >= 0 &&
371         rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
372         rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
373     );
374 }
375
376
377 // isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
378
379 class isDisabled extends React.Component {
380     constructor(props) {
381         super(props);
382     }
383     render() {
384         let props = this.props;
385         return (<div/>)
386     }
387 }
388
389 function showInput(e){
390   let target = e.target;
391   if(target.parentElement.classList.contains("addInput")) {
392     target = target.parentElement;
393   }
394   target.style.display = 'none';
395   target.parentElement.nextElementSibling.style.display = 'flex';
396   // e.target.parentElement.nextElementSibling.children[1].style.display = 'initial';
397 }
398 function hideInput(e){
399   let target = e.target;
400   if(target.parentElement.classList.contains("removeInput")) {
401     target = target.parentElement;
402   }
403   target.parentElement.style.display = 'none';
404   target.parentElement.previousElementSibling.children[1].style.display = 'inline';
405   target.previousSibling.value = '';
406 }
407
408