RIFT-15726 - optimize download size -> lodash usage in UI
[osm/UI.git] / skyquake / plugins / logging / src / loggingGeneral.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 import React from 'react';
19 import _isEmpty from 'lodash/isEmpty';
20 import _find from 'lodash/find';
21 import _cloneDeep from 'lodash/cloneDeep';
22 import './logging.scss';
23
24 import Button from 'widgets/button/rw.button.js';
25 import DashboardCard from 'widgets/dashboard_card/dashboard_card.jsx';
26 import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
27
28 import LoggingActions from './loggingActions.js';
29 import LoggingStore from './loggingStore.js';
30 import LoggingNav from './loggingNav.jsx';
31
32 import CategorySeverityGrid from './categorySeverityGrid.jsx';
33 import DenyEventsEditGroup from './denyEventsEditGroup.jsx';
34 import {DropList, RadioButtonGroup, CardSection } from './loggingWidgets.jsx';
35
36 import Crouton from 'react-crouton';
37 import 'style/common.scss';
38
39
40 class DefaultCategorySeverityPanel extends React.Component {
41
42   render() {
43     const {defaultSeverities, severities, ...props } = this.props;
44     return (
45       <DashboardCard className="defaultCategorySeverityPanel"
46         showHeader={true}
47         title="Syslog Category Severity">
48         <CategorySeverityGrid defaultSeverities={defaultSeverities}
49           severityOptions={severities}/>
50       </DashboardCard>
51     );
52   }
53 }
54
55
56 class LoggingEventsPanel extends React.Component {
57
58   handleChangeAllowDuplicateEvents(e) {
59     console.log("LoggingEventsPanel.handleChangeAllowDuplicateEvents:");
60     console.log("- e.currentTarget.value=", e.currentTarget.value);
61     console.log("- e.currentTarget=", e.currentTarget);
62
63     // NOTE, we may want to generalize our string to boolean convert/compare
64     let allowFlag = (e.currentTarget.value == 'true');
65     LoggingStore.updateAllowDuplicateEvents(allowFlag);
66   }
67
68   handleAddDenyEvent(e) {
69     LoggingStore.addDenyEvent(null);
70   }
71
72   render() {
73     const {allowDuplicateEvents, eventIDs, radioItems, ...props} = this.props;
74     let self = this;
75     let selectedIndex = allowDuplicateEvents ? 0 :1;
76
77     return (
78       <DashboardCard className="loggingEventsPanel"
79         showHeader={true}
80         title="Events">
81         <CardSection title={"Duplicates"}>
82           <div className="radioButtonGroupHeader">Allow duplicate events</div>
83           <RadioButtonGroup className="radioButtonGroup"
84             items={radioItems}
85             selectedItem={radioItems[selectedIndex]}
86             onChange={this.handleChangeAllowDuplicateEvents}
87             radioGroupName="allowDuplicateEvents"
88           />
89         </CardSection>
90         <CardSection title={"Deny"}>
91           <DenyEventsEditGroup className="denyEventsEditGroup"
92             eventIDs={eventIDs}
93             warnInvalidEventID={true}
94           />
95           <div className="plusButton" onClick={this.handleAddDenyEvent}>
96             <span className="oi" data-glyph="plus"
97                   title="Add event id" aria-hidden="true"></span>
98           </div>
99         </CardSection>
100       </DashboardCard>
101     );
102   }
103 }
104 LoggingEventsPanel.defaultProps = {
105   radioItems: [
106     { label: "Allow", value: true },
107     { label: "Deny", value: false }
108   ],
109   allowDuplicateEvents: false,
110   eventIDs: []
111 }
112
113 /**
114  *
115  */
116 class LoggingGeneralDetailsPanel extends React.Component {
117   constructor(props) {
118     super(props);
119     this.state = {
120       syslogViewerURL: props.syslogViewerURL
121     };
122   }
123
124   componentWillReceiveProps(props) {
125     this.setState({
126       syslogViewerURL: props.syslogViewerURL
127     });
128   }
129
130   handleUpdateDetailTextField(fieldName) {
131     let self = this;
132     return function(e) {
133       let state = {};
134       state[fieldName] = e.target.value;
135       self.setState(state);
136     }
137   }
138
139   handleUpdateSyslogViewerURL(e) {
140     LoggingStore.updateSyslogViewerURL(e.target.value);
141   }
142   handleOpenSysLogViewerURL(e) {
143     e.preventDefault();
144     window.open(this.state.syslogViewerURL);
145   }
146
147   render() {
148     return (
149       <DashboardCard className="loggingGeneralDetailsPanel"
150         showHeader={true}
151         title="SysLog Viewer">
152         <div className="section syslogViewerSection">
153           <label className="sectionLabel">Syslog Viewer URL</label>
154           <div className="syslogViewerControls">
155             <input className="textBox" type="text"
156               value={this.state.syslogViewerURL}
157               onBlur={this.handleUpdateSyslogViewerURL}
158               onChange={this.handleUpdateDetailTextField('syslogViewerURL')}
159               placeholder="syslogViewer URL"
160             />
161             <div className="goButton"
162               onClick={this.handleOpenSysLogViewerURL.bind(this)}>
163               <span>Go</span>
164               <span className="oi" data-glyph="arrow-right"
165                     title="Add event id" aria-hidden="true"></span>
166             </div>
167           </div>
168         </div>
169       </DashboardCard>
170     );
171   }
172 }
173 LoggingGeneralDetailsPanel.defaultProps = {
174   syslogViewerURL: ""
175 }
176
177
178
179 /**
180  *  Page level class renders the general logging config page of three panels:
181  * 1. Default severity per category
182  * 2. Events configuration
183  * 3. Syslog viewer setting, launch
184  */
185 export default class LoggingGeneral extends React.Component {
186
187   constructor(props) {
188     super(props);
189     this.state = LoggingStore.getState();
190     LoggingStore.listen(this.storeListener);
191     this.state.validateErrorEvent = 0;
192     this.state.validateErrorMsg = '';
193     this.state.isLoading = true;
194     this.state.showDumpStateButton = false;
195   }
196
197   storeListener = (state) => {
198     this.setState(state);
199   }
200
201   getData() {
202     LoggingStore.getLoggingConfig();
203     this.setState({
204       isLoading: _isEmpty(this.state.loggingConfig)
205     });
206   }
207   componentWillUnmount = () => {
208     LoggingStore.unlisten(this.storeListener);
209   }
210
211   componentDidMount() {
212     //console.log("LoggingGeneral.componentDidMount called");
213     this.getData();
214   }
215
216   componentDidUpdate() {
217     //console.log("LoggingGeneral.componentDidUpdate called");
218   }
219
220   handleSave = (formData, e) => {
221     e.preventDefault();
222
223     if (this.validateData()) {
224       this.setState({
225         isLoading: true
226       });
227       LoggingStore.updateLoggingConfig(
228          /* this.collectNulledCategories(
229             this.state.initialLoggingConfig,
230             this.state.loggingConfig),
231             this.removeCategoryNulls(
232             this.state.loggingConfig */
233         this.state.nulledCategories,
234         this.cleanupConfig(
235           this.state.loggingConfig
236         )
237       )
238     } else {
239       console.log("LoggingGeneral.handleSave failed validation");
240     }
241     this.context.router.push({pathname: ''});
242   }
243   // removeCategoryNulls(config) {
244   //   let cleanConfig = _cloneDeep(config);
245   //   let cleanSeverities = [];
246   //   config.defaultSeverities.map(function(d) {
247   //     if (d.severity) {
248   //       cleanSeverities.push(d);
249   //     }
250   //   });
251   //   cleanConfig.defaultSeverities = cleanSeverities;
252   //   return cleanConfig;
253   // }
254   cleanupConfig(config) {
255     let cleanConfig = _cloneDeep(config);
256     let cleanSeverities = [];
257     cleanConfig.defaultSeverities && cleanConfig.defaultSeverities.map((defSev) => {
258       if (defSev.severity) {
259         cleanSeverities.push(defSev);
260       }
261     });
262     cleanConfig.defaultSeverities = cleanSeverities;
263
264     return cleanConfig;
265   }
266   // collectNulledCategories(oldCat, newCat) {
267   //   let nulledCategories = [];
268   //   let newSeverities = newCat.defaultSeverities;
269   //   let oldSeverities = oldCat.defaultSeverities;
270   //   newSeverities.map(function(c, i) {
271   //     if(!c.severity) {
272   //       if(oldSeverities[i].severity) {
273   //         //verify that categories are the same
274   //         if(oldSeverities[i].category == c.category) {
275   //           nulledCategories.push({category: c.category})
276   //         }
277   //       }
278   //     }
279   //   });
280   //   return nulledCategories;
281   // }
282   validateData() {
283
284     function isEventIdValid(eventID) {
285       // Return true if null, empty string or a number, else return false
286       if (!eventID || eventID.length == 0) {
287         return true;
288       } else {
289         return (isNaN(+eventID)) ? false : true;
290       }
291     }
292
293     let invalidEventIDs = [];
294     this.state.loggingConfig.denyEventIDs.forEach(
295       function(eventID, index) {
296         if (!isEventIdValid(eventID)) {
297           invalidEventIDs.push({ eventID: eventID, index: index});
298         }
299       })
300     if (invalidEventIDs.length > 0) {
301       console.log("invalidEvents = ", invalidEventIDs);
302       if (invalidEventIDs.length == 1) {
303         let msg = 'There is ' + invalidEventIDs.length + ' invalid event ID';
304         this.validateError(msg);
305       } else {
306         let msg = 'There are ' + invalidEventIDs.length + ' invalid event IDs';
307         this.validateError(msg);
308       }
309       // How should we identify each invalid value?
310
311       return false;
312     } else {
313       return true;
314     }
315   }
316   handleCancel = (e) => {
317     console.log("LoggingGeneral.handleCancel clicked");
318     e.preventDefault();
319     // TODO: restore original state
320     LoggingStore.resetLoggingConfigData();
321     this.context.router.push({pathname: ''});
322
323   }
324
325   validateError = (msg) => {
326     this.setState({
327       validateErrorEvent: true,
328       validateErrorMsg: msg
329     });
330   }
331   validateReset = () => {
332     this.setState({
333       validateErrorEvent: false
334     });
335   }
336   returnCrouton = () => {
337     return <Crouton id={Date.now()}
338       message={this.state.validateErrorMsg}
339       type={'error'}
340       hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
341       onDismiss={this.validateReset}
342     />;
343   }
344   preventDefault = (e) => {
345     e.preventDefault();
346     e.stopPropagation();
347   }
348
349   renderDumpStateButton() {
350
351   }
352
353   render() {
354     var self = this;
355
356
357     let errorMessage = this.returnCrouton();
358
359     let syslogViewerURL = this.state.loggingConfig.syslogviewer;
360     let defaultSeverities = this.state.loggingConfig.defaultSeverities;
361     // NOTE: There are modifications to original code here
362     // for RIFT-14856 so that default severities map to syslog sink
363     
364     // Find first syslog sink with (WTF - no type on sinks!) name syslog.
365     let syslogSink = this.state.loggingConfig.sinks && _find(this.state.loggingConfig.sinks, {
366       name: 'syslog'
367     });
368     let defaultSyslogSeverities = [];
369
370     this.state.loggingConfig && this.state.loggingConfig.defaultSeverities && this.state.loggingConfig.defaultSeverities.map((defaultSeverity) => {
371       // Mapping between default categories and names inside a sink
372       let syslogFilterCategory = (syslogSink.filter && syslogSink.filter.category && _find(syslogSink.filter.category, {
373         name: defaultSeverity.category
374       })) || {
375         name: defaultSeverity.category,
376         severity: null
377       };
378       defaultSyslogSeverities.push(syslogFilterCategory);
379     });
380     let severities = this.state.loggingConfig.severities;
381     let allowDuplicateEvents = this.state.loggingConfig.allowDuplicateEvents;
382     let denyEventIDs = this.state.loggingConfig.denyEventIDs;
383
384     let dumpStateButton = (
385         <button name="dump_state"
386             onClick={this.handleDumpState}>Dump State</button>);
387
388
389     return (
390       <div className="app-body">
391         {
392           (this.state.showLoggingNav) ? <LoggingNav currentPage="General" /> : ''
393         }
394
395         <form className="loggingPage loggingGeneral"
396           onSubmit={this.preventDefault}>
397           {errorMessage}
398           <ScreenLoader show={this.state.isLoading}/>
399           <div className="panelContainer">
400             {/*<DefaultCategorySeverityPanel defaultSeverities={defaultSeverities}
401               severities={severities}
402               />*/}
403             <DefaultCategorySeverityPanel defaultSeverities={defaultSyslogSeverities}
404               severities={severities}
405               />
406             <LoggingEventsPanel allowDuplicateEvents={allowDuplicateEvents}
407               eventIDs={denyEventIDs} />
408             <LoggingGeneralDetailsPanel syslogViewerURL={syslogViewerURL} />
409           </div>
410           <div className="loggingPageFooter">
411             <div className="loggingformButtonGroup">
412               {
413                 (this.state.showDumpStateButton) ? dumpStateButton : ''
414               }
415               <Button className="cancel light" label="Reset"
416                 onClick={this.handleCancel} />
417               <Button className="save dark" role="button" label="Save"
418                 onClick={this.handleSave.bind(this, true)} />
419             </div>
420           </div>
421         </form>
422       </div>
423     );
424   }
425
426   // Dev and debug support
427   handleDumpState = (e) => {
428     console.log("State dump:");
429     console.log("event ids=", this.state.loggingConfig.denyEventIDs);
430     console.log("initial state=", this.state.initialLoggingConfig);
431     console.log("active state=", this.state.loggingConfig);
432   }
433 }
434 LoggingGeneral.contextTypes = {
435   router: React.PropTypes.object
436 }