Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the...
[aai/sparky-fe.git] / src / app / model / history / History.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2021 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *       http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 import React, { Component } from 'react';
22 import { connect } from 'react-redux';
23
24 import commonApi from 'utils/CommonAPIService.js';
25 import deepDiffMapper from 'utils/DiffUtil.js';
26 import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
27 import Spinner from 'utils/SpinnerContainer.jsx';
28
29 import HistoryGallery from './components/HistoryGallery.jsx';
30 import HistoryCard from './components/HistoryCard.jsx';
31 import NodeDiffCard from './components/NodeDiffCard.jsx';
32 import AnimationControls from './components/AnimationControls.jsx';
33 import moment from "moment";
34 import Grid from 'react-bootstrap/lib/Grid';
35 import Row from 'react-bootstrap/lib/Row';
36 import Col from 'react-bootstrap/lib/Col';
37 import Button from 'react-bootstrap/lib/Button';
38 import Modal from 'react-bootstrap/lib/Modal';
39 import Pagination from 'react-js-pagination';
40 import { HistoryConstants } from './HistoryConstants';
41 import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
42 import Tooltip from 'react-bootstrap/lib/Tooltip';
43 import ReactBootstrapSlider from 'react-bootstrap-slider';
44
45 let INVLIST = GlobalExtConstants.INVLIST;
46 /**
47  * This class is used to handle any url interactions for models.
48  * When a user selects a inventory item in browse or special search,
49  * this model class should be used to handle the url + params and query
50  * the proxy server.
51  */
52
53 class History extends Component {
54
55   elements = [];
56   pageTitle = '';
57   nodeType = '';
58   nodeResults = '';
59   payload = {start : atob(this.props.match.params.nodeUriEnc)};
60
61   constructor(props) {
62     console.log(props);
63     super(props);
64     this.state = {
65                   activePage: 1,
66                   totalResults: 0,
67                   enableBusyFeedback: true,
68                   enableBusyRecentFeedback: true,
69                   enableBusyHistoryStateFeedback: true,
70                   enableBusyDiffFeedback: true,
71                   data: [],
72                   nodes: [],
73                   nodeCurrentState: null,
74                   nodeHistoryState: null,
75                   showHistoryModal: false,
76                   splitScreenCard: false,
77                   entries: [],
78                   filteredEntries: [],
79                   isLifeCycle: false,
80                   isState: false,
81                   currentStateHistoryValue: parseInt(this.props.match.params.epochTime),
82                   stepEpochStateTime: 1000,
83                   maxEpochStartTime: Math.round(new Date().getTime()),
84                   minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
85                   nodeDiff: null,
86                   showSlider: false,
87                   sliderTickArray: null,
88                   showTicks: INVLIST.showTicks,
89                   selectedHistoryStateFormatted: moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A'),
90                   nodeDisplay: (this.props.match.params.nodeType).toUpperCase(),// + ' : ' + (atob(this.props.match.params.nodeUriEnc)).split(this.props.match.params.nodeType + '\/').pop(),
91                   nodeName: (this.props.match.params.nodeType).toUpperCase(),
92                   historyErrMsg: null,
93                   changesErrMsg: null,
94                   lifecycleErrMsg: null,
95                   currentErrMsg: null
96                 };
97                 console.log('minEpochStateTime: '+this.state.minEpochStateTime);
98                 console.log('maxEpochStartTime: '+this.state.maxEpochStartTime);
99                 console.log('stepEpochStateTime: '+this.state.stepEpochStateTime);
100                 console.log('currentStateHistoryValue: '+this.state.currentStateHistoryValue);
101
102   }
103   resultsMessage = '';
104   componentDidMount = () => {
105     console.log('[History.jsx] componentDidMount props available are', JSON.stringify(this.props));
106     if(INVLIST.isHistoryEnabled){
107         this.beforefetchInventoryData();
108     }
109   };
110   componentWillUnmount  = () => {
111     console.log('[History.jsx] componentWillUnMount');
112   }
113   getUnixSecondsFromMs = (ms) => {
114     return ms/1000;
115   }
116   beforefetchInventoryData = (param) => {
117     if (param) {
118       this.setState(
119         { enableBusyFeedback: true, activePage: 1, totalResults: 0},
120         function () { this.fetchInventoryData(param); }.bind(this)
121       );
122     } else {
123       this.fetchInventoryData();
124     }
125   };
126     initState = () =>{
127         this.setState({
128               activePage: 1,
129               totalResults: 0,
130               enableBusyFeedback: true,
131               enableBusyRecentFeedback: true,
132               enableBusyHistoryStateFeedback: true,
133               enableBusyDiffFeedback: true,
134               data: [],
135               nodes: [],
136               nodeCurrentState: null,
137               nodeHistoryState: null,
138               showHistoryModal: false,
139               splitScreenCard: false,
140               entries: [],
141               filteredEntries: [],
142               isLifeCycle: false,
143               isState: false,
144               stepEpochStateTime: 1000,
145               maxEpochStartTime: Math.round(new Date().getTime()),
146               minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
147               nodeDiff: null,
148               showSlider: false,
149               sliderTickArray: null,
150               showTicks: INVLIST.showTicks,
151               nodeDisplay: (this.props.match.params.nodeType).toUpperCase(),// + ' : ' + (atob(this.props.match.params.nodeUriEnc)).split(this.props.match.params.nodeType + '\/').pop(),
152               nodeName: (this.props.match.params.nodeType).toUpperCase(),
153               historyErrMsg: null,
154               changesErrMsg: null,
155               lifecycleErrMsg: null,
156               currentErrMsg: null
157             });
158       }
159   getSettings = () => {
160     const settings = {
161           'NODESERVER': INVLIST.NODESERVER,
162           'PROXY': INVLIST.PROXY,
163           'PREFIX': INVLIST.PREFIX,
164           'VERSION': INVLIST.VERSION,
165           'USESTUBS': INVLIST.useStubs
166         };
167     return settings;
168   }
169   fetchInventoryData = (param) => {
170     console.log('fetchInventoryData', param);
171     this.resultsMessage = '';
172     let settings = this.getSettings();
173     const inventory = INVLIST.INVENTORYLIST;
174     let url = '';
175     console.log('[History.jsx] fetchInventoryData nodeUriEnc= ', atob(this.props.match.params.nodeUriEnc));
176     let pageName = "History";
177     this.nodeResults = '';
178     switch(this.props.match.params.type){
179         case('nodeState'):
180             this.setState({splitScreenCard: true, isState: true, showState: true});
181             this.getCurrentStateCall(settings);
182             this.pageTitle = "State of "+ this.state.nodeDisplay +" at "+ moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A');
183             break;
184         case('nodeLifeCycleSince'):            
185             this.setState({splitScreenCard: false, isLifeCycle: true, showLifeCycle: true});
186             this.getCurrentStateCall(settings);
187             this.commonApiServiceCall(settings, null);
188             this.pageTitle = "Network element state(s) of "+  this.state.nodeDisplay;
189             break;
190         case('nodeLifeCycle'):
191             this.setState({splitScreenCard: false, isLifeCycle: true, showLifeCycle: true});
192             this.getCurrentStateCall(settings);
193             this.commonApiServiceCall(settings, null);
194             this.pageTitle = "Network element state(s) of "+  this.state.nodeDisplay;            
195             break;
196         default:
197             this.pageTitle = "History";
198             this.setState({splitScreenCard: false, isLifeCycle: true, showLifeCycle: true});
199             this.getCurrentStateCall(settings);
200             this.commonApiServiceCall(settings, null);
201     }
202     console.log('[History.jsx] active page', this.state.activePage);
203   };
204   generateEntries = (properties, relationships, actions) =>{
205     let tempEntries = [];
206     if(properties){
207         for (var i = 0; i < properties.length; i++) {
208             properties[i].displayTimestamp =  moment(properties[i].timestamp).format('dddd, MMMM Do, YYYY h:mm:ss A');
209             properties[i].timeRank = properties[i].timestamp;
210             properties[i].type = "attribute";
211             properties[i].header = "Attribute: " + properties[i].key;
212             if(properties[i].value !== null && properties[i].value !== 'null'){
213                 properties[i].action = "Updated";
214                 properties[i].body   = "Updated to value: " + properties[i].value;
215             }else{
216                 properties[i].action = "Deleted";
217                 properties[i].body   = "Removed";
218             }
219             properties[i]['tx-id'] = (properties[i]['tx-id']) ? properties[i]['tx-id'] : 'N/A';
220             tempEntries.push(properties[i]);
221         }
222     }
223     if(actions){
224         for (var k = 0; k < actions.length; k++) {
225             actions[k].displayTimestamp =  moment(actions[k].timestamp).format('dddd, MMMM Do, YYYY h:mm:ss A');
226             actions[k].timeRank = actions[k].timestamp;
227             actions[k].type = "action";
228             actions[k].header = "Action: " + actions[k].action;
229             if(actions[k].action === 'CREATED'){
230                 actions[k].action = "Created";
231                 actions[k].body   = "Network Element Created";
232             }else if(actions[k].action === 'DELETED'){
233                 actions[k].action = "Deleted";
234                 actions[k].body   = "Network Element Removed";
235             }
236             actions[k]['tx-id'] = (actions[k]['tx-id']) ? actions[k]['tx-id'] : 'N/A';
237             tempEntries.push(actions[k]);
238         }
239     }
240     if(relationships){
241         for (var j = 0; j < relationships.length; j++) {
242             if(relationships[j].timestamp){
243                 relationships[j].dbStartTime = relationships[j].timestamp;
244                 relationships[j].displayTimestamp =  moment(relationships[j].dbStartTime).format('dddd, MMMM Do, YYYY h:mm:ss A');
245                 relationships[j].timeRank = relationships[j].dbStartTime;
246                 relationships[j].type   = "relationship";
247                 relationships[j].header = "Relationship added";
248                 relationships[j].body   = "The relationship with label " + relationships[j]['relationship-label'] + " was added from this node to the "+ relationships[j]['node-type'] +" at " + relationships[j].url;
249                 relationships[j].action = "Added";
250                 relationships[j]['tx-id'] = (relationships[j]['tx-id']) ? relationships[j]['tx-id'] : 'N/A';
251                 let additions = JSON.parse(JSON.stringify(relationships[j]));
252                 tempEntries.push(additions);
253             }
254             if(relationships[j]['end-timestamp']){
255                  relationships[j].dbEndTime = relationships[j]['end-timestamp'];
256                  relationships[j].sot = relationships[j]['end-sot'];
257                  relationships[j].displayTimestamp =  moment(relationships[j].dbEndTime).format('dddd, MMMM Do, YYYY h:mm:ss A');
258                  relationships[j].timeRank = relationships[j].dbEndTime;
259                  relationships[j].header = "Relationship removed";
260                  relationships[j].body   = "The " + relationships[j]['node-type'] +" : " + relationships[j].url + " relationship with label " + relationships[j]['relationship-label'] + " was removed from this node.";
261                  relationships[j].type   = "relationship";
262                  relationships[j].action = "Deleted";
263                  relationships[j]['tx-id'] = (relationships[j]['tx-id']) ? relationships[j]['tx-id'] : 'N/A';
264                  let deletions = JSON.parse(JSON.stringify(relationships[j]));
265                  tempEntries.push(deletions);
266             }
267         }
268     }
269
270     let tempEntriesSorted = tempEntries.sort(function(a, b) {
271                                                        var compareA = a.timeRank;
272                                                        var compareB = b.timeRank;
273                                                        if(compareA > compareB) return -1;
274                                                        if(compareA < compareB) return 1;
275                                                        return 0;
276                                                       });
277
278     this.setState({
279                 totalResults : tempEntriesSorted.length,
280                 entries : tempEntriesSorted,
281                 filteredEntries: tempEntriesSorted
282               });
283
284   }
285   triggerHistoryStateCall = (nodeUri, epochTime) =>{
286      //get url for historical call
287      let settings = this.getSettings();
288      window.scrollTo(0, 400);
289       this.setState({
290                      enableBusyHistoryStateFeedback : true,
291                      enableBusyDiffFeedback: true
292                    });
293      this.getHistoryStateCall(settings, null, null, epochTime);
294   }
295   generateDiffArray = (arr) => {
296     let tempArray = {};
297     tempArray['properties'] = [];
298     tempArray['related-to'] = [];
299
300     for (var i = 0; i < arr.properties.length; i++ ){
301         tempArray['properties'][arr.properties[i].key] = arr.properties[i];
302     }
303     for (var j = 0; j < arr['related-to'].length; j++ ){
304         //TODO use id if it is coming
305         tempArray['related-to'][arr['related-to'][j].url] = arr['related-to'][j];
306     }
307     return tempArray;
308   }
309   getCurrentStateCall = (settings,url,param) =>{
310       this.setState({currentErrMsg:null});
311       commonApi(settings, "query?format=state", 'PUT', this.payload, 'currentNodeState', null, 'history-traversal')
312       .then(res => {
313         let node = atob(this.props.match.params.nodeUriEnc).split(res.data.results[0]['node-type'] + '\/').pop();
314         res.data.results[0].primaryHeader = 'Current state of ' + this.state.nodeName + ' - ' + node;
315         res.data.results[0].secondaryHeader = atob(this.props.match.params.nodeUriEnc);
316         this.setState(
317         {
318           nodeCurrentState : res.data.results[0],
319           enableBusyRecentFeedback: false,
320           nodeDisplay :node
321         });
322         if(this.props.match.params.type === 'nodeState'){
323             this.getHistoryStateCall(settings, null);
324         }
325         console.log('After recent node service call ......',this.state);
326         console.log('[History.jsx] recent node results : ',  res.data.results[0]);
327         }, error=>{
328           this.triggerError(error, "current");
329         }).catch(error => {
330           this.triggerError(error, 'current');
331         });
332   };
333   getHistoryStateCall = (settings, url, param, timeStamp) =>{
334       let ts = this.state.currentStateHistoryValue;
335       if(timeStamp){
336         ts = parseInt(timeStamp);
337       }
338       if(this.state.showTicks){
339         this.setState({changesErrMsg:null});
340         commonApi(settings, "query?format=changes", 'PUT', this.payload, 'historicalNodeStateChanges', null, 'history-traversal')
341         .then(res => {
342           let tickTempArray = [];
343           for(var j = 0; j <  res.data.results.length; j++ ){
344               for(var k = 0; k < res.data.results[j].changes.length; k++){
345                   if(!tickTempArray.includes(res.data.results[j].changes[k])){
346                       tickTempArray.push(res.data.results[j].changes[k]);
347                   }
348               }
349           }
350           let tickArray = tickTempArray.sort(function(a, b) {
351                                                                  var compareA = a;
352                                                                  var compareB = b;
353                                                                  if(compareA < compareB) return -1;
354                                                                  if(compareA > compareB) return 1;
355                                                                  return 0;
356                                                                 });
357           console.log("tick array: " + tickArray);
358           this.setState({showSlider:true, sliderTickArray: tickArray});
359         }, error=>{
360           this.triggerError(error, "changes");
361         }).catch(error => {
362           this.triggerError(error, 'changes');
363         });
364       }else{
365         this.setState({showSlider:true});
366       }
367       this.setState({historyErrMsg:null});
368       commonApi(settings, "query?format=state&startTs=" + ts + "&endTs=" + ts, 'PUT', this.payload, 'historicalNodeState', null, 'history-traversal')
369       .then(res => {
370          let node = atob(this.props.match.params.nodeUriEnc).split(res.data.results[0]['node-type'] + '\/').pop();
371          res.data.results[0].primaryHeader = 'Historical state of '+ this.state.nodeName + ' - ' + node + ' as of ' + moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A');
372          res.data.results[0].secondaryHeader = atob(this.props.match.params.nodeUriEnc);
373         this.setState(
374         {
375           showState: true,
376           splitScreenCard: true,
377           nodeHistoryState :  res.data.results[0],
378           enableBusyHistoryStateFeedback: false
379         });
380         console.log('After historical node state service call ......',this.state);
381         console.log('[History.jsx] historical node state results : ',  res.data.results[0]);
382         if(this.state.nodeHistoryState != null && this.state.nodeCurrentState != null){
383                     let nodeDiffHistoryArr = this.generateDiffArray(this.state.nodeHistoryState);
384                     let nodeDiffCurrentArr = this.generateDiffArray(this.state.nodeCurrentState);
385                     var result = deepDiffMapper.map(nodeDiffHistoryArr, nodeDiffCurrentArr);
386                     console.log("diff map" + result);
387                     this.setState({ nodeDiff: result, enableBusyDiffFeedback: false });
388                 }else{
389                     this.setState({ enableBusyDiffFeedback: false });
390          }
391       }, error=>{
392           this.triggerError(error, "historic");
393       }).catch(error => {
394           this.triggerError(error, 'historic');
395       });
396
397   };
398
399   commonApiServiceCall = (settings,url,param) =>{
400     let path = "query?format=lifecycle";
401     let stubPath = "nodeLifeCycle";
402     if(this.props.match.params.type === "nodeLifeCycleSince"){
403         path += "&startTs=" + parseInt(this.props.match.params.epochTime);
404         stubPath += "Since";
405     }
406     this.setState({lifecycleErrMsg:null});
407     commonApi(settings, path, 'PUT', this.payload, stubPath, null, 'history-traversal')
408       .then(res => {
409         // Call dispatcher to update state
410         console.log('once before service call ......',this.state);
411         let resp =  res.data.results[0];
412         this.resultsMessage = '';
413         let totalResults = 0;
414         if(resp && resp.properties.length + resp['related-to'].length > 0){
415             if(this.props.match.params.type === "nodeState"){
416                 totalResults = 1;
417             }else{
418                 //wait to generate entries to set this
419                 totalResults = 0;
420             }
421         }
422         this.setState(
423           {
424             totalResults : totalResults,
425             enableBusyFeedback:false,
426           });
427         if(resp){
428             this.generateEntries(resp.properties, resp['related-to'], resp['node-actions']);
429         }
430         console.log('After service call ......',this.state);
431         console.log('[History.jsx] results : ', resp);
432       }, error=>{
433         this.triggerError(error, 'lifecycle');
434       }).catch(error => {
435         this.triggerError(error, 'lifecycle');
436       });
437   };
438   triggerError = (error, type) => {
439       console.error('[History.jsx] error : ', JSON.stringify(error));
440         let errMsg = '';
441         if (error.response) {
442                 // The request was made and the server responded with a status code
443                 // that falls out of the range of 2xx
444                 console.log(error.response.data);
445                 console.log(error.response.status);
446                 console.log(error.response.headers);
447                 if(error.response.status){
448                     errMsg += " Code: " + error.response.status;
449                 }
450                 if(error.response.data){
451               errMsg += " - " + JSON.stringify(error.response.data);
452           }
453         } else if (error.request) {
454                 // The request was made but no response was received
455                 // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
456                 // http.ClientRequest in node.js
457                 console.log(error.request);
458                 errMsg += " - Request was made but no response received";
459         } else {
460                 // Something happened in setting up the request that triggered an Error
461                 console.log('Error', error.message);
462                 errMsg += " - Unknown error occurred " + error.message;
463         }
464         //Suppress 404's because that is just no results
465         if(error.response && error.response.status === 404){
466            errMsg = '';
467         }
468         if(type === 'lifecycle'){
469              this.setState({
470                     lifecycleErrMsg: errMsg,
471                     enableBusyFeedback: false,
472                     totalResults: 0
473                   });
474         }else if (type === 'changes'){
475              this.setState({
476                     showSlider:true,
477                     changesErrMsg: errMsg
478              });
479         }else if (type === 'historic'){
480             console.log('[History.jsx] historical node state error : ', error);
481             this.setState(
482             {
483               showState: true,
484               splitScreenCard: true,
485               nodeHistoryState : null,
486               enableBusyHistoryStateFeedback:false,
487               enableBusyDiffFeedback:false,
488               historyErrMsg: errMsg
489             });
490         }else if (type === 'current'){
491             this.setState(
492             {
493               nodeCurrentState : null,
494               currentErrMsg: errMsg,
495               enableBusyRecentFeedback:false
496             });
497         }else{
498             console.log('[History.jsx] triggerError method called without a type.' );
499         }
500     }
501
502   componentWillReceiveProps(nextProps) {
503     console.log('[History.jsx] componentWillReceiveProps');
504     console.log('[History.jsx] next nodeUri:', atob(nextProps.match.params.nodeUriEnc));
505     console.log('[History.jsx] this nodeUri:', atob(this.props.match.params.nodeUriEnc));
506
507     if (nextProps.match.params.nodeUriEnc
508             && nextProps.match.params.type
509             && nextProps.match.params.epochTime
510             && ((nextProps.match.params.nodeUriEnc !== this.props.match.params.nodeUriEnc) ||
511                 (nextProps.match.params.type !== this.props.match.params.type) ||
512                 (nextProps.match.params.epochTime !== this.props.match.params.epochTime))
513     ) {
514       this.initState();
515       this.props = nextProps;
516       this.beforefetchInventoryData();
517     }
518   };
519
520   handlePageChange = (pageNumber) => {
521     console.log('[History.jsx] HandelPageChange active page is', pageNumber);
522     this.setState(
523       { activePage: pageNumber, enableBusyFeedback: true },
524       function () { this.beforefetchInventoryData(); }.bind(this)
525     );
526   };
527
528   // HELPER FUNCTIONS
529   isContaining = (nameKey, listArray) => {
530     let found = false;
531     listArray.map((lists) => {
532       if (lists.id === nameKey) {
533         found = true;
534       }
535     });
536     return found;
537   };
538
539   stateHistoryFormat = (event) =>{
540      this.setState({ currentStateHistoryValue: event.target.value, selectedHistoryStateFormatted: moment(event.target.value).format('dddd, MMMM Do, YYYY h:mm:ss A')});
541   };
542   changeHistoryState = () =>{
543     console.log('minEpochStateTime: ' + this.state.minEpochStateTime);
544     console.log('maxEpochStartTime: ' + this.state.maxEpochStartTime);
545     console.log('stepEpochStateTime: ' + this.state.stepEpochStateTime);
546     console.log('currentStateHistoryValue: ' + this.state.currentStateHistoryValue);
547     console.log("Calling the route again with a new timestamp");
548     this.props.history.push('/history/' + this.props.match.params.type + '/' + this.props.match.params.nodeType + '/' + this.props.match.params.nodeUriEnc + '/' + this.state.currentStateHistoryValue);
549   }
550   filterList = (event) =>{
551      var updatedList = this.state.entries;
552      updatedList = updatedList.filter((entry) =>{
553        return JSON.stringify(entry).toLowerCase().search(
554          event.target.value.toLowerCase()) !== -1;
555      });
556      this.setState({filteredEntries: updatedList, totalResults: updatedList.length});
557    }
558
559    setHistoricStateValues = (currValue) =>{
560      this.setState({currentStateHistoryValue: currValue, selectedHistoryStateFormatted: moment(currValue).format('dddd, MMMM Do, YYYY h:mm:ss A')});
561    }
562
563    navigateHistory = (time) =>{
564      this.props.history.push('/history/' + this.props.match.params.type + '/' + this.props.match.params.nodeType + '/' + this.props.match.params.nodeUriEnc + '/' + time);
565    }
566
567    setStateValue = (key, value) =>{
568       this.setState((state) => { key : value });
569    }
570    getStateValue = (stateVar) => {
571      return this.state[stateVar];
572    }
573
574   render() {
575     console.log('[History Props] render: ', JSON.stringify(this.props) + 'elements : ', this.elements);
576     console.log('[History nodeUri] render: ', atob(this.props.match.params.nodeUriEnc));
577     if(INVLIST.isHistoryEnabled){
578         return (
579           <div>
580           <header className='addPadding jumbotron my-4'>
581             <h1 className='display-2'>Network Element History</h1>
582             <p className='lead'>
583               On this page you have the ability to view a network element in its current and historic state.
584               The attributes are clickable to view extended information about who and when they were last updated.
585               {this.props.match.params.type === "nodeLifeCycle" || this.props.match.params.type === "nodeLifeCycleSince"
586               ? 'The table at the bottom of the page shows a list of all updates made on the network element (it is filterable).\n' +
587                 'Click an update in that table to rebuild the historic state at the time of the update.\n' +
588                 'A difference will be displayed between the current state and the historic state in the center.' : ''}
589             </p>
590           </header>
591           <Grid fluid={true} className='addPadding'>
592             <Row>
593               <Col className='col-lg-12'>
594                     <div className='card d3-model-card'>
595                       <div className='card-header model-card-header'>
596                         <h2>{this.pageTitle}</h2>
597                       </div>
598                       <Row className={this.state.changesErrMsg ? 'show' : 'hidden'} >
599                         <div className='addPaddingTop alert alert-danger' role="alert">
600                             An error occurred while trying to get the state changes, please try again later. If this issue persists, please contact the system administrator. {this.state.changesErrMsg}
601                         </div>
602                       </Row>
603                       { (this.state.isState && this.state.showSlider && this.state.showTicks) && (
604                       <div className='card-header'>
605                         <Row className='show-grid'>
606                            <Col md={3}>
607                                <ReactBootstrapSlider
608                                      value={this.state.currentStateHistoryValue}
609                                      change={this.stateHistoryFormat}
610                                      slideStop={this.stateHistoryFormat}
611                                      step={ 1 }
612                                      ticks={ this.state.sliderTickArray }
613                                      ticks_snap_bounds={ 10000 }
614                                      orientation="horizontal" />
615                                    <p>{this.state.selectedHistoryStateFormatted}</p>
616                                    <button type='button' className='btn btn-outline-primary' onClick={this.changeHistoryState}>Refresh</button>
617                            </Col>
618                            <AnimationControls playControlsDisabled={true} get={this.getStateValue} set={this.setStateValue} tickArray={this.state.sliderTickArray} currentValue={this.state.currentStateHistoryValue} setValueState={this.setHistoricStateValues} setNavigate={this.navigateHistory} />
619                         </Row>
620                       </div>)}
621                       { (this.state.isState && this.state.showSlider && !this.state.showTicks) && (
622                       <div className='card-header'>
623                         <Row className='show-grid'>
624                            <Col md={12}>
625                                 <ReactBootstrapSlider
626                                       value={this.state.currentStateHistoryValue}
627                                       change={this.stateHistoryFormat}
628                                       slideStop={this.stateHistoryFormat}
629                                       step={this.state.stepEpochStateTime}
630                                       max={this.state.maxEpochStartTime}
631                                       min={this.state.minEpochStartTime}
632                                       orientation="horizontal" />
633                                    <p>{this.state.selectedHistoryStateFormatted}</p>
634                                    <button type='button' className='btn btn-outline-primary' onClick={this.changeHistoryState}>Refresh</button>
635                            </Col>
636                         </Row>
637                       </div>)}
638                       <div className='card-content model-card-content'>
639                         <div>
640                               <Row className='show-grid'>
641                                 <Col md={12}>
642                                   <Spinner loading={this.state.enableBusyHistoryStateFeedback && this.state.showState}>
643                                     {this.state.showState && !this.historyErrMsg && (<HistoryCard split={this.state.splitScreenCard} node={this.state.nodeHistoryState}/>)}
644                                     <Row className={this.state.historyErrMsg ? 'show' : 'hidden'} >
645                                       <div className='addPaddingTop alert alert-danger' role="alert">
646                                           An error occurred while trying to get the historic state, please try again later. If this issue persists, please contact the system administrator. {this.state.historyErrMsg}
647                                       </div>
648                                     </Row>
649                                   </Spinner>
650                                   <Spinner loading={this.state.showState && this.state.enableBusyDiffFeedback}>
651                                     {this.state.showState && (<NodeDiffCard diff={this.state.nodeDiff}/>)}
652                                   </Spinner>
653                                   <Spinner loading={this.state.enableBusyRecentFeedback}>
654                                     { !this.currentErrMsg && (<HistoryCard split={this.state.splitScreenCard} node={this.state.nodeCurrentState}/>)}
655                                     <Row className={this.state.currentErrMsg ? 'show' : 'hidden'} >
656                                       <div className='addPaddingTop alert alert-danger' role="alert">
657                                           An error occurred while trying to get the current state, please try again later. If this issue persists, please contact the system administrator. {this.state.currentErrMsg}
658                                       </div>
659                                     </Row>
660                                   </Spinner>
661                                   <hr />
662                                   <span className='resultMessage'>{this.resultsMessage}</span>
663                                 </Col>
664                               </Row>
665                         </div>
666                       </div>
667                       <div className='card-footer'>
668                          <strong>Tip:</strong> <em>Click any attribute to view more details</em>
669                       </div>
670                    </div>
671               </Col>
672             </Row>
673             <Row>
674                 <div className={'addPaddingTop alert alert-danger ' + (this.state.lifecycleErrMsg ? 'show' : 'hidden')} role="alert">
675                     An error occurred while trying to get the list of updates on the current node, please try again later. If this issue persists, please contact the system administrator. {this.state.lifecycleErrMsg}
676                 </div>
677             {this.state.showLifeCycle && !this.state.lifecycleErrMsg && (
678                <Col className='col-lg-12'>
679                      <div className='card d3-model-card'>
680                        <div className='card-header model-card-header'>
681                          <h2 className={this.props.match.params.type === "nodeLifeCycle" ? 'show' : 'hidden'}>All Updates on {this.state.nodeDisplay}</h2>
682                          <h2 className={this.props.match.params.type === "nodeLifeCycleSince" ? 'show' : 'hidden'}>All Updates on {this.state.nodeDisplay} Since {this.state.selectedHistoryStateFormatted}</h2>
683                        </div>
684                        <div className='card-header'>
685                             <p><strong>Tip:</strong> <em>Click any update to view the state of the node at that point in time</em></p>
686                             <div>
687                               <h5>Total Results: <strong>{this.state.totalResults}</strong></h5>
688                             </div>
689                             <div>
690                                 <form>
691                                     <fieldset className="form-group">
692                                         <input type="text" className="form-control form-control-lg" placeholder="Search" onChange={this.filterList}/>
693                                     </fieldset>
694                                 </form>
695                             </div>
696                        </div>
697                        <div className='card-content model-card-content'>
698                             <Spinner loading={this.state.enableBusyFeedback}>
699                               <div>
700                                   <HistoryGallery nodeId={atob(this.props.match.params.nodeUriEnc)} entries={this.state.filteredEntries} triggerState={this.triggerHistoryStateCall}/>
701                               </div>
702                             </Spinner>
703                        </div>
704                      </div>
705                 </Col> )}
706               </Row>
707           </Grid>
708           </div>
709         );
710      }else{
711         return(<p>History Not Enabled for this instance, please check config.</p>)
712      }
713    }
714 }
715
716 export default History;