2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 import React, { Component } from 'react';
22 import { connect } from 'react-redux';
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';
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';
45 let INVLIST = GlobalExtConstants.INVLIST;
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
53 class History extends Component {
59 payload = {start : atob(this.props.match.params.nodeUriEnc)};
67 enableBusyFeedback: true,
68 enableBusyRecentFeedback: true,
69 enableBusyHistoryStateFeedback: true,
70 enableBusyDiffFeedback: true,
73 nodeCurrentState: null,
74 nodeHistoryState: null,
75 showHistoryModal: false,
76 splitScreenCard: 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,
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(),
94 lifecycleErrMsg: null,
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);
104 componentDidMount = () => {
105 console.log('[History.jsx] componentDidMount props available are', JSON.stringify(this.props));
106 if(INVLIST.isHistoryEnabled){
107 this.beforefetchInventoryData();
110 componentWillUnmount = () => {
111 console.log('[History.jsx] componentWillUnMount');
113 getUnixSecondsFromMs = (ms) => {
116 beforefetchInventoryData = (param) => {
119 { enableBusyFeedback: true, activePage: 1, totalResults: 0},
120 function () { this.fetchInventoryData(param); }.bind(this)
123 this.fetchInventoryData();
130 enableBusyFeedback: true,
131 enableBusyRecentFeedback: true,
132 enableBusyHistoryStateFeedback: true,
133 enableBusyDiffFeedback: true,
136 nodeCurrentState: null,
137 nodeHistoryState: null,
138 showHistoryModal: false,
139 splitScreenCard: false,
144 stepEpochStateTime: 1000,
145 maxEpochStartTime: Math.round(new Date().getTime()),
146 minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
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(),
155 lifecycleErrMsg: null,
159 getSettings = () => {
161 'NODESERVER': INVLIST.NODESERVER,
162 'PROXY': INVLIST.PROXY,
163 'PREFIX': INVLIST.PREFIX,
164 'VERSION': INVLIST.VERSION,
165 'USESTUBS': INVLIST.useStubs
169 fetchInventoryData = (param) => {
170 console.log('fetchInventoryData', param);
171 this.resultsMessage = '';
172 let settings = this.getSettings();
173 const inventory = INVLIST.INVENTORYLIST;
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){
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');
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;
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;
197 this.pageTitle = "History";
198 this.setState({splitScreenCard: false, isLifeCycle: true, showLifeCycle: true});
199 this.getCurrentStateCall(settings);
200 this.commonApiServiceCall(settings, null);
202 console.log('[History.jsx] active page', this.state.activePage);
204 generateEntries = (properties, relationships, actions) =>{
205 let tempEntries = [];
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;
216 properties[i].action = "Deleted";
217 properties[i].body = "Removed";
219 properties[i]['tx-id'] = (properties[i]['tx-id']) ? properties[i]['tx-id'] : 'N/A';
220 tempEntries.push(properties[i]);
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";
236 actions[k]['tx-id'] = (actions[k]['tx-id']) ? actions[k]['tx-id'] : 'N/A';
237 tempEntries.push(actions[k]);
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);
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);
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;
279 totalResults : tempEntriesSorted.length,
280 entries : tempEntriesSorted,
281 filteredEntries: tempEntriesSorted
285 triggerHistoryStateCall = (nodeUri, epochTime) =>{
286 //get url for historical call
287 let settings = this.getSettings();
288 window.scrollTo(0, 400);
290 enableBusyHistoryStateFeedback : true,
291 enableBusyDiffFeedback: true
293 this.getHistoryStateCall(settings, null, null, epochTime);
295 generateDiffArray = (arr) => {
297 tempArray['properties'] = [];
298 tempArray['related-to'] = [];
300 for (var i = 0; i < arr.properties.length; i++ ){
301 tempArray['properties'][arr.properties[i].key] = arr.properties[i];
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];
309 getCurrentStateCall = (settings,url,param) =>{
310 this.setState({currentErrMsg:null});
311 commonApi(settings, "query?format=state", 'PUT', this.payload, 'currentNodeState', null, 'history-traversal')
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);
318 nodeCurrentState : res.data.results[0],
319 enableBusyRecentFeedback: false,
322 if(this.props.match.params.type === 'nodeState'){
323 this.getHistoryStateCall(settings, null);
325 console.log('After recent node service call ......',this.state);
326 console.log('[History.jsx] recent node results : ', res.data.results[0]);
328 this.triggerError(error, "current");
330 this.triggerError(error, 'current');
333 getHistoryStateCall = (settings, url, param, timeStamp) =>{
334 let ts = this.state.currentStateHistoryValue;
336 ts = parseInt(timeStamp);
338 if(this.state.showTicks){
339 this.setState({changesErrMsg:null});
340 commonApi(settings, "query?format=changes", 'PUT', this.payload, 'historicalNodeStateChanges', null, 'history-traversal')
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]);
350 let tickArray = tickTempArray.sort(function(a, b) {
353 if(compareA < compareB) return -1;
354 if(compareA > compareB) return 1;
357 console.log("tick array: " + tickArray);
358 this.setState({showSlider:true, sliderTickArray: tickArray});
360 this.triggerError(error, "changes");
362 this.triggerError(error, 'changes');
365 this.setState({showSlider:true});
367 this.setState({historyErrMsg:null});
368 commonApi(settings, "query?format=state&startTs=" + ts + "&endTs=" + ts, 'PUT', this.payload, 'historicalNodeState', null, 'history-traversal')
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);
376 splitScreenCard: true,
377 nodeHistoryState : res.data.results[0],
378 enableBusyHistoryStateFeedback: false
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 });
389 this.setState({ enableBusyDiffFeedback: false });
392 this.triggerError(error, "historic");
394 this.triggerError(error, 'historic');
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);
406 this.setState({lifecycleErrMsg:null});
407 commonApi(settings, path, 'PUT', this.payload, stubPath, null, 'history-traversal')
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"){
418 //wait to generate entries to set this
424 totalResults : totalResults,
425 enableBusyFeedback:false,
428 this.generateEntries(resp.properties, resp['related-to'], resp['node-actions']);
430 console.log('After service call ......',this.state);
431 console.log('[History.jsx] results : ', resp);
433 this.triggerError(error, 'lifecycle');
435 this.triggerError(error, 'lifecycle');
438 triggerError = (error, type) => {
439 console.error('[History.jsx] error : ', JSON.stringify(error));
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;
450 if(error.response.data){
451 errMsg += " - " + JSON.stringify(error.response.data);
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";
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;
464 //Suppress 404's because that is just no results
465 if(error.response && error.response.status === 404){
468 if(type === 'lifecycle'){
470 lifecycleErrMsg: errMsg,
471 enableBusyFeedback: false,
474 }else if (type === 'changes'){
477 changesErrMsg: errMsg
479 }else if (type === 'historic'){
480 console.log('[History.jsx] historical node state error : ', error);
484 splitScreenCard: true,
485 nodeHistoryState : null,
486 enableBusyHistoryStateFeedback:false,
487 enableBusyDiffFeedback:false,
488 historyErrMsg: errMsg
490 }else if (type === 'current'){
493 nodeCurrentState : null,
494 currentErrMsg: errMsg,
495 enableBusyRecentFeedback:false
498 console.log('[History.jsx] triggerError method called without a type.' );
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));
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))
515 this.props = nextProps;
516 this.beforefetchInventoryData();
520 handlePageChange = (pageNumber) => {
521 console.log('[History.jsx] HandelPageChange active page is', pageNumber);
523 { activePage: pageNumber, enableBusyFeedback: true },
524 function () { this.beforefetchInventoryData(); }.bind(this)
529 isContaining = (nameKey, listArray) => {
531 listArray.map((lists) => {
532 if (lists.id === nameKey) {
539 stateHistoryFormat = (event) =>{
540 this.setState({ currentStateHistoryValue: event.target.value, selectedHistoryStateFormatted: moment(event.target.value).format('dddd, MMMM Do, YYYY h:mm:ss A')});
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);
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;
556 this.setState({filteredEntries: updatedList, totalResults: updatedList.length});
559 setHistoricStateValues = (currValue) =>{
560 this.setState({currentStateHistoryValue: currValue, selectedHistoryStateFormatted: moment(currValue).format('dddd, MMMM Do, YYYY h:mm:ss A')});
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);
567 setStateValue = (key, value) =>{
568 this.setState((state) => { key : value });
570 getStateValue = (stateVar) => {
571 return this.state[stateVar];
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){
580 <header className='addPadding jumbotron my-4'>
581 <h1 className='display-2'>Network Element History</h1>
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.' : ''}
591 <Grid fluid={true} className='addPadding'>
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>
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}
603 { (this.state.isState && this.state.showSlider && this.state.showTicks) && (
604 <div className='card-header'>
605 <Row className='show-grid'>
607 <ReactBootstrapSlider
608 value={this.state.currentStateHistoryValue}
609 change={this.stateHistoryFormat}
610 slideStop={this.stateHistoryFormat}
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>
618 <AnimationControls playControlsDisabled={true} get={this.getStateValue} set={this.setStateValue} tickArray={this.state.sliderTickArray} currentValue={this.state.currentStateHistoryValue} setValueState={this.setHistoricStateValues} setNavigate={this.navigateHistory} />
621 { (this.state.isState && this.state.showSlider && !this.state.showTicks) && (
622 <div className='card-header'>
623 <Row className='show-grid'>
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>
638 <div className='card-content model-card-content'>
640 <Row className='show-grid'>
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}
650 <Spinner loading={this.state.showState && this.state.enableBusyDiffFeedback}>
651 {this.state.showState && (<NodeDiffCard diff={this.state.nodeDiff}/>)}
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}
662 <span className='resultMessage'>{this.resultsMessage}</span>
667 <div className='card-footer'>
668 <strong>Tip:</strong> <em>Click any attribute to view more details</em>
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}
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>
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>
687 <h5>Total Results: <strong>{this.state.totalResults}</strong></h5>
691 <fieldset className="form-group">
692 <input type="text" className="form-control form-control-lg" placeholder="Search" onChange={this.filterList}/>
697 <div className='card-content model-card-content'>
698 <Spinner loading={this.state.enableBusyFeedback}>
700 <HistoryGallery nodeId={atob(this.props.match.params.nodeUriEnc)} entries={this.state.filteredEntries} triggerState={this.triggerHistoryStateCall}/>
711 return(<p>History Not Enabled for this instance, please check config.</p>)
716 export default History;