DISPATCH-350 Console: Display links popup when client is clicked on topology page. Display links table for external connections on overview page
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrList.html b/console/hawtio/src/main/webapp/plugin/html/qdrList.html
index 1459c28..e259e64 100644
--- a/console/hawtio/src/main/webapp/plugin/html/qdrList.html
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrList.html
@@ -57,6 +57,7 @@
<!-- ng-pattern="testPattern(attribute)" -->
<input ng-if="attribute.type == 'number'" type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.rawValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
<input ng-if="attribute.type == 'text'" type="text" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
+ <textarea ng-if="attribute.type == 'textarea'" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"></textarea>
<span ng-if="attribute.type == 'disabled'" >{{getAttributeValue(attribute)}}</span>
</div>
<div ng-if="attribute.input == 'select'">
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
index e713e19..14653b1 100644
--- a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
@@ -98,8 +98,18 @@
</script>
<script type="text/ng-template" id="connection.html">
<div class="row-fluid">
- <h3>Connection {{connection.data.title}}</h3>
- <div class="gridStyle noHighlight" ng-grid="connectionGrid"></div>
+ <ul class="nav nav-tabs">
+ <li ng-repeat="mode in connectionModes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li>
+ </ul>
+ <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
+ <h3>Connection {{connection.data.title}}</h3>
+ <div class="gridStyle noHighlight" ng-grid="connectionGrid"></div>
+ </div>
+ <div ng-show="currentMode.id === 'links'" class="selectedItems">
+ <h3>External links for connection {{connection.data.title}}</h3>
+ <!-- <button title="{{quiesceState.buttonText}} links" type="button" ng-hide="quiesceHide()" ng-class="quiesceClass()" class="btn" ng-click="quiesceAllClicked()" ng-disabled="quiesceState.buttonDisabled">{{quiesceState.buttonText}}</button> -->
+ <div class="gridStyle noHighlight" ng-grid="connectionLinksGrid"></div>
+ </div>
</div>
</script>
<script type="text/ng-template" id="titleHeaderCellTemplate.html">
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
index 61ca2c1..864441f 100644
--- a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
@@ -49,7 +49,6 @@
-->
<div id="topology" ng-show="mode == 'Diagram'"><!-- d3 toplogy here --></div>
<div id="crosssection"><!-- d3 pack here --></div>
- <!-- <div id="addRouter" ng-show="mode == 'Add Node'"></div> -->
<div id="node_context_menu" class="contextMenu">
<ul>
<li class="na" ng-class="{new: contextNode.cls == 'temp'}" ng-click="addingNode.trigger = 'editNode'">Edit...</li>
@@ -73,9 +72,26 @@
<div id="svg_legend"></div>
<div id="multiple_details">
<div class="gridStyle" ng-grid="multiDetails"></div>
+ </div>
+ <div id="link_details">
+ <div class="gridStyle" ng-grid="linkDetails"></div>
</div>
</div>
</div>
+
+
+<script type="text/ng-template" id="titleHeaderCellTemplate.html">
+ <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
+ <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
+ <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
+ <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
+ <div class="ngSortPriority">{{col.sortPriority}}</div>
+ </div>
+</script>
+<script type="text/ng-template" id="titleCellTemplate.html">
+ <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
+</script>
+
<!--
This is the template for the node edit dialog that is displayed.
-->
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
index debc508..a9a76ec 100644
--- a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
@@ -1000,11 +1000,11 @@
displayName: 'Unsettled',
headerCellTemplate: 'titleHeaderCellTemplate.html',
cellClass: 'grid-values'
- },
+ }/*,
{
cellClass: 'gridCellButton',
cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
- }
+ }*/
]
}
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
index 2c4e6b6..3b69c31 100644
--- a/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrTopology.js
@@ -69,28 +69,199 @@
QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$dialog',
function($scope, $rootScope, QDRService, $location, $timeout, $dialog) {
- $scope.multiData = [{name: ''}, {name: ''}, {name: ''}]
+ $scope.multiData = []
+ $scope.selectedClient = [];
+ $scope.quiesceState = {}
+ var dontHide = false;
+ $scope.quiesceConnection = function (row) {
+ var entity = row.entity;
+ var state = $scope.quiesceState[entity.connectionId].state;
+ if (state === 'enabled') {
+ // start quiescing all links
+ $scope.quiesceState[entity.connectionId].state = 'quiescing';
+ } else if (state === 'quiesced') {
+ // start reviving all links
+ $scope.quiesceState[entity.connectionId].state = 'reviving';
+ }
+ $scope.multiDetails.updateState(entity);
+ dontHide = true;
+ $scope.multiDetails.selectRow(row.rowIndex, true);
+ $scope.multiDetails.showLinksList(row)
+ }
+ $scope.quiesceDisabled = function (row) {
+ return $scope.quiesceState[row.entity.connectionId].buttonDisabled;
+ }
+ $scope.quiesceText = function (row) {
+ return $scope.quiesceState[row.entity.connectionId].buttonText;
+ }
+ $scope.quiesceClass = function (row) {
+ var stateClassMap = {
+ enabled: 'btn-primary',
+ quiescing: 'btn-warning',
+ reviving: 'btn-warning',
+ quiesced: 'btn-danger'
+ }
+ return stateClassMap[$scope.quiesceState[row.entity.connectionId].state];
+ }
$scope.multiDetails = {
data: 'multiData',
+ selectedItems: $scope.selectedClient,
+ multiSelect: false,
+ afterSelectionChange: function (obj) {
+ if (obj.selected && obj.orig) {
+ var detailsDiv = d3.select('#link_details')
+ var isVis = detailsDiv.style('display') === 'block';
+ if (!dontHide && isVis && $scope.connectionId === obj.entity.connectionId) {
+ hideLinkDetails();
+ return;
+ }
+ dontHide = false;
+ $scope.multiDetails.showLinksList(obj)
+ }
+ },
+ showLinksList: function (obj) {
+ $scope.linkData = obj.entity.linkData;
+ $scope.connectionId = obj.entity.connectionId;
+ var visibleLen = Math.min(obj.entity.linkData.length, 10)
+ var left = parseInt(d3.select('#multiple_details').style("left"))
+ var detailsDiv = d3.select('#link_details')
+ detailsDiv
+ .style({
+ display: 'block',
+ opacity: 1,
+ left: (left + 20) + "px",
+ top: (mouseY + 20 + $(document).scrollTop()) + "px",
+ height: (visibleLen + 1) * 30 + "px", // +1 for the header row
+ 'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'})
+ },
+ updateState: function (entity) {
+ var state = $scope.quiesceState[entity.connectionId].state
+
+ // count enabled and disabled links for this connection
+ var enabled = 0, disabled = 0;
+ entity.linkData.forEach ( function (link) {
+ if (link.adminStatus === 'enabled')
+ ++enabled;
+ if (link.adminStatus === 'disabled')
+ ++disabled;
+ })
+
+ var linkCount = entity.linkData.length;
+ // if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled
+ if (state === 'quiescing' && (enabled > 0)) {
+ $scope.quiesceState[entity.connectionId].buttonText = 'Quiescing';
+ $scope.quiesceState[entity.connectionId].buttonDisabled = true;
+ } else
+ // if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced
+ // if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced
+ if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) {
+ $scope.quiesceState[entity.connectionId].buttonText = 'Revive';
+ $scope.quiesceState[entity.connectionId].buttonDisabled = false;
+ $scope.quiesceState[entity.connectionId].state = 'quiesced'
+ } else
+ // if state is reviving and any links are disabled, button should say 'Reviving' and be disabled
+ if (state === 'reviving' && (disabled > 0)) {
+ $scope.quiesceState[entity.connectionId].buttonText = 'Reviving';
+ $scope.quiesceState[entity.connectionId].buttonDisabled = true;
+ } else
+ // if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled
+ if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) {
+ $scope.quiesceState[entity.connectionId].buttonText = 'Quiesce';
+ $scope.quiesceState[entity.connectionId].buttonDisabled = false;
+ $scope.quiesceState[entity.connectionId].state = 'enabled'
+ }
+ },
columnDefs: [
{
field: 'host',
- displayName: 'Host'
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'Connection host'
},
{
field: 'user',
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
displayName: 'User'
},
{
field: 'properties',
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
displayName: 'Properties'
- },
+ }/*,
{
- field: 'isEncrypted',
- displayName: 'Encrypted'
- }
+ cellClass: 'gridCellButton',
+ cellTemplate: '<button title="{{quiesceText(row)}} the links" type="button" ng-class="quiesceClass(row)" class="btn" ng-click="$event.stopPropagation();quiesceConnection(row)" ng-disabled="quiesceDisabled(row)">{{quiesceText(row)}}</button>'
+ }*/
]
};
+ $scope.linkData = [];
+ $scope.quiesceLinkClass = function (row) {
+ var stateClassMap = {
+ enabled: 'btn-primary',
+ disabled: 'btn-danger'
+ }
+ return stateClassMap[row.entity.adminStatus]
+ //return stateClassMap[$scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity]];
+ }
+ $scope.quiesceLink = function (row) {
+ var state = row.entity.adminStatus === 'enabled' ? 'disabled' : 'enabled';
+ $scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity] = state;
+ }
+ $scope.quiesceLinkDisabled = function (row) {
+ return false;
+ }
+ $scope.quiesceLinkText = function (row) {
+ return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce";
+ }
+ $scope.linkDetails = {
+ data: 'linkData',
+ columnDefs: [
+ {
+ field: 'adminStatus',
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'Admin stat'
+ },
+ {
+ field: 'dir',
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'dir'
+ },
+ {
+ field: 'owningAddr',
+ cellTemplate: "titleCellTemplate.html",
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'Address'
+ },
+ {
+ field: 'deliveryCount',
+ displayName: 'Delivered',
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ cellClass: 'grid-values'
+
+ },
+ {
+ field: 'undeliveredCount',
+ displayName: 'Undelivered',
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ cellClass: 'grid-values'
+ },
+ {
+ field: 'unsettledCount',
+ displayName: 'Unsettled',
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ cellClass: 'grid-values'
+ }/*,
+
+ {
+ cellClass: 'gridCellButton',
+ cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
+ }*/
+ ]
+ }
if (!QDRService.connected) {
// we are not connected. we probably got here from a bookmark or manual page reload
@@ -806,6 +977,16 @@
.style("opacity", 0)
.each("end", function (d) {
d3.select("#multiple_details").style("display", "none")
+ stopUpdateConnectionsGrid();
+ })
+ hideLinkDetails();
+ }
+ function hideLinkDetails() {
+ d3.select("#link_details").transition()
+ .duration(500)
+ .style("opacity", 0)
+ .each("end", function (d) {
+ d3.select("#link_details").style("display", "none")
})
}
@@ -904,6 +1085,7 @@
})
.on("click", function (d) {
dblckickPos = d3.mouse(this);
+ QDR.log.debug("dblckickPos is [" + dblckickPos[0] + ", " + dblckickPos[1] + "]")
d3.event.stopPropagation();
clearPopups();
var diameter = 400;
@@ -1011,7 +1193,6 @@
circle.selectAll('circle')
.classed('selected', function (d) { return (d === selected_node) })
.classed('fixed', function (d) { return (d.fixed & 0b1) })
- //.classed('multiple', function(d) { return (d.normals && d.normals.length > 1) } )
// add new circle nodes. if nodes[] is longer than the existing paths, add a new path for each new element
var g = circle.enter().append('svg:g')
@@ -1165,19 +1346,7 @@
}
clickPos = d3.mouse(this);
d3.event.stopPropagation();
- $scope.multiData = []
- d.normals.forEach( function (n) {
- $scope.multiData.push(n)
- })
- $scope.$apply();
- d3.select('#multiple_details')
- .style({
- display: 'block',
- opacity: 1,
- height: (d.normals.length + 1) * 30 + "px",
- 'overflow-y': d.normals.length > 10 ? 'scroll' : 'hidden',
- left: (mouseX + $(document).scrollLeft()) + "px",
- top: (mouseY + $(document).scrollTop()) + "px"})
+ startUpdateConnectionsGrid(d);
})
var appendContent = function (g) {
@@ -1300,6 +1469,124 @@
}
+ var startUpdateConnectionsGrid = function (d) {
+ var extendConnections = function () {
+ $scope.multiData = []
+ var normals = d.normals;
+ // find updated normals for d
+ d3.selectAll('.normal')
+ .each(function(newd) {
+ if (newd.id == d.id && newd.name == d.name) {
+ normals = newd.normals;
+ }
+ });
+ if (normals) {
+ normals.forEach( function (n) {
+ var nodeInfo = QDRService.topology.nodeInfo();
+ var links = nodeInfo[n.key]['.router.link'];
+ var linkTypeIndex = links.attributeNames.indexOf('linkType');
+ var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+ n.linkData = [];
+ links.results.forEach( function (link) {
+ if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.connectionId) {
+ var l = {};
+ l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr');
+ l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir');
+ if (l.owningAddr && l.owningAddr.length > 2)
+ if (l.owningAddr[0] === 'M')
+ l.owningAddr = l.owningAddr.substr(2)
+ else
+ l.owningAddr = l.owningAddr.substr(1)
+
+ l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
+ l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
+ l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
+ l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus');
+ l.identity = QDRService.valFor(links.attributeNames, link, 'identity')
+ l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId')
+
+ // TODO: remove this fake quiescing/reviving logic when the routers do the work
+ initConnState(n.connectionId)
+ if ($scope.quiesceState[n.connectionId].linkStates[l.identity])
+ l.adminStatus = $scope.quiesceState[n.connectionId].linkStates[l.identity];
+ if ($scope.quiesceState[n.connectionId].state == 'quiescing') {
+ if (l.adminStatus === 'enabled') {
+ // 25% chance of switching
+ var chance = Math.floor(Math.random() * 2);
+ if (chance == 1) {
+ l.adminStatus = 'disabled';
+ $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'disabled';
+ }
+ }
+ }
+ if ($scope.quiesceState[n.connectionId].state == 'reviving') {
+ if (l.adminStatus === 'disabled') {
+ // 25% chance of switching
+ var chance = Math.floor(Math.random() * 2);
+ if (chance == 1) {
+ l.adminStatus = 'enabled';
+ $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'enabled';
+ }
+ }
+ }
+ QDR.log.debug("pushing link state for " + l.owningAddr + " status: "+ l.adminStatus)
+
+
+ n.linkData.push(l)
+ }
+ })
+ $scope.multiData.push(n)
+ if (n.connectionId == $scope.connectionId)
+ $scope.linkData = n.linkData;
+ initConnState(n.connectionId)
+ $scope.multiDetails.updateState(n)
+ })
+ }
+ $scope.$apply();
+
+ d3.select('#multiple_details')
+ .style({
+ height: (normals.length + 1) * 30 + "px",
+ 'overflow-y': normals.length > 10 ? 'scroll' : 'hidden'
+ })
+ }
+
+ QDRService.addUpdatedAction("normalsStats", extendConnections)
+ extendConnections();
+ clearPopups();
+ var display = 'block'
+ var left = mouseX + $(document).scrollLeft()
+ if (d.normals.length === 1) {
+ display = 'none'
+ left = left - 30;
+ mouseY = mouseY - 20
+ }
+ d3.select('#multiple_details')
+ .style({
+ display: display,
+ opacity: 1,
+ left: (mouseX + $(document).scrollLeft()) + "px",
+ top: (mouseY + $(document).scrollTop()) + "px"})
+ if (d.normals.length === 1) {
+ // simulate a click on the connection to popup the link details
+ $scope.multiDetails.showLinksList( {entity: d} )
+ }
+ }
+ var stopUpdateConnectionsGrid = function () {
+ QDRService.delUpdatedAction("normalsStats");
+ }
+
+ var initConnState = function (id) {
+ if (!angular.isDefined($scope.quiesceState[id])) {
+ $scope.quiesceState[id] = {
+ state: 'enabled',
+ buttonText: 'Quiesce',
+ buttonDisabled: false,
+ linkStates: {}
+ }
+ }
+ }
+
function nextHop(thisNode, d) {
if ((thisNode) && (thisNode != d)) {
var target = findNextHopNode(thisNode, d);