summaryrefslogtreecommitdiffstats
path: root/src/app/model/history/HistoryQuery.jsx
diff options
context:
space:
mode:
authorwr148d <wr148d@att.com>2021-01-15 15:32:00 -0500
committerwr148d <wr148d@att.com>2021-02-11 09:47:17 -0500
commit5ee7367a101143715c2869d72ea4a6fbf55f5af6 (patch)
tree84bf43601c0cce4fb37b5b3b494e113c96d5591e /src/app/model/history/HistoryQuery.jsx
parentddc05d4ea0254b427fea6ec80e2b03950eeca4ce (diff)
Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the Builder FE Updates
Issue-ID: AAI-3250 Change-Id: I576e37f77f7e9b40d72e4a5e7de645e9f62bc7d2 Signed-off-by: wr148d <wr148d@att.com>
Diffstat (limited to 'src/app/model/history/HistoryQuery.jsx')
-rw-r--r--src/app/model/history/HistoryQuery.jsx814
1 files changed, 814 insertions, 0 deletions
diff --git a/src/app/model/history/HistoryQuery.jsx b/src/app/model/history/HistoryQuery.jsx
new file mode 100644
index 0000000..f001f97
--- /dev/null
+++ b/src/app/model/history/HistoryQuery.jsx
@@ -0,0 +1,814 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+
+import commonApi from 'utils/CommonAPIService.js';
+import deepDiffMapper from 'utils/DiffUtil.js';
+import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
+
+import moment from "moment";
+import Grid from 'react-bootstrap/lib/Grid';
+import Row from 'react-bootstrap/lib/Row';
+import Col from 'react-bootstrap/lib/Col';
+import Button from 'react-bootstrap/lib/Button';
+import Modal from 'react-bootstrap/lib/Modal';
+import * as d3 from "d3";
+import 'd3-selection-multi';
+import ReactBootstrapSlider from 'react-bootstrap-slider';
+import HistoryCard from './components/HistoryCard.jsx';
+import NodeDiffCard from './components/NodeDiffCard.jsx';
+import AnimationControls from './components/AnimationControls.jsx';
+import TopologyDiffCard from './components/TopologyDiffCard.jsx';
+import OutputVisualization, {Visualization} from 'generic-components/OutputVisualization.jsx';
+
+
+
+let INVLIST = GlobalExtConstants.INVLIST;
+
+/**
+ * This class is to show visualizations of queries and show a historical state
+ */
+
+class HistoryQuery extends Component {
+
+ svgWidth = window.outerWidth * 0.8;
+ elements = [];
+ pageTitle = '';
+ nodeType = '';
+ nodeResults = '';
+
+ constructor(props) {
+ console.log(props);
+ super(props);
+ this.state = {
+ totalResults: 0,
+ enableBusyRecentFeedback: true,
+ enableBusyHistoryStateFeedback: true,
+ enableBusyDiffFeedback: true,
+ data: [],
+ nodes: [],
+ payload: ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
+ topologyCurrentState: null,
+ topologyHistoryState: null,
+ currentStateHistoryValue: parseInt(this.props.match.params.epochTime),
+ stepEpochStateTime: 1000,
+ maxEpochStartTime: Math.round(new Date().getTime()),
+ minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
+ nodeDiff: null,
+ selectedHistoryStateFormatted: moment(parseInt(this.props.match.params.epochTime)).format('dddd, MMMM Do, YYYY h:mm:ss A'),
+ queryDisplay: ((this.props.match.params.type == 'cq') ? JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
+ currentGraphNodes: [],
+ currentGraphLinks: [],
+ historicGraphNodes: [],
+ historicGraphLinks: [],
+ selectedNodeHistoryState: null,
+ selectedNodeCurrentState: null,
+ showNodeModal: false,
+ showTopologyDiffModal: false,
+ nodeDisplay: '',
+ totalDiff: null,
+ showSlider: false,
+ sliderTickArray: null,
+ showTicks: INVLIST.showTicks,
+ rawMappedCurrentState: null,
+ rawMappedHistoricState: null,
+ isPlaying: false,
+ isPaused: false,
+ isStopped: false,
+ intervalId: null,
+ diffOverlayOn: false,
+ currentErrMsg: null,
+ historicErrMsg: null
+
+ };
+ console.log('minEpochStateTime: '+this.state.minEpochStateTime);
+ console.log('maxEpochStartTime: '+this.state.maxEpochStartTime);
+ console.log('stepEpochStateTime: '+this.state.stepEpochStateTime);
+ console.log('currentStateHistoryValue: '+this.state.currentStateHistoryValue);
+ }
+ resultsMessage = '';
+ componentDidMount = () => {
+ console.log('[HistoryQuery.jsx] componentDidMount props available are', JSON.stringify(this.props));
+ if(INVLIST.isHistoryEnabled){
+ this.getCurrentStateCall(this.getSettings());
+ }
+ };
+
+ componentWillUnmount = () => {
+ console.log('[History.jsx] componentWillUnMount');
+ }
+ beforefetchInventoryData = (param) => {
+ if (param) {
+ this.setState({});
+ } else {
+ this.fetchInventoryData();
+ }
+ };
+ initState = () =>{
+ this.setState({
+ totalResults: 0,
+ enableBusyRecentFeedback: true,
+ enableBusyHistoryStateFeedback: true,
+ enableBusyDiffFeedback: true,
+ data: [],
+ nodes: [],
+ payload : ((this.props.match.params.type == 'cq') ? atob(this.props.match.params.payloadEnc) : { "dsl" : atob(this.props.match.params.payloadEnc)}),
+ topologyHistoryState: null,
+ topologyCurrentState: null,
+ stepEpochStateTime: 1000,
+ maxEpochStartTime: Math.round(new Date().getTime()),
+ minEpochStartTime: parseInt(this.props.match.params.epochTime) - 259200000,
+ nodeDiff: null,
+ queryDisplay: ((this.props.match.params.type == 'cq') ? JSON.parse(atob(this.props.match.params.payloadEnc)).query : atob(this.props.match.params.payloadEnc)),
+ currentGraphNodes: [],
+ currentGraphLinks: [],
+ historicGraphNodes: [],
+ historicGraphLinks: [],
+ selectedNodeHistoryState: null,
+ selectedNodeCurrentState: null,
+ showNodeModal: false,
+ showTopologyDiffModal: false,
+ nodeDisplay: '',
+ totalDiff: null,
+ showSlider: false,
+ sliderTickArray: null,
+ rawMappedCurrentState: null,
+ rawMappedHistoricState: null,
+ showTicks: INVLIST.showTicks,
+ diffOverlayOn: false,
+ currentErrMsg: null,
+ historicErrMsg: null
+ });
+ }
+ getSettings = () => {
+ const settings = {
+ 'NODESERVER': INVLIST.NODESERVER,
+ 'PROXY': INVLIST.PROXY,
+ 'PREFIX': INVLIST.PREFIX,
+ 'VERSION': INVLIST.VERSION,
+ 'USESTUBS': INVLIST.useStubs
+ };
+ return settings;
+ }
+ fetchInventoryData = (param) => {
+ console.log('fetchInventoryData', param);
+ };
+
+ generateDiffArray = (arr) => {
+ let tempArray = {};
+ tempArray['properties'] = [];
+ tempArray['related-to'] = [];
+
+ for (var i = 0; i < arr.properties.length; i++ ){
+ if(arr.properties[i].key !== "length"){
+ tempArray['properties'][arr.properties[i].key] = arr.properties[i];
+ }else{
+ tempArray['properties']["LENGTH"] = arr.properties[i];
+ }
+ }
+ for (var j = 0; j < arr['related-to'].length; j++ ){
+ tempArray['related-to'][arr['related-to'][j].url] = arr['related-to'][j];
+ }
+ return tempArray;
+ }
+
+ getCurrentStateCall = (settings,url,param) =>{
+ let path = "";
+ let stubPath = "";
+ if(this.props.match.params.type == 'cq'){
+ path = 'query';
+ stubPath = 'currentCQState';
+ }else{
+ path = 'dsl';
+ stubPath = "currentBYOQState";
+ }
+ this.setState({currentErrMsg:null});
+ commonApi(settings, path + '?format=state', 'PUT', this.state.payload, stubPath, null, 'history-traversal')
+ .then(res => {
+ this.getHistoryStateCall(this.getSettings());
+ Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, res.data, this);
+ this.setState(
+ {
+ topologyCurrentState : res.data,
+ enableBusyRecentFeedback: false
+ });
+ console.log('After recent node service call ......',this.state);
+ console.log('[HistoryQuery.jsx] recent node results : ', res.data);
+ }, error=>{
+ this.triggerError(error, "current");
+ }).catch(error => {
+ this.triggerError(error, "current");
+ });
+ };
+ triggerError = (error, type) => {
+ console.error('[HistoryQuery.jsx] error : ', JSON.stringify(error));
+ let errMsg = '';
+ if (error.response) {
+ // The request was made and the server responded with a status code
+ // that falls out of the range of 2xx
+ console.log(error.response.data);
+ console.log(error.response.status);
+ console.log(error.response.headers);
+ if(error.response.status){
+ errMsg += " Code: " + error.response.status;
+ }
+ if(error.response.data){
+ errMsg += " - " + JSON.stringify(error.response.data);
+ }
+ } else if (error.request) {
+ // The request was made but no response was received
+ // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
+ // http.ClientRequest in node.js
+ console.log(error.request);
+ errMsg += " - Request was made but no response received";
+ } else {
+ // Something happened in setting up the request that triggered an Error
+ console.log('Error', error.message);
+ errMsg += " - Unknown error occurred " + error.message;
+ }
+ //Suppress 404's because that is just no results
+ if(error.response && error.response.status === 404){
+ errMsg = '';
+ }
+ if(type === 'current'){
+ this.setState({
+ topologyCurrentState : null,
+ currentErrMsg: errMsg,
+ enableBusyRecentFeedback:false
+ });
+ }else if (type === 'historic'){
+ if(this.state.isPlaying){
+ this.setState({
+ isPlaying: false,
+ isStopped: true,
+ isPaused: false });
+ }else{
+ this.setState({
+ topologyHistoryState : null,
+ nodeDiff: null,
+ enableBusyDiffFeedback: false,
+ enableBusyHistoryStateFeedback:false,
+ historicErrMsg: errMsg
+ });
+ }
+ }else{
+ console.log('[HistoryQuery.jsx] tiggerError method called without a type.' );
+ }
+ }
+ getHistoryStateCall = (settings, url, param, timeStamp) =>{
+ let path = "";
+ let stubPath = "";
+ let stubChangesPath = "";
+ if(this.props.match.params.type == 'cq'){
+ path = 'query';
+ stubPath = 'historicalCQState';
+ stubChangesPath = 'historicalCQChanges';
+ }else{
+ path = 'dsl';
+ stubPath = 'historicalBYOQState';
+ stubChangesPath = 'historicalBYOQChanges';
+ }
+ let ts = this.state.currentStateHistoryValue;
+ if(timeStamp){
+ ts = parseInt(timeStamp);
+ }
+ this.setState({historicErrMsg:null});
+ commonApi(settings, path + "?format=state" + "&startTs=" + ts + "&endTs=" + ts, 'PUT', this.state.payload, stubPath, null, 'history-traversal')
+ .then(res => {
+ Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, res.data, this);
+ this.setState(
+ {
+ topologyHistoryState : res.data,
+ enableBusyHistoryStateFeedback: false
+ });
+ console.log('After historical node state service call ......',this.state);
+ console.log('[HistoryQuery.jsx] historical node state results : ', res.data);
+ if(this.state.topologyHistoryState != null && this.state.topologyCurrentState != null){
+ let topologyDiffHistoryArr = [];
+ let topologyDiffCurrentArr = [];
+ let tempNodeCurrentState = [];
+ let tempNodeHistoricState = [];
+ for( var i = 0; i < this.state.topologyHistoryState.results.length; i++ ){
+ topologyDiffHistoryArr[this.state.topologyHistoryState.results[i].url] = this.generateDiffArray(this.state.topologyHistoryState.results[i]);
+ tempNodeHistoricState[this.state.topologyHistoryState.results[i].url] = this.state.topologyHistoryState.results[i];
+ }
+ for( var j = 0; j < this.state.topologyCurrentState.results.length; j++ ){
+ topologyDiffCurrentArr[this.state.topologyCurrentState.results[j].url] = this.generateDiffArray(this.state.topologyCurrentState.results[j]);
+ tempNodeCurrentState[this.state.topologyCurrentState.results[j].url] = this.state.topologyCurrentState.results[j];
+ }
+ var result = deepDiffMapper.map(topologyDiffHistoryArr, topologyDiffCurrentArr);
+ console.log("diff map" + result);
+ this.setState({ totalDiff: result, enableBusyDiffFeedback: false, rawMappedCurrentState: tempNodeCurrentState, rawMappedHistoricState: tempNodeHistoricState});
+ }else{
+ this.setState({ enableBusyDiffFeedback: false });
+ }
+ }, error=>{
+ this.triggerError(error, "historic");
+ }).catch(error => {
+ this.triggerError(error, "historic");
+ });
+ if(this.state.showTicks){
+ commonApi(settings, path + "?format=changes", 'PUT', this.state.payload, stubChangesPath, null, 'history-traversal')
+ .then(res => {
+ let tickTempArray = [];
+ for(var j = 0; j < res.data.results.length; j++ ){
+ for(var k = 0; k < res.data.results[j].changes.length; k++){
+ if(!tickTempArray.includes(res.data.results[j].changes[k])){
+ tickTempArray.push(res.data.results[j].changes[k]);
+ }
+ }
+ }
+ let tickArray = tickTempArray.sort(function(a, b) {
+ var compareA = a;
+ var compareB = b;
+ if(compareA < compareB) return -1;
+ if(compareA > compareB) return 1;
+ return 0;
+ });
+ console.log("tick array: " + tickArray);
+ this.setState({showSlider:true, sliderTickArray: tickArray});
+ }).catch(error => {
+ console.log('[HistoryQuery.jsx] historical node changes error : ', error);
+ this.setState({showSlider:false});
+ });
+ }else{
+ this.setState({showSlider:true});
+ }
+ };
+
+componentWillReceiveProps(nextProps) {
+ console.log('[History.jsx] componentWillReceiveProps');
+ console.log('[History.jsx] next payloadEnc:', atob(nextProps.match.params.payloadEnc));
+ console.log('[History.jsx] this payloadEnc:', atob(this.props.match.params.payloadEnc));
+
+ if (nextProps.match.params.payloadEnc
+ && nextProps.match.params.type
+ && nextProps.match.params.epochTime
+ && ((nextProps.match.params.payloadEnc !== this.props.match.params.payloadEnc) ||
+ (nextProps.match.params.type !== this.props.match.params.type) ||
+ (nextProps.match.params.epochTime !== this.props.match.params.epochTime))
+ ) {
+ this.props = nextProps;
+ this.initState();
+ this.getCurrentStateCall(this.getSettings());
+ }
+ };
+
+ handlePageChange = (pageNumber) => {
+ console.log('[History.jsx] HandelPageChange active page is', pageNumber);
+ this.setState({});
+ };
+
+ stateHistoryFormat = (event) =>{
+ this.setState({ currentStateHistoryValue: event.target.value, selectedHistoryStateFormatted: moment(event.target.value).format('dddd, MMMM Do, YYYY h:mm:ss A')});
+ };
+
+ changeHistoryState = () =>{
+ console.log('minEpochStateTime: ' + this.state.minEpochStateTime);
+ console.log('maxEpochStartTime: ' + this.state.maxEpochStartTime);
+ console.log('stepEpochStateTime: ' + this.state.stepEpochStateTime);
+ console.log('currentStateHistoryValue: ' + this.state.currentStateHistoryValue);
+ console.log("Calling the route again with a new timestamp");
+ this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.currentStateHistoryValue);
+ }
+
+ viewTopologyComp = () =>{
+ this.setState({
+ showTopologyDiffModal: true
+ });
+ }
+
+ resetGraph = () =>{
+ Visualization.chart('currentState', this.state.currentGraphNodes, this.state.currentGraphLinks, this.state.topologyCurrentState, this);
+ Visualization.chart('historicState', this.state.historicGraphNodes, this.state.historicGraphLinks, this.state.topologyHistoryState, this);
+ }
+
+ addOverlay = (elementType, key, modificationType) =>{
+ let chartToOverlay = 'currentState';
+ let color = '';
+ let modIcon = '';
+ if (modificationType === "deleted"){
+ chartToOverlay = "historicState";
+ }
+ switch (modificationType){
+ case 'deleted':
+ color = 'red';
+ modIcon = '-';
+ break;
+ case 'created':
+ color = 'green';
+ modIcon = '+';
+ break;
+ case 'modified':
+ color = 'orange';
+ modIcon = '*';
+ break;
+ default:
+ console.log("hit default " + modificationType);
+ }
+ if(key){
+ key = (((decodeURIComponent(key)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-');
+ }else{
+ key='';
+ }
+ console.log("adding overlay item for - element type: " + elementType + " key: " + key + " modificationType: " + modificationType );
+ let elementKey = elementType + chartToOverlay + key;
+ let element = d3.select("#" + elementKey);
+
+ if(elementType === "line"){
+ element.attrs({ 'stroke': color, 'stroke-opacity': .6, 'stroke-width': '3px'});
+ }
+ if(elementType === "nodeIcon"){
+ element.classed("nodeIcon-" + modificationType, true);
+ let elementKeyNode = 'node' + chartToOverlay + key;
+ let elementNode = d3.select("#" + elementKeyNode);
+ elementNode.append("text")
+ .attr("dy", 10)
+ .attr("dx", 35)
+ .attr('font-size', 25)
+ .text(modIcon);
+ }
+ //Need to also add to historicGraph for modifications in addition to current
+ if(modificationType === 'modified'){
+ let elementKeyMod = elementType + 'historicState' + key;
+ let elementMod = d3.select("#" + elementKeyMod);
+ elementMod.classed("nodeIcon-" + modificationType, true);
+
+ let elementKeyModNode = 'nodehistoricState' + key;
+ let elementModNode = d3.select("#" + elementKeyModNode);
+ elementModNode.append("text")
+ .attr("dy", 10)
+ .attr("dx", 35)
+ .attr('font-size', 25)
+ .text(modIcon);
+ }
+ }
+
+ viewTopologyCompVisual = () =>{
+ if(this.state.diffOverlayOn){
+ this.setState({ diffOverlayOn: false});
+ this.resetGraph();
+ }else if(this.state.totalDiff){
+ this.setState({ diffOverlayOn: true});
+ const properties = Object.entries(this.state.totalDiff).forEach((prop) => {
+ if (prop){
+ let propWorkaround = prop;
+ if(prop.data){
+ propWorkaround = prop.data;
+ }
+ let tempProp = propWorkaround[1];
+ let attributeProperties = '';
+ let relationships = '';
+ let topLevelType = tempProp.type;
+ let alreadyHighlighted = false;
+ if(topLevelType){
+ this.addOverlay('nodeIcon', propWorkaround[0], topLevelType);
+ //set this to not mark as modified when it is added new
+ alreadyHighlighted = true;
+ }
+ if(!topLevelType && tempProp.properties){
+ for (var key in tempProp.properties) {
+ if (tempProp.properties.hasOwnProperty(key)) {
+ let property = tempProp.properties[key];
+ if(property && ((property.value && property.value.type !== 'unchanged') || property.type)){
+ this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
+ break;
+ }
+ }
+ }
+ }
+ if(tempProp['related-to'] || tempProp.data['related-to']){
+ let rel = null;
+ let topLevelType = null;
+ if(tempProp['related-to']){
+ rel = tempProp['related-to'];
+ }else if (tempProp.data['related-to']) {
+ rel = tempProp.data['related-to'];
+ topLevelType = tempProp.type;
+ }
+ relationships = Object.entries(rel).forEach((property) => {
+ let relationProp = property[1];
+ if(relationProp && relationProp.type && relationProp.type.type && relationProp.type.type !== "unchanged"){
+ //do nothing since we only care about additions and deletions on relationships, should not be considered modified
+ console.log("relationship considered modified: id: " + relationProp.id + " type: "+ relationProp.type + " mod type: " + relationProp.type.type)
+ }else if(relationProp && relationProp.type && !relationProp.type.type && relationProp.url && relationProp.url.data){
+ if(!alreadyHighlighted){
+ this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
+ }
+ this.addOverlay('line', relationProp.id.data, relationProp.type);
+ }else if (relationProp && relationProp.type && relationProp.data){
+ if(!alreadyHighlighted){
+ this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
+ }
+ this.addOverlay('line', relationProp.data.id, relationProp.type);
+ }else if (topLevelType){
+ if(!alreadyHighlighted){
+ this.addOverlay('nodeIcon', propWorkaround[0], 'modified');
+ }
+ this.addOverlay('line', relationProp.id, topLevelType);
+ }
+ });
+ }
+ }else{
+ //No changes, do nothing
+ }
+ });
+
+ }else{
+ // do nothing if no diff
+ }
+ }
+
+ closeTopologyDiffModal = () => {
+ this.setState({
+ showTopologyDiffModal: false
+ });
+ }
+ openNodeModal(nodeDisplay, nodeUri, nodeType){ // open modal
+ console.log('history >> showModal');
+ this.setState({ nodeDiff: this.state.totalDiff[nodeUri]});
+ nodeDisplay = "State Comparison of " + nodeUri;
+ if(!this.state.rawMappedHistoricState[nodeUri]){
+ this.state.rawMappedHistoricState[nodeUri] = {};
+ }
+ if(!this.state.rawMappedCurrentState[nodeUri]){
+ this.state.rawMappedCurrentState[nodeUri] = {};
+ }
+ this.state.rawMappedHistoricState[nodeUri].primaryHeader = "Historic State of " + nodeDisplay;
+ this.state.rawMappedCurrentState[nodeUri].primaryHeader = "Current State of " + nodeDisplay;
+ if(nodeDisplay){
+ this.setState({
+ nodeDisplay: nodeDisplay,
+ selectedNodeHistoryState: this.state.rawMappedHistoricState[nodeUri],
+ selectedNodeCurrentState: this.state.rawMappedCurrentState[nodeUri],
+ focusedNodeUri: nodeUri,
+ focusedNodeType: nodeType,
+ showNodeModal:true
+ });
+ }else{
+ this.setState({
+ showNodeModal:true
+ });
+ }
+ }
+ closeNodeModal = () => {
+ this.setState({
+ showNodeModal: false
+ });
+ }
+
+ getStateIndex = () =>{
+ return this.state.sliderTickArray.indexOf(this.state.currentStateHistoryValue);
+ }
+
+ navigateAnimation = (index, command) => {
+ if(!command){
+ 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')});
+ }else if (command === 'play'){
+ this.setState({currentStateHistoryValue: this.state.sliderTickArray[index], selectedHistoryStateFormatted: moment(this.state.sliderTickArray[index]).format('dddd, MMMM Do, YYYY h:mm:ss A')});
+ }
+ this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + this.state.sliderTickArray[index]);
+ }
+
+ play = () =>{
+ if(this.state.isPlaying){
+ if(!this.state.enableBusyHistoryStateFeedback){
+ var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
+ if(this.state.sliderTickArray.length > this.getStateIndex() + 1){
+ this.navigateAnimation(this.getStateIndex() + 1, 'play');
+
+ }else{
+ this.setState({isPlaying:false, isStopped: true, isPaused: false});
+ }
+ }
+ }else{
+ clearInterval(this.state.intervalId);
+ }
+ }
+
+ animationControl = (controlType) => {
+ console.log("Control was hit: " + controlType);
+ switch(controlType){
+ case 'play':
+ if(!this.state.isPlaying){
+ this.setState({isPlaying:true, isStopped: false, isPaused: false, intervalId: setInterval(this.play, INVLIST.animationIntervalMs)});
+ }
+ break;
+ case 'pause':
+ if(this.state.isPlaying){
+ clearInterval(this.state.intervalId);
+ this.setState({isPlaying:false, isPaused: true});
+ }
+ break;
+ case 'stop':
+ if(this.state.isPlaying || this.state.isPaused){
+ clearInterval(this.state.intervalId);
+ this.setState({isPlaying:false, isStopped: true, isPaused: false});
+ }
+ break;
+ case 'skipForwardStep':
+ var index = Math.min(this.state.sliderTickArray.length - 1, this.getStateIndex() + 1);
+ this.navigateAnimation(index);
+ break;
+ case 'skipBackwardStep':
+ var index = Math.max(0, this.getStateIndex() - 1);
+ this.navigateAnimation(index);
+ break;
+ case 'skipForwardLast':
+ this.navigateAnimation(this.state.sliderTickArray.length - 1);
+ break;
+ case 'skipBackwardEpoch':
+ this.navigateAnimation(0);
+ break;
+ default:
+ this.setState({isPlaying:false, isStopped: false, isPaused: false});
+ break;
+ }
+ }
+ // START: These functions are for the animation controls
+ setHistoricStateValues = (currValue) =>{
+ this.setState({currentStateHistoryValue: currValue, selectedHistoryStateFormatted: moment(currValue).format('dddd, MMMM Do, YYYY h:mm:ss A')});
+ }
+
+ navigateHistory = (time) =>{
+ this.props.history.push('/historyQuery/' + this.props.match.params.type + '/' + this.props.match.params.payloadEnc + '/' + time);
+ }
+
+ setStateValue = (key, value) =>{
+ this.setState((state) => { key : value });
+ }
+ getStateValue = (stateVar) => {
+ return this.state[stateVar];
+ }
+ clear = (interval) =>{
+ clearInterval(interval);
+ }
+ // END
+
+ render() {
+ if(INVLIST.isHistoryEnabled){
+ return (
+ <div>
+ <header className='addPadding jumbotron my-4'>
+ <h1 className='display-2'>History Visualization</h1>
+ <p className='lead'>
+ On this page you have the ability to view a series of network elements in their current and historic state.
+ Zooming and panning are enabled in the graphs. You can use the scrollwheel to zoom and click+drag to pan.
+ If a node is single clicked you can drag the elements to reposition them. Double-clicking a node will
+ show the comparison between the two nodes in a pop-up modal. Animation controls are provided to seemlessly transition between states.
+ You can view the graph difference in a visual form by clicking View Visual Comparison.
+ </p>
+ </header>
+ <Grid fluid={true} className="addPadding">
+ <Row className={this.state.currentErrMsg ? 'show' : 'hidden'} >
+ <div className='addPaddingTop alert alert-danger' role="alert">
+ 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}
+ </div>
+ </Row>
+ <Row className={!this.state.currentErrMsg ? 'show' : 'hidden'} >
+ <Col className='col-lg-12'>
+ <div className='card d3-history-query-card'>
+ <div className='card-header history-query-card-header'>
+ <h2><strong>Current State</strong> of <em>{this.state.queryDisplay}</em></h2>
+ </div>
+ <div className={'card-header ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
+ <button type='button' className='btn btn-outline-primary' onClick={this.viewTopologyCompVisual}>Toggle Visual Comparison</button>
+ </div>
+ <div className='history-query-card-content'>
+ {!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>)}
+ <OutputVisualization identifier="currentState" width={this.svgWidth} height="600" overflow="scroll"/>
+ </div>
+ <div className='card-footer'>
+ <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>
+ </div>
+ </div>
+ </Col>
+ </Row>
+ <Row className={this.state.historicErrMsg ? 'show' : 'hidden'} >
+ <div className='addPaddingTop alert alert-danger' role="alert">
+ 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}
+ </div>
+ </Row>
+ <Row className={!this.state.historicErrMsg ? 'show' : 'hidden'}>
+ <Col className='col-lg-12'>
+ <div className='card d3-history-query-card'>
+ <div className='card-header history-query-card-header'>
+ <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>
+ </div>
+ <div className='card-header'>
+ { (this.state.showSlider && this.state.showTicks) && (<Row className='show-grid'>
+ <Col md={3}>
+ <ReactBootstrapSlider
+ value={this.state.currentStateHistoryValue}
+ change={this.stateHistoryFormat}
+ slideStop={this.stateHistoryFormat}
+ step={ 1 }
+ ticks={ this.state.sliderTickArray }
+ ticks_snap_bounds={ 10000 }
+ orientation="horizontal" />
+ </Col>
+ <Col md={8}>
+ <i className='icon-controls-skipbackstartover animationControlIcon' onClick={() => this.animationControl('skipBackwardEpoch')} role="img"></i>
+ <i className='icon-controls-rewind animationControlIcon' onClick={() => this.animationControl('skipBackwardStep')} role="img"></i>
+ <i className={'icon-controls-pointer ' + (this.state.isPlaying ? 'animationPlayingIcon' : 'animationControlIcon')} onClick={() => this.animationControl('play')} role="img"></i>
+ <i className={'icon-controls-pause ' + (this.state.isPaused ? 'animationPausedIcon' : 'animationControlIcon')} onClick={() => this.animationControl('pause')} role="img"></i>
+ <i className={'icon-controls-stop ' + (this.state.isStopped ? 'animationStoppedIcon' : 'animationControlIcon')} onClick={() => this.animationControl('stop')} role="img"></i>
+ <i className='icon-controls-fastforward animationControlIcon' onClick={() => this.animationControl('skipForwardStep')} role="img"></i>
+ <i className='icon-controls-skipforward animationControlIcon' onClick={() => this.animationControl('skipForwardLast')} role="img"></i>
+ </Col>
+ </Row>
+ )}
+ { (this.state.showSlider && !this.state.showTicks) && (<ReactBootstrapSlider
+ value={this.state.currentStateHistoryValue}
+ change={this.stateHistoryFormat}
+ slideStop={this.stateHistoryFormat}
+ step={this.state.stepEpochStateTime}
+ max={this.state.maxEpochStartTime}
+ min={this.state.minEpochStartTime}
+ orientation="horizontal" />)}
+
+ <p>{this.state.selectedHistoryStateFormatted}</p>
+ <button type='button' className='btn btn-outline-primary' onClick={this.changeHistoryState}>Refresh</button>
+ </div>
+ <div className='history-query-card-content' >
+ {!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>)}
+ <OutputVisualization identifier="historicState" width={this.svgWidth} height="600" overflow="scroll"/>
+ </div>
+ <div className={'card-footer ' + (this.state.topologyHistoryState ? '' : 'hidden')}>
+ <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>
+ </div>
+ </div>
+
+ </Col>
+ </Row>
+ </Grid>
+ <div className='static-modal'>
+ <Modal show={this.state.showNodeModal} onHide={this.closeNodeModal} dialogClassName="modal-override">
+ <Modal.Header>
+ <Modal.Title>Retrieve {this.state.nodeDisplay} History</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Grid fluid={true}>
+ <Row className='show-grid'>
+ <Col md={4}>
+ <HistoryCard node={this.state.selectedNodeHistoryState}/>
+ </Col>
+ <NodeDiffCard diff={this.state.nodeDiff}/>
+ <Col md={4}>
+ <HistoryCard node={this.state.selectedNodeCurrentState}/>
+ </Col>
+ </Row>
+ </Grid>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={this.closeNodeModal}>Close</Button>
+ </Modal.Footer>
+ </Modal>
+ </div>
+ <div className='static-modal'>
+ <Modal show={this.state.showTopologyDiffModal} onHide={this.closeTopologyDiffModal}>
+ <Modal.Header>
+ <Modal.Title>Retrieve Topology History</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Grid fluid={true}>
+ <Row className='show-grid'>
+ <Col md={12}>
+ <TopologyDiffCard node={this.state.totalDiff}/>
+ </Col>
+ </Row>
+ </Grid>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={this.closeTopologyDiffModal}>Close</Button>
+ </Modal.Footer>
+ </Modal>
+ </div>
+ </div>
+ );
+ }else{
+ return(<p>History Not Enabled for this instance, please check config.</p>)
+ }
+ }
+}
+
+export default HistoryQuery;