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';
28 import moment from "moment";
29 import Grid from 'react-bootstrap/lib/Grid';
30 import Row from 'react-bootstrap/lib/Row';
31 import Col from 'react-bootstrap/lib/Col';
32 import Button from 'react-bootstrap/lib/Button';
33 import Modal from 'react-bootstrap/lib/Modal';
34 import * as d3 from "d3";
35 import 'd3-selection-multi';
36 import ReactBootstrapSlider from 'react-bootstrap-slider';
37 import HistoryCard from './components/HistoryCard.jsx';
38 import NodeDiffCard from './components/NodeDiffCard.jsx';
39 import AnimationControls from './components/AnimationControls.jsx';
40 import TopologyDiffCard from './components/TopologyDiffCard.jsx';
41 import OutputVisualization, {Visualization} from 'generic-components/OutputVisualization.jsx';
45 let INVLIST = GlobalExtConstants.INVLIST;
48 * This class is to show visualizations of queries and show a historical state
51 class HistoryQuery extends Component {
53 svgWidth = window.outerWidth * 0.8;
64 enableBusyRecentFeedback: true,
65 enableBusyHistoryStateFeedback: true,
66 enableBusyDiffFeedback: true,
69 payload: ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
70 topologyCurrentState: null,
71 topologyHistoryState: null,
72 currentStateHistoryValue: parseInt(this.props.match.params.epochTime),
73 stepEpochStateTime: 1000,
74 maxEpochStartTime: Math.round(new Date().getTime()),
75 minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
77 selectedHistoryStateFormatted: moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A'),
78 queryDisplay: ((this.props.match.params.type == 'cq') ? JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
79 currentGraphNodes: [],
80 currentGraphLinks: [],
81 historicGraphNodes: [],
82 historicGraphLinks: [],
83 selectedNodeHistoryState: null,
84 selectedNodeCurrentState: null,
86 showTopologyDiffModal: false,
90 sliderTickArray: null,
91 showTicks: INVLIST.showTicks,
92 rawMappedCurrentState: null,
93 rawMappedHistoricState: null,
103 console.log('minEpochStateTime: '+this.state.minEpochStateTime);
104 console.log('maxEpochStartTime: '+this.state.maxEpochStartTime);
105 console.log('stepEpochStateTime: '+this.state.stepEpochStateTime);
106 console.log('currentStateHistoryValue: '+this.state.currentStateHistoryValue);
109 componentDidMount = () => {
110 console.log('[HistoryQuery.jsx] componentDidMount props available are', JSON.stringify(this.props));
111 if(INVLIST.isHistoryEnabled){
112 this.getCurrentStateCall(this.getSettings());
116 componentWillUnmount = () => {
117 console.log('[History.jsx] componentWillUnMount');
119 beforefetchInventoryData = (param) => {
123 this.fetchInventoryData();
129 enableBusyRecentFeedback: true,
130 enableBusyHistoryStateFeedback: true,
131 enableBusyDiffFeedback: true,
134 payload : ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
135 topologyHistoryState: null,
136 topologyCurrentState: null,
137 stepEpochStateTime: 1000,
138 maxEpochStartTime: Math.round(new Date().getTime()),
139 minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
141 queryDisplay: ((this.props.match.params.type == 'cq') ? JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
142 currentGraphNodes: [],
143 currentGraphLinks: [],
144 historicGraphNodes: [],
145 historicGraphLinks: [],
146 selectedNodeHistoryState: null,
147 selectedNodeCurrentState: null,
148 showNodeModal: false,
149 showTopologyDiffModal: false,
153 sliderTickArray: null,
154 rawMappedCurrentState: null,
155 rawMappedHistoricState: null,
156 showTicks: INVLIST.showTicks,
157 diffOverlayOn: false,
162 getSettings = () => {
164 'NODESERVER': INVLIST.NODESERVER,
165 'PROXY': INVLIST.PROXY,
166 'PREFIX': INVLIST.PREFIX,
167 'VERSION': INVLIST.VERSION,
168 'USESTUBS': INVLIST.useStubs
172 fetchInventoryData = (param) => {
173 console.log('fetchInventoryData', param);
176 generateDiffArray = (arr) => {
178 tempArray['properties'] = [];
179 tempArray['related-to'] = [];
181 for (var i = 0; i < arr.properties.length; i++ ){
182 if(arr.properties[i].key !== "length"){
183 tempArray['properties'][arr.properties[i].key] = arr.properties[i];
185 tempArray['properties']["LENGTH"] = arr.properties[i];
188 for (var j = 0; j < arr['related-to'].length; j++ ){
189 tempArray['related-to'][arr['related-to'][j].url] = arr['related-to'][j];
194 getCurrentStateCall = (settings,url,param) =>{
197 if(this.props.match.params.type == 'cq'){
199 stubPath = 'currentCQState';
202 stubPath = "currentBYOQState";
204 this.setState({currentErrMsg:null});
205 commonApi(settings, path + '?format=state', 'PUT', this.state.payload, stubPath, null, 'history-traversal')
207 this.getHistoryStateCall(this.getSettings());
208 Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, res.data, this);
211 topologyCurrentState : res.data,
212 enableBusyRecentFeedback: false
214 console.log('After recent node service call ......',this.state);
215 console.log('[HistoryQuery.jsx] recent node results : ', res.data);
217 this.triggerError(error, "current");
219 this.triggerError(error, "current");
222 triggerError = (error, type) => {
223 console.error('[HistoryQuery.jsx] error : ', JSON.stringify(error));
225 if (error.response) {
226 // The request was made and the server responded with a status code
227 // that falls out of the range of 2xx
228 console.log(error.response.data);
229 console.log(error.response.status);
230 console.log(error.response.headers);
231 if(error.response.status){
232 errMsg += " Code: " + error.response.status;
234 if(error.response.data){
235 errMsg += " - " + JSON.stringify(error.response.data);
237 } else if (error.request) {
238 // The request was made but no response was received
239 // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
240 // http.ClientRequest in node.js
241 console.log(error.request);
242 errMsg += " - Request was made but no response received";
244 // Something happened in setting up the request that triggered an Error
245 console.log('Error', error.message);
246 errMsg += " - Unknown error occurred " + error.message;
248 //Suppress 404's because that is just no results
249 if(error.response && error.response.status === 404){
252 if(type === 'current'){
254 topologyCurrentState : null,
255 currentErrMsg: errMsg,
256 enableBusyRecentFeedback:false
258 }else if (type === 'historic'){
259 if(this.state.isPlaying){
266 topologyHistoryState : null,
268 enableBusyDiffFeedback: false,
269 enableBusyHistoryStateFeedback:false,
270 historicErrMsg: errMsg
274 console.log('[HistoryQuery.jsx] tiggerError method called without a type.' );
277 getHistoryStateCall = (settings, url, param, timeStamp) =>{
280 let stubChangesPath = "";
281 if(this.props.match.params.type == 'cq'){
283 stubPath = 'historicalCQState';
284 stubChangesPath = 'historicalCQChanges';
287 stubPath = 'historicalBYOQState';
288 stubChangesPath = 'historicalBYOQChanges';
290 let ts = this.state.currentStateHistoryValue;
292 ts = parseInt(timeStamp);
294 this.setState({historicErrMsg:null});
295 commonApi(settings, path + "?format=state" + "&startTs=" + ts + "&endTs=" + ts, 'PUT', this.state.payload, stubPath, null, 'history-traversal')
297 Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, res.data, this);
300 topologyHistoryState : res.data,
301 enableBusyHistoryStateFeedback: false
303 console.log('After historical node state service call ......',this.state);
304 console.log('[HistoryQuery.jsx] historical node state results : ', res.data);
305 if(this.state.topologyHistoryState != null && this.state.topologyCurrentState != null){
306 let topologyDiffHistoryArr = [];
307 let topologyDiffCurrentArr = [];
308 let tempNodeCurrentState = [];
309 let tempNodeHistoricState = [];
310 for( var i = 0; i < this.state.topologyHistoryState.results.length; i++ ){
311 topologyDiffHistoryArr[this.state.topologyHistoryState.results[i].url] = this.generateDiffArray(this.state.topologyHistoryState.results[i]);
312 tempNodeHistoricState[this.state.topologyHistoryState.results[i].url] = this.state.topologyHistoryState.results[i];
314 for( var j = 0; j < this.state.topologyCurrentState.results.length; j++ ){
315 topologyDiffCurrentArr[this.state.topologyCurrentState.results[j].url] = this.generateDiffArray(this.state.topologyCurrentState.results[j]);
316 tempNodeCurrentState[this.state.topologyCurrentState.results[j].url] = this.state.topologyCurrentState.results[j];
318 var result = deepDiffMapper.map(topologyDiffHistoryArr, topologyDiffCurrentArr);
319 console.log("diff map" + result);
320 this.setState({ totalDiff: result, enableBusyDiffFeedback: false, rawMappedCurrentState: tempNodeCurrentState, rawMappedHistoricState: tempNodeHistoricState});
322 this.setState({ enableBusyDiffFeedback: false });
325 this.triggerError(error, "historic");
327 this.triggerError(error, "historic");
329 if(this.state.showTicks){
330 commonApi(settings, path + "?format=changes", 'PUT', this.state.payload, stubChangesPath, null, 'history-traversal')
332 let tickTempArray = [];
333 for(var j = 0; j < res.data.results.length; j++ ){
334 for(var k = 0; k < res.data.results[j].changes.length; k++){
335 if(!tickTempArray.includes(res.data.results[j].changes[k])){
336 tickTempArray.push(res.data.results[j].changes[k]);
340 let tickArray = tickTempArray.sort(function(a, b) {
343 if(compareA < compareB) return -1;
344 if(compareA > compareB) return 1;
347 console.log("tick array: " + tickArray);
348 this.setState({showSlider:true, sliderTickArray: tickArray});
350 console.log('[HistoryQuery.jsx] historical node changes error : ', error);
351 this.setState({showSlider:false});
354 this.setState({showSlider:true});
358 componentWillReceiveProps(nextProps) {
359 console.log('[History.jsx] componentWillReceiveProps');
360 console.log('[History.jsx] next payloadEnc:', atob(nextProps.match.params.payloadEnc));
361 console.log('[History.jsx] this payloadEnc:', atob(this.props.match.params.payloadEnc));
363 if (nextProps.match.params.payloadEnc
364 && nextProps.match.params.type
365 && nextProps.match.params.epochTime
366 && ((nextProps.match.params.payloadEnc !== this.props.match.params.payloadEnc) ||
367 (nextProps.match.params.type !== this.props.match.params.type) ||
368 (nextProps.match.params.epochTime !== this.props.match.params.epochTime))
370 this.props = nextProps;
372 this.getCurrentStateCall(this.getSettings());
376 handlePageChange = (pageNumber) => {
377 console.log('[History.jsx] HandelPageChange active page is', pageNumber);
381 stateHistoryFormat = (event) =>{
382 this.setState({ currentStateHistoryValue: event.target.value, selectedHistoryStateFormatted: moment(event.target.value).format('dddd, MMMM Do, YYYY h:mm:ss A')});
385 changeHistoryState = () =>{
386 console.log('minEpochStateTime: ' + this.state.minEpochStateTime);
387 console.log('maxEpochStartTime: ' + this.state.maxEpochStartTime);
388 console.log('stepEpochStateTime: ' + this.state.stepEpochStateTime);
389 console.log('currentStateHistoryValue: ' + this.state.currentStateHistoryValue);
390 console.log("Calling the route again with a new timestamp");
391 this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.currentStateHistoryValue);
394 viewTopologyComp = () =>{
396 showTopologyDiffModal: true
401 Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, this.state.topologyCurrentState, this);
402 Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, this.state.topologyHistoryState, this);
405 addOverlay = (elementType, key, modificationType) =>{
406 let chartToOverlay = 'currentState';
409 if (modificationType === "deleted"){
410 chartToOverlay = "historicState";
412 switch (modificationType){
426 console.log("hit default " + modificationType);
429 key = (((decodeURIComponent(key)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-');
433 console.log("adding overlay item for - element type: " + elementType + " key: " + key + " modificationType: " + modificationType );
434 let elementKey = elementType + chartToOverlay + key;
435 let element = d3.select("#" + elementKey);
437 if(elementType === "line"){
438 element.attrs({ 'stroke': color, 'stroke-opacity': .6, 'stroke-width': '3px'});
440 if(elementType === "nodeIcon"){
441 element.classed("nodeIcon-" + modificationType, true);
442 let elementKeyNode = 'node' + chartToOverlay + key;
443 let elementNode = d3.select("#" + elementKeyNode);
444 elementNode.append("text")
447 .attr('font-size', 25)
450 //Need to also add to historicGraph for modifications in addition to current
451 if(modificationType === 'modified'){
452 let elementKeyMod = elementType + 'historicState' + key;
453 let elementMod = d3.select("#" + elementKeyMod);
454 elementMod.classed("nodeIcon-" + modificationType, true);
456 let elementKeyModNode = 'nodehistoricState' + key;
457 let elementModNode = d3.select("#" + elementKeyModNode);
458 elementModNode.append("text")
461 .attr('font-size', 25)
466 viewTopologyCompVisual = () =>{
467 if(this.state.diffOverlayOn){
468 this.setState({ diffOverlayOn: false});
470 }else if(this.state.totalDiff){
471 this.setState({ diffOverlayOn: true});
472 const properties = Object.entries(this.state.totalDiff).forEach((prop) => {
474 let propWorkaround = prop;
476 propWorkaround = prop.data;
478 let tempProp = propWorkaround[1];
479 let attributeProperties = '';
480 let relationships = '';
481 let topLevelType = tempProp.type;
482 let alreadyHighlighted = false;
484 this.addOverlay('nodeIcon', propWorkaround[0], topLevelType);
485 //set this to not mark as modified when it is added new
486 alreadyHighlighted = true;
488 if(!topLevelType && tempProp.properties){
489 for (var key in tempProp.properties) {
490 if (tempProp.properties.hasOwnProperty(key)) {
491 let property = tempProp.properties[key];
492 if(property && ((property.value && property.value.type !== 'unchanged') || property.type)){
493 this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
499 if(tempProp['related-to'] || tempProp.data['related-to']){
501 let topLevelType = null;
502 if(tempProp['related-to']){
503 rel = tempProp['related-to'];
504 }else if (tempProp.data['related-to']) {
505 rel = tempProp.data['related-to'];
506 topLevelType = tempProp.type;
508 relationships = Object.entries(rel).forEach((property) => {
509 let relationProp = property[1];
510 if(relationProp && relationProp.type && relationProp.type.type && relationProp.type.type !== "unchanged"){
511 //do nothing since we only care about additions and deletions on relationships, should not be considered modified
512 console.log("relationship considered modified: id: " + relationProp.id + " type: "+ relationProp.type + " mod type: " + relationProp.type.type)
513 }else if(relationProp && relationProp.type && !relationProp.type.type && relationProp.url && relationProp.url.data){
514 if(!alreadyHighlighted){
515 this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
517 this.addOverlay('line', relationProp.id.data, relationProp.type);
518 }else if (relationProp && relationProp.type && relationProp.data){
519 if(!alreadyHighlighted){
520 this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
522 this.addOverlay('line', relationProp.data.id, relationProp.type);
523 }else if (topLevelType){
524 if(!alreadyHighlighted){
525 this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
527 this.addOverlay('line', relationProp.id, topLevelType);
532 //No changes, do nothing
537 // do nothing if no diff
541 closeTopologyDiffModal = () => {
543 showTopologyDiffModal: false
546 openNodeModal(nodeDisplay, nodeUri, nodeType){ // open modal
547 console.log('history >> showModal');
548 this.setState({ nodeDiff: this.state.totalDiff[nodeUri]});
549 nodeDisplay = "State Comparison of " + nodeUri;
550 if(!this.state.rawMappedHistoricState[nodeUri]){
551 this.state.rawMappedHistoricState[nodeUri] = {};
553 if(!this.state.rawMappedCurrentState[nodeUri]){
554 this.state.rawMappedCurrentState[nodeUri] = {};
556 this.state.rawMappedHistoricState[nodeUri].primaryHeader = "Historic State of " + nodeDisplay;
557 this.state.rawMappedCurrentState[nodeUri].primaryHeader = "Current State of " + nodeDisplay;
560 nodeDisplay: nodeDisplay,
561 selectedNodeHistoryState: this.state.rawMappedHistoricState[nodeUri],
562 selectedNodeCurrentState: this.state.rawMappedCurrentState[nodeUri],
563 focusedNodeUri: nodeUri,
564 focusedNodeType: nodeType,
573 closeNodeModal = () => {
579 getStateIndex = () =>{
580 return this.state.sliderTickArray.indexOf(this.state.currentStateHistoryValue);
583 navigateAnimation = (index, command) => {
585 this.setState({isPlaying:false, isStopped: false, isPaused: true, currentStateHistoryValue: this.state.sliderTickArray[index], selectedHistoryStateFormatted: moment(this.state.sliderTickArray[index]).format('dddd, MMMM Do, YYYY h:mm:ss A')});
586 }else if (command === 'play'){
587 this.setState({currentStateHistoryValue: this.state.sliderTickArray[index], selectedHistoryStateFormatted: moment(this.state.sliderTickArray[index]).format('dddd, MMMM Do, YYYY h:mm:ss A')});
589 this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.sliderTickArray[index]);
593 if(this.state.isPlaying){
594 if(!this.state.enableBusyHistoryStateFeedback){
595 var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
596 if(this.state.sliderTickArray.length > this.getStateIndex() + 1){
597 this.navigateAnimation(this.getStateIndex() + 1, 'play');
600 this.setState({isPlaying:false, isStopped: true, isPaused: false});
604 clearInterval(this.state.intervalId);
608 animationControl = (controlType) => {
609 console.log("Control was hit: " + controlType);
612 if(!this.state.isPlaying){
613 this.setState({isPlaying:true, isStopped: false, isPaused: false, intervalId: setInterval(this.play, INVLIST.animationIntervalMs)});
617 if(this.state.isPlaying){
618 clearInterval(this.state.intervalId);
619 this.setState({isPlaying:false, isPaused: true});
623 if(this.state.isPlaying || this.state.isPaused){
624 clearInterval(this.state.intervalId);
625 this.setState({isPlaying:false, isStopped: true, isPaused: false});
628 case 'skipForwardStep':
629 var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
630 this.navigateAnimation(index);
632 case 'skipBackwardStep':
633 var index = Math.max(0, this.getStateIndex() - 1);
634 this.navigateAnimation(index);
636 case 'skipForwardLast':
637 this.navigateAnimation(this.state.sliderTickArray.length - 1);
639 case 'skipBackwardEpoch':
640 this.navigateAnimation(0);
643 this.setState({isPlaying:false, isStopped: false, isPaused: false});
647 // START: These functions are for the animation controls
648 setHistoricStateValues = (currValue) =>{
649 this.setState({currentStateHistoryValue: currValue, selectedHistoryStateFormatted: moment(currValue).format('dddd, MMMM Do, YYYY h:mm:ss A')});
652 navigateHistory = (time) =>{
653 this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + time);
656 setStateValue = (key, value) =>{
657 this.setState((state) => { key : value });
659 getStateValue = (stateVar) => {
660 return this.state[stateVar];
662 clear = (interval) =>{
663 clearInterval(interval);
668 if(INVLIST.isHistoryEnabled){
671 <header className='addPadding jumbotron my-4'>
672 <h1 className='display-2'>History Visualization</h1>
674 On this page you have the ability to view a series of network elements in their current and historic state.
675 Zooming and panning are enabled in the graphs. You can use the scrollwheel to zoom and click+drag to pan.
676 If a node is single clicked you can drag the elements to reposition them. Double-clicking a node will
677 show the comparison between the two nodes in a pop-up modal. Animation controls are provided to seemlessly transition between states.
678 You can view the graph difference in a visual form by clicking View Visual Comparison.
681 <Grid fluid={true} className="addPadding">
682 <Row className={this.state.currentErrMsg ? 'show' : 'hidden'} >
683 <div className='addPaddingTop alert alert-danger' role="alert">
684 An error occurred while trying to get current information, please try again later. If this issue persists, please contact the system administrator. {this.state.currentErrMsg}
687 <Row className={!this.state.currentErrMsg ? 'show' : 'hidden'} >
688 <Col className='col-lg-12'>
689 <div className='card d3-history-query-card'>
690 <div className='card-header history-query-card-header'>
691 <h2><strong>Current State</strong> of <em>{this.state.queryDisplay}</em></h2>
693 <div className={'card-header ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
694 <button type='button' className='btn btn-outline-primary' onClick={this.viewTopologyCompVisual}>Toggle Visual Comparison</button>
696 <div className='history-query-card-content'>
697 {!this.state.topologyCurrentState || (this.state.topologyCurrentState.results && this.state.topologyCurrentState.results.length === 0) && (<div className='addPaddingTop'><p>No current state for this query</p></div>)}
698 <OutputVisualization identifier="currentState" width={this.svgWidth} height="600" overflow="scroll"/>
700 <div className='card-footer'>
701 <strong>Tip:</strong> <em>Click and drag network elements to reposition them, double-click nodes to see the node detail comparison. In addition: The graph supports pinch zoom or scrollwheel for zooming. Panning can be done by single-click and drag.</em>
706 <Row className={this.state.historicErrMsg ? 'show' : 'hidden'} >
707 <div className='addPaddingTop alert alert-danger' role="alert">
708 An error occurred, while trying to get historic information, please try again later. If this issue persists, please contact the system administrator. {this.state.historicErrMsg}
711 <Row className={!this.state.historicErrMsg ? 'show' : 'hidden'}>
712 <Col className='col-lg-12'>
713 <div className='card d3-history-query-card'>
714 <div className='card-header history-query-card-header'>
715 <h2><strong>Historical State</strong> of <em>{this.state.queryDisplay}</em> at {moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A')}</h2>
717 <div className='card-header'>
718 { (this.state.showSlider && this.state.showTicks) && (<Row className='show-grid'>
720 <ReactBootstrapSlider
721 value={this.state.currentStateHistoryValue}
722 change={this.stateHistoryFormat}
723 slideStop={this.stateHistoryFormat}
725 ticks={ this.state.sliderTickArray }
726 ticks_snap_bounds={ 10000 }
727 orientation="horizontal" />
730 <i className='icon-controls-skipbackstartover animationControlIcon' onClick={() => this.animationControl('skipBackwardEpoch')} role="img"></i>
731 <i className='icon-controls-rewind animationControlIcon' onClick={() => this.animationControl('skipBackwardStep')} role="img"></i>
732 <i className={'icon-controls-pointer ' + (this.state.isPlaying ? 'animationPlayingIcon' : 'animationControlIcon')} onClick={() => this.animationControl('play')} role="img"></i>
733 <i className={'icon-controls-pause ' + (this.state.isPaused ? 'animationPausedIcon' : 'animationControlIcon')} onClick={() => this.animationControl('pause')} role="img"></i>
734 <i className={'icon-controls-stop ' + (this.state.isStopped ? 'animationStoppedIcon' : 'animationControlIcon')} onClick={() => this.animationControl('stop')} role="img"></i>
735 <i className='icon-controls-fastforward animationControlIcon' onClick={() => this.animationControl('skipForwardStep')} role="img"></i>
736 <i className='icon-controls-skipforward animationControlIcon' onClick={() => this.animationControl('skipForwardLast')} role="img"></i>
740 { (this.state.showSlider && !this.state.showTicks) && (<ReactBootstrapSlider
741 value={this.state.currentStateHistoryValue}
742 change={this.stateHistoryFormat}
743 slideStop={this.stateHistoryFormat}
744 step={this.state.stepEpochStateTime}
745 max={this.state.maxEpochStartTime}
746 min={this.state.minEpochStartTime}
747 orientation="horizontal" />)}
749 <p>{this.state.selectedHistoryStateFormatted}</p>
750 <button type='button' className='btn btn-outline-primary' onClick={this.changeHistoryState}>Refresh</button>
752 <div className='history-query-card-content' >
753 {!this.state.topologyHistoryState || (this.state.topologyHistoryState.results && this.state.topologyHistoryState.results.length === 0) && (<div className="addPaddingTop"><p>No state during this time period</p></div>)}
754 <OutputVisualization identifier="historicState" width={this.svgWidth} height="600" overflow="scroll"/>
756 <div className={'card-footer ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
757 <strong>Tip:</strong> <em>Click and drag network elements to reposition them, double-click nodes to see the node detail comparison. In addition: The graph supports pinch zoom or scrollwheel for zooming. Panning can be done by single-click and drag.</em>
764 <div className='static-modal'>
765 <Modal show={this.state.showNodeModal} onHide={this.closeNodeModal} dialogClassName="modal-override">
767 <Modal.Title>Retrieve {this.state.nodeDisplay} History</Modal.Title>
771 <Row className='show-grid'>
773 <HistoryCard node={this.state.selectedNodeHistoryState}/>
775 <NodeDiffCard diff={this.state.nodeDiff}/>
777 <HistoryCard node={this.state.selectedNodeCurrentState}/>
783 <Button onClick={this.closeNodeModal}>Close</Button>
787 <div className='static-modal'>
788 <Modal show={this.state.showTopologyDiffModal} onHide={this.closeTopologyDiffModal}>
790 <Modal.Title>Retrieve Topology History</Modal.Title>
794 <Row className='show-grid'>
796 <TopologyDiffCard node={this.state.totalDiff}/>
802 <Button onClick={this.closeTopologyDiffModal}>Close</Button>
809 return(<p>History Not Enabled for this instance, please check config.</p>)
814 export default HistoryQuery;