You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2019/04/15 15:52:56 UTC

[qpid-dispatch] branch master updated: DISPATCH-1308 Added Kill button and confirm dialog to connections

This is an automated email from the ASF dual-hosted git repository.

eallen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/qpid-dispatch.git


The following commit(s) were added to refs/heads/master by this push:
     new 993501d  DISPATCH-1308 Added Kill button and confirm dialog to connections
993501d is described below

commit 993501d152be094e30a517afe4ebcb3d83fe3658
Author: Ernest Allen <ea...@redhat.com>
AuthorDate: Mon Apr 15 11:52:37 2019 -0400

    DISPATCH-1308 Added Kill button and confirm dialog to connections
---
 console/stand-alone/index.html                     |  12 +
 console/stand-alone/main.js                        |   2 +
 console/stand-alone/plugin/html/qdrList.html       |   3 +-
 console/stand-alone/plugin/html/qdrOverview.html   |   2 +-
 console/stand-alone/plugin/js/qdrList.js           | 295 ++++++++++++---------
 console/stand-alone/plugin/js/qdrOverview.js       |  53 ++++
 .../plugin/js/qdrOverviewKillController.js         |  35 +++
 7 files changed, 277 insertions(+), 125 deletions(-)

diff --git a/console/stand-alone/index.html b/console/stand-alone/index.html
index ed2041c..e745089 100644
--- a/console/stand-alone/index.html
+++ b/console/stand-alone/index.html
@@ -125,6 +125,18 @@ under the License.
         })
     </script>
 
+    <script type="text/ng-template" id="confirmKill.html">
+        <div class="modal-header">
+            <h3 class="modal-title">Confirm kill order for {{name}}</h3>
+        </div>
+        <div class="modal-body">
+            Are you sure you want to kill this connection?
+        </div>
+        <div class="modal-footer">
+            <button class="btn btn-danger" type="button" ng-click="ok()">Kill</button>
+            <button class="btn btn-secondary" type="button" ng-click="cancel()">Cancel</button>
+        </div>
+    </script>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/console/stand-alone/main.js b/console/stand-alone/main.js
index c40aa9b..11445c1 100644
--- a/console/stand-alone/main.js
+++ b/console/stand-alone/main.js
@@ -34,6 +34,7 @@ import { NavBarController } from './plugin/js/navbar.js';
 import { OverviewController } from './plugin/js/qdrOverview.js';
 import { OverviewChartsController } from './plugin/js/qdrOverviewChartsController.js';
 import { OverviewLogsController } from './plugin/js/qdrOverviewLogsController.js';
+import { OverviewKillController } from './plugin/js/qdrOverviewKillController.js';
 import { TopologyController } from './plugin/js/topology/qdrTopology.js';
 import { ChordController } from './plugin/js/chord/qdrChord.js';
 import { ListController } from './plugin/js/qdrList.js';
@@ -281,6 +282,7 @@ import { posint } from './plugin/js/posintDirective.js';
   QDR.module.controller('QDR.OverviewController', OverviewController);
   QDR.module.controller('QDR.OverviewChartsController', OverviewChartsController);
   QDR.module.controller('QDR.OverviewLogsController', OverviewLogsController);
+  QDR.module.controller('QDR.OverviewKillController', OverviewKillController);
   QDR.module.controller('QDR.TopAddressesController', TopAddressesController);
   QDR.module.controller('QDR.ChartDialogController', ChartDialogController);
   QDR.module.controller('QDR.DetailDialogController', DetailDialogController);
diff --git a/console/stand-alone/plugin/html/qdrList.html b/console/stand-alone/plugin/html/qdrList.html
index 845339d..bac387a 100644
--- a/console/stand-alone/plugin/html/qdrList.html
+++ b/console/stand-alone/plugin/html/qdrList.html
@@ -83,7 +83,8 @@ under the License.
                 <li ng-repeat="mode in modes" ng-show="isValid(mode)" ng-click="selectMode(mode)"
                     ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html="mode.content"> </li>
             </ul>
-            <h4>{{selectedRecordName | to_trusted}}</h4>
+            <h4>{{selectedRecordName | to_trusted}} <span ng-if="isConnection(selectedEntity)"><button
+                        ng-click="killAConnection(selectedRecordName)" class="btn btn-danger">Kill</button></span></h4>
             <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
                 <div ng-show="selectedRecordName === selectedEntity" class="no-content">There are no
                     {{selectedEntity | safePlural | to_trusted}}</div>
diff --git a/console/stand-alone/plugin/html/qdrOverview.html b/console/stand-alone/plugin/html/qdrOverview.html
index 98d7027..05b27b8 100644
--- a/console/stand-alone/plugin/html/qdrOverview.html
+++ b/console/stand-alone/plugin/html/qdrOverview.html
@@ -203,7 +203,7 @@ under the License.
             <li ng-repeat="mode in gridModes" ng-click="selectMode(mode,'Connection')" ng-class="{active : isModeSelected(mode,'Connection')}" title="{{mode.title}}" ng-bind-html="mode.content"> </li>
         </ul>
         <div ng-if="isModeVisible('Connection','attributes')" class="selectedItems">
-            <h3>Connection {{connection.title}}</h3>
+            <h3>Connection {{connection.title}} <button ng-click="killAConnection(connection)" class="btn btn-danger">Kill</button></h3>
             <div class="grid noHighlight" ui-grid="connectionGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
         </div>
         <div ng-if="isModeVisible('Connection','links')" class="selectedItems">
diff --git a/console/stand-alone/plugin/js/qdrList.js b/console/stand-alone/plugin/js/qdrList.js
index e162371..3d79417 100644
--- a/console/stand-alone/plugin/js/qdrList.js
+++ b/console/stand-alone/plugin/js/qdrList.js
@@ -18,7 +18,7 @@ under the License.
 */
 /* global angular d3 */
 
-import { QDRFolder, QDRLeaf, QDRCore, QDRLogger, QDRTemplatePath, QDRRedirectWhenConnected} from './qdrGlobals.js';
+import { QDRFolder, QDRLeaf, QDRCore, QDRLogger, QDRTemplatePath, QDRRedirectWhenConnected } from './qdrGlobals.js';
 
 export class ListController {
   constructor(QDRService, QDRChartService, $scope, $log, $location, $uibModal, $filter, $timeout, uiGridConstants, $sce) {
@@ -87,7 +87,7 @@ export class ListController {
     ];
     let canDelete = function () {
       if ($scope.selectedEntity === 'listener' && $scope.detailFields) {
-        if ($scope.detailFields.some( function (field) {
+        if ($scope.detailFields.some(function (field) {
           return field.attributeName === 'Http' && field.attributeValue === true;
         })) {
           return false;
@@ -108,20 +108,20 @@ export class ListController {
         $scope.fetchingLog = true;
         let entity; // undefined since it is not supported in the GET-LOG call
         QDRService.management.connection.sendMethod($scope.currentNode.id, entity, {}, $scope.currentMode.op)
-          .then( function (response) {
+          .then(function (response) {
             let statusCode = response.context.message.application_properties.statusCode;
             if (statusCode < 200 || statusCode >= 300) {
               QDRCore.notification('error', response.context.message.statusDescription);
               QDRLog.error('Error ' + response.context.message.statusDescription);
               return;
             }
-            $timeout( function () {
+            $timeout(function () {
               $scope.fetchingLog = false;
-              $scope.logResults = response.response.filter( function (entry) {
+              $scope.logResults = response.response.filter(function (entry) {
                 return entry[0] === $scope.detailsObject.module;
-              }).sort( function (a, b) {
+              }).sort(function (a, b) {
                 return b[5] - a[5];
-              }).map( function (entry) {
+              }).map(function (entry) {
                 return {
                   type: entry[1],
                   message: entry[2],
@@ -139,18 +139,18 @@ export class ListController {
     };
 
     $scope.expandAll = function () {
-      $('#entityTree').fancytree('getTree').visit(function(node){
+      $('#entityTree').fancytree('getTree').visit(function (node) {
         node.setExpanded(true);
       });
     };
     $scope.contractAll = function () {
-      $('#entityTree').fancytree('getTree').visit(function(node){
+      $('#entityTree').fancytree('getTree').visit(function (node) {
         node.setExpanded(false);
       });
     };
 
     if (!QDRService.management.connection.is_connected()) {
-    // we are not connected. we probably got here from a bookmark or manual page reload
+      // we are not connected. we probably got here from a bookmark or manual page reload
       QDRRedirectWhenConnected($location, 'list');
       return;
     }
@@ -160,11 +160,11 @@ export class ListController {
 
     let classOverrides = {
       'connection': function (row, nodeId) {
-        let isConsole = QDRService.utilities.isAConsole (row.properties.value, row.identity.value, row.role.value, nodeId);
+        let isConsole = QDRService.utilities.isAConsole(row.properties.value, row.identity.value, row.role.value, nodeId);
         return isConsole ? 'console' : row.role.value === 'inter-router' ? 'inter-router' : 'external';
       },
       'router.link': function (row, nodeId) {
-        let link = {nodeId: nodeId, connectionId: row.connectionId.value};
+        let link = { nodeId: nodeId, connectionId: row.connectionId.value };
 
         let isConsole = QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link));
         return isConsole ? 'console' : row.linkType.value;
@@ -180,17 +180,17 @@ export class ListController {
     };
 
     var lookupOperations = function () {
-      let ops = QDRService.management.schema().entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ';});
+      let ops = QDRService.management.schema().entityTypes[$scope.selectedEntity].operations.filter(function (op) { return op !== 'READ'; });
       $scope.operation = ops.length ? ops[0] : '';
       return ops;
     };
     let entityTreeChildren = [];
     let expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
     let saveExpanded = function () {
-    // save the list of entities that are expanded
+      // save the list of entities that are expanded
       let tree = $('#entityTree').fancytree('getTree');
       let list = [];
-      tree.visit( function (tnode) {
+      tree.visit(function (tnode) {
         if (tnode.isExpanded()) {
           list.push(tnode.key);
         }
@@ -199,7 +199,7 @@ export class ListController {
     };
 
     var onTreeNodeBeforeActivate = function (event, data) {
-    // if node is toplevel entity
+      // if node is toplevel entity
       if (data.node.data.typeName === 'entity') {
         return false;
         /*
@@ -263,7 +263,7 @@ export class ListController {
     };
     var getExpanded = function (tree) {
       let list = [];
-      tree.visit( function (tnode) {
+      tree.visit(function (tnode) {
         if (tnode.isExpanded()) {
           list.push(tnode);
         }
@@ -323,10 +323,10 @@ export class ListController {
       node.removeChildren();
       if (tableRows.length == 0) {
         newNode = {
-          extraClasses:   'no-data',
-          typeName:   'none',
-          title:      'no data',
-          key:        node.key + '.1'
+          extraClasses: 'no-data',
+          typeName: 'none',
+          title: 'no data',
+          key: node.key + '.1'
         };
         node.addNode(newNode);
         if (expand) {
@@ -334,18 +334,18 @@ export class ListController {
           $scope.selectedRecordName = entity;
         }
       } else {
-        let children = tableRows.map( function (row) {
+        let children = tableRows.map(function (row) {
           let addClass = entity;
           if (classOverrides[entity]) {
             addClass += ' ' + classOverrides[entity](row, $scope.currentNode.id);
           }
           let child = {
-            typeName:   'attribute',
-            extraClasses:   addClass,
-            tooltip:    addClass,
-            key:        row.name.value,
-            title:      row.name.value,
-            details:    row
+            typeName: 'attribute',
+            extraClasses: addClass,
+            tooltip: addClass,
+            key: row.name.value,
+            title: row.name.value,
+            details: row
           };
           return child;
         });
@@ -353,7 +353,7 @@ export class ListController {
       }
       // top level node was expanded
       if (wasExpanded)
-        node.setExpanded(true, {noAnimation: true, noEvents: true, noFocus: true});
+        node.setExpanded(true, { noAnimation: true, noEvents: true, noFocus: true });
       // if the parent node was active, but none of the children were active, active the 1st child
       if (wasActive) {
         if (!activeChildKey) {
@@ -367,19 +367,19 @@ export class ListController {
         newNode = tree.getNodeByKey(activeChildKey);
         // the node may not be there after the update
         if (newNode)
-          newNode.setActive(true, {noFocus: true}); // fires the onTreeNodeActivated event for this node
+          newNode.setActive(true, { noFocus: true }); // fires the onTreeNodeActivated event for this node
       }
       //resizer();
     };
 
 
     var resizer = function () {
-    // this forces the tree and the grid to be the size of the browser window.
-    // the effect is that the tree and the grid will have vertical scroll bars if needed.
-    // the alternative is to let the tree and grid determine the size of the page and have
-    // the scroll bar on the window
+      // this forces the tree and the grid to be the size of the browser window.
+      // the effect is that the tree and the grid will have vertical scroll bars if needed.
+      // the alternative is to let the tree and grid determine the size of the page and have
+      // the scroll bar on the window
       // don't allow HTML in the tree titles
-      $('.fancytree-title').each( function () {
+      $('.fancytree-title').each(function () {
         let unsafe = $(this).html();
         $(this).html(unsafe.replace(/</g, '&lt;').replace(/>/g, '&gt;'));
       });
@@ -388,52 +388,52 @@ export class ListController {
       $scope.details.excessRows = $scope.detailFields.length;
       $scope.gridApi.grid.handleWindowResize();
       $scope.gridApi.core.refresh();
-      
+
     };
     $(window).resize(resizer);
 
     var schemaProps = function (entityName, key, currentNode) {
-      let typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+      let typeMap = { integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea' };
 
       let entity = QDRService.management.schema().entityTypes[entityName];
       let value = entity.attributes[key];
       // skip identity and depricated fields
       if (!value)
-        return {input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': ''};
+        return { input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': '' };
       let description = value.description || '';
       let val = value['default'];
       let disabled = (key == 'identity' || description.startsWith('Deprecated'));
       // special cases
       if (entityName == 'log' && key == 'module') {
-        return {input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': ''};
+        return { input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': '' };
       }
       if (entityName === 'linkRoutePattern' && key === 'connector') {
-      // turn input into a select. the values will be populated later
+        // turn input into a select. the values will be populated later
         value.type = [];
         // find all the connector names and populate the select
         QDRService.management.topology.fetchEntity(currentNode.id, 'connector', ['name'], function (nodeName, dotentity, response) {
-          $scope.detailFields.some( function (field) {
+          $scope.detailFields.some(function (field) {
             if (field.name === 'connector') {
-              field.rawtype = response.results.map (function (result) {return result[0];});
+              field.rawtype = response.results.map(function (result) { return result[0]; });
               return true;
             }
           });
         });
       }
       return {
-        name:       key,
-        humanName:  QDRService.utilities.humanify(key),
-        description:value.description,
-        type:       disabled ? 'disabled' : typeMap[value.type],
-        rawtype:    value.type,
-        input:      typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input'
+        name: key,
+        humanName: QDRService.utilities.humanify(key),
+        description: value.description,
+        type: disabled ? 'disabled' : typeMap[value.type],
+        rawtype: value.type,
+        input: typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input'
           : 'select',
-        selected:   val ? val : undefined,
-        'default':  value['default'],
-        value:      val,
-        required:   value.required,
-        unique:     value.unique,
-        disabled:   disabled
+        selected: val ? val : undefined,
+        'default': value['default'],
+        value: val,
+        required: value.required,
+        unique: value.unique,
+        disabled: disabled
       };
     };
     $scope.getAttributeValue = function (attribute) {
@@ -448,32 +448,32 @@ export class ListController {
       let details = [];
       $scope.detailsObject = {};
       let attrs = Object.keys(row).sort();
-      attrs.forEach( function (attr) {
+      attrs.forEach(function (attr) {
         let changed = $scope.detailFields.filter(function (old) {
           return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false;
         });
         let schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode);
-        details.push( {
-          attributeName:  QDRService.utilities.humanify(attr),
+        details.push({
+          attributeName: QDRService.utilities.humanify(attr),
           attributeValue: attr === 'port' ? row[attr].value : QDRService.utilities.pretty(row[attr].value),
-          name:           attr,
-          changed:        changed.length,
-          rawValue:       row[attr].value,
-          graph:          row[attr].graph,
-          title:          row[attr].title,
-          chartExists:    (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr)),
+          name: attr,
+          changed: changed.length,
+          rawValue: row[attr].value,
+          graph: row[attr].graph,
+          title: row[attr].title,
+          chartExists: (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr)),
           aggchartExists: (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr, true)),
           aggregateValue: QDRService.utilities.pretty(row[attr].aggregate),
-          aggregateTip:   row[attr].aggregateTip,
-
-          input:          schemaEntity.input,
-          type:           schemaEntity.type,
-          required:       schemaEntity.required,
-          unique:         schemaEntity.unique,
-          selected:       schemaEntity.selected,
-          rawtype:        schemaEntity.rawtype,
-          disabled:       schemaEntity.disabled,
-          'default':      schemaEntity['default']
+          aggregateTip: row[attr].aggregateTip,
+
+          input: schemaEntity.input,
+          type: schemaEntity.type,
+          required: schemaEntity.required,
+          unique: schemaEntity.unique,
+          selected: schemaEntity.selected,
+          rawtype: schemaEntity.rawtype,
+          disabled: schemaEntity.disabled,
+          'default': schemaEntity['default']
         });
         $scope.detailsObject[attr] = row[attr].value;
       });
@@ -493,7 +493,7 @@ export class ListController {
       if (tree) {
         let q = d3.queue(10);
         let expanded = getExpanded(tree);
-        expanded.forEach( function (node) {
+        expanded.forEach(function (node) {
           q.defer(q_updateTableData, node.key, node.key === $scope.selectedEntity);
         });
 
@@ -505,7 +505,7 @@ export class ListController {
             if ($scope.ActivatedKey) {
               let node = tree.getNodeByKey($scope.ActivatedKey);
               if (node) {
-                node.setActive(true, {noEvents: true});
+                node.setActive(true, { noEvents: true });
               }
             }
             if (!tree.getActiveNode()) {
@@ -528,41 +528,87 @@ export class ListController {
     };
 
     // The selection dropdown (list of routers) was changed.
-    $scope.selectNode = function(node) {
+    $scope.selectNode = function (node) {
       $scope.selectedNode = node.name;
       $scope.selectedNodeId = node.id;
-      $timeout( function () {
+      $timeout(function () {
         setCurrentNode();
         updateNow = true;
       });
     };
 
-    $scope.$watch('ActivatedKey', function(newValue, oldValue) {
+    $scope.$watch('ActivatedKey', function (newValue, oldValue) {
       if (newValue !== oldValue) {
         localStorage[ActivatedKey] = $scope.ActivatedKey;
       }
     });
-    $scope.$watch('selectedEntity', function(newValue, oldValue) {
+    $scope.$watch('selectedEntity', function (newValue, oldValue) {
       if (newValue !== oldValue) {
         localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
         $scope.operations = lookupOperations();
       }
     });
-    $scope.$watch('selectedNode', function(newValue, oldValue) {
+    $scope.$watch('selectedNode', function (newValue, oldValue) {
       if (newValue !== oldValue) {
         localStorage['QDRSelectedNode'] = $scope.selectedNode;
         localStorage['QDRSelectedNodeId'] = $scope.selectedNodeId;
       }
     });
-    $scope.$watch('selectedRecordName', function(newValue, oldValue) {
+    $scope.$watch('selectedRecordName', function (newValue, oldValue) {
       if (newValue != oldValue) {
         localStorage['QDRSelectedRecordName'] = $scope.selectedRecordName;
       }
     });
+    $scope.isConnection = function (selectedEntity) {
+      return selectedEntity === 'connection';
+    };
+    $scope.killAConnection = function (selectedRecordName) {
+      const identityField = $scope.detailFields.filter(field => field.name === 'identity');
+      if (identityField) {
+        killDialog({
+          entity: {
+            name: selectedRecordName,
+            identity: identityField[0].attributeValue,
+            routerId: $scope.selectedNodeId
+          }
+        });
+      }
+    };
+    function killDialog(row) {
+      let d = $uibModal.open({
+        animation: true,
+        templateUrl: 'confirmKill.html',
+        controller: 'QDR.OverviewKillController',
+        resolve: {
+          entity: row.entity
+        }
+      });
+      d.result.then(function (confirmed) {
+        if (confirmed) {
+          QDRService.management.connection.sendMethod(
+            row.entity.routerId,
+            "connection",
+            { adminStatus: 'deleted', identity: row.entity.identity },
+            "UPDATE",
+            { adminStatus: 'deleted' }
+          ).then(results => {
+            let statusCode = results.context.message.application_properties.statusCode;
+            if (statusCode < 200 || statusCode >= 300) {
+              QDRCore.notification('error', results.context.message.application_properties.statusDescription);
+              QDRLog.error(`Error when killing ${row.entity.name}: ${results.context.message.application_properties.statusDescription}`);
+            } else {
+              QDRCore.notification('success', `Manually killed ${row.entity.name}`);
+              QDRLog.info(`Manually killed ${row.entity.name}`);
+            }
+          });
+          updateExpandedEntities();
+        }
+      });
+    }
 
     /* Called periodically to refresh the data on the page */
     var q_updateTableData = function (entity, expand, callback) {
-    // don't update the data when on the operations tabs
+      // don't update the data when on the operations tabs
       if ($scope.currentMode.id !== 'attributes') {
         callback(null);
         return;
@@ -577,7 +623,7 @@ export class ListController {
           let nameIndex = attributeNames.indexOf('name');
           let identityIndex = attributeNames.indexOf('identity');
           let ent = QDRService.management.schema().entityTypes[entity];
-          for (let i=0; i<records.length; ++i) {
+          for (let i = 0; i < records.length; ++i) {
             let record = records[i];
             let aggregate = aggregates ? aggregates[i] : undefined;
             let row = {};
@@ -594,9 +640,9 @@ export class ListController {
               callback(Error(msg));
               return;
             }
-            for (let j=0; j<attributeNames.length; ++j) {
+            for (let j = 0; j < attributeNames.length; ++j) {
               let col = attributeNames[j];
-              row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+              row[col] = { value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: '' };
               if (ent) {
                 let att = ent.attributes[col];
                 if (att) {
@@ -608,7 +654,7 @@ export class ListController {
                     if (att.graph) {
                       row[col].aggregate = att.graph ? aggregate[j].sum : '';
                       let tip = [];
-                      aggregate[j].detail.forEach( function (line) {
+                      aggregate[j].detail.forEach(function (line) {
                         tip.push(line);
                       });
                       row[col].aggregateTip = angular.toJson(tip);
@@ -619,8 +665,8 @@ export class ListController {
             }
             tableRows.push(row);
           }
-          tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value); });
-          selectRow({entity: dotentity, rows: tableRows, expand: expand});
+          tableRows.sort(function (a, b) { return a.name.value.localeCompare(b.name.value); });
+          selectRow({ entity: dotentity, rows: tableRows, expand: expand });
         }
         callback(null);  // let queue handler know we are done
       };
@@ -639,28 +685,31 @@ export class ListController {
     };
     $scope.detailFields = [];
 
-    $scope.addToGraph = function(rowEntity) {
+    $scope.addToGraph = function (rowEntity) {
       let chart = QDRChartService.registerChart(
-        {nodeId: $scope.selectedNodeId,
+        {
+          nodeId: $scope.selectedNodeId,
           entity: $scope.selectedEntity,
-          name:   $scope.selectedRecordName,
-          attr:    rowEntity.name,
-          forceCreate: true});
+          name: $scope.selectedRecordName,
+          attr: rowEntity.name,
+          forceCreate: true
+        });
 
       doDialog('tmplChartConfig.html', chart);
     };
 
-    $scope.addAllToGraph = function(rowEntity) {
+    $scope.addAllToGraph = function (rowEntity) {
       let chart = QDRChartService.registerChart({
-        nodeId:     $scope.selectedNodeId,
-        entity:     $scope.selectedEntity,
-        name:       $scope.selectedRecordName,
-        attr:       rowEntity.name,
-        type:       'rate',
+        nodeId: $scope.selectedNodeId,
+        entity: $scope.selectedEntity,
+        name: $scope.selectedRecordName,
+        attr: rowEntity.name,
+        type: 'rate',
         rateWindow: updateInterval,
         visibleDuration: 0.25,
         forceCreate: true,
-        aggregate:   true});
+        aggregate: true
+      });
       doDialog('tmplChartConfig.html', chart);
     };
 
@@ -677,7 +726,7 @@ export class ListController {
       if (tip && tip.length) {
         let data = angular.fromJson(tip);
         let table = '<table class=\'tiptable\'><tbody>';
-        data.forEach (function (row) {
+        data.forEach(function (row) {
           table += '<tr>';
           table += '<td>' + row.node + '</td><td align=\'right\'>' + QDRService.utilities.pretty(row.val) + '</td>';
           table += '</tr>';
@@ -688,8 +737,8 @@ export class ListController {
     };
     var aggregateColumn = function () {
       if ((aggregateEntities.indexOf($scope.selectedEntity) > -1 && $scope.detailCols.length != 3) ||
-      (aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
-      // column defs have to be reassigned and not spliced, so no push/pop
+        (aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
+        // column defs have to be reassigned and not spliced, so no push/pop
         $scope.detailCols = [
           {
             field: 'attributeName',
@@ -721,7 +770,7 @@ export class ListController {
 
       $scope.details.columnDefs = $scope.detailCols;
       if ($scope.gridApi)
-        $scope.gridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+        $scope.gridApi.core.notifyDataChange(uiGridConstants.dataChange.COLUMN);
     };
 
     $scope.gridApi = undefined;
@@ -750,17 +799,17 @@ export class ListController {
       multiSelect: false,
       jqueryUIDraggable: true,
       excessRows: 20,
-      onRegisterApi: function(gridApi) {
+      onRegisterApi: function (gridApi) {
         $scope.gridApi = gridApi;
       }
     };
 
-    $scope.$on('$destroy', function() {
+    $scope.$on('$destroy', function () {
       clearTimeout(updateIntervalHandle);
       $(window).off('resize', resizer);
     });
 
-    function gotMethodResponse (entity, context) {
+    function gotMethodResponse(entity, context) {
       let statusCode = context.message.application_properties.statusCode, note;
       if (statusCode < 200 || statusCode >= 300) {
         note = 'Failed to ' + $filter('Pascalcase')($scope.currentMode.op) + ' ' + entity + ': ' + context.message.application_properties.statusDescription;
@@ -775,7 +824,7 @@ export class ListController {
     }
     $scope.ok = function () {
       let attributes = {};
-      $scope.detailFields.forEach( function (field) {
+      $scope.detailFields.forEach(function (field) {
         let value = field.rawValue;
         if (field.input === 'input') {
           if (field.type === 'text' || field.type === 'disabled')
@@ -794,13 +843,13 @@ export class ListController {
         }
       });
       QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
-        .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+        .then(function (response) { gotMethodResponse($scope.selectedEntity, response.context); });
     };
     $scope.remove = function () {
       let identity = $scope.selectedTreeNode.data.details.identity.value;
-      let attributes = {type: $scope.selectedEntity, identity: identity};
+      let attributes = { type: $scope.selectedEntity, identity: identity };
       QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
-        .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+        .then(function (response) { gotMethodResponse($scope.selectedEntity, response.context); });
     };
 
     function doDialog(template, chart) {
@@ -812,11 +861,11 @@ export class ListController {
         templateUrl: QDRTemplatePath + template,
         controller: 'QDR.ChartDialogController',
         resolve: {
-          chart: function() {
+          chart: function () {
             return chart;
           },
           updateTick: function () {
-            return function () {};
+            return function () { };
           },
           dashboard: function () {
             return $scope;
@@ -825,13 +874,13 @@ export class ListController {
             return true;
           }
         }
-      }).result.then(function() {
+      }).result.then(function () {
         QDRChartService.unRegisterChart(chart);
       });
     }
     var setCurrentNode = function () {
       let currentNode;
-      $scope.nodes.some( function (node, i) {
+      $scope.nodes.some(function (node, i) {
         if (node.name === $scope.selectedNode) {
           currentNode = $scope.nodes[i];
           return true;
@@ -852,7 +901,7 @@ export class ListController {
       QDRService.management.topology.stopUpdating();
       QDRService.management.topology.delUpdatedAction('initList');
 
-      $scope.nodes = QDRService.management.topology.nodeList().sort(function (a, b) { 
+      $scope.nodes = QDRService.management.topology.nodeList().sort(function (a, b) {
         return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 0;
       });
       // unable to get node list? Bail.
@@ -861,11 +910,11 @@ export class ListController {
         $location.search('org', 'list');
       }
       if (!angular.isDefined($scope.selectedNode)) {
-      //QDRLog.debug("selectedNode was " + $scope.selectedNode);
+        //QDRLog.debug("selectedNode was " + $scope.selectedNode);
         if ($scope.nodes.length > 0) {
           $scope.selectedNode = $scope.nodes[0].name;
           $scope.selectedNodeId = $scope.nodes[0].id;
-        //QDRLog.debug("forcing selectedNode to " + $scope.selectedNode);
+          //QDRLog.debug("forcing selectedNode to " + $scope.selectedNode);
         }
       }
       setCurrentNode();
@@ -877,7 +926,7 @@ export class ListController {
         }
       }
       let sortedEntities = Object.keys(QDRService.management.schema().entityTypes).sort();
-      sortedEntities.forEach( function (entity) {
+      sortedEntities.forEach(function (entity) {
         if (excludedEntities.indexOf(entity) == -1) {
           if (!angular.isDefined($scope.selectedEntity))
             $scope.selectedEntity = entity;
@@ -914,9 +963,9 @@ export class ListController {
         expand: onTreeNodeExpanded,
         collapse: onTreeNodeCollapsed,
         beforeActivate: onTreeNodeBeforeActivate,
-        beforeSelect: function(event, data){
+        beforeSelect: function (event, data) {
           // A node is about to be selected: prevent this for folders:
-          if( data.node.isFolder() ){
+          if (data.node.isFolder()) {
             return false;
           }
         },
@@ -933,7 +982,7 @@ export class ListController {
         source: entityTreeChildren
       });
     };
-    QDRService.management.topology.ensureAllEntities({entity: 'connection'}, function () {
+    QDRService.management.topology.ensureAllEntities({ entity: 'connection' }, function () {
       QDRService.management.topology.setUpdateEntities(['connection']);
       // keep the list of routers up to date
       QDRService.management.topology.startUpdating(true);
@@ -946,7 +995,7 @@ export class ListController {
       let now = Date.now();
       if (((now - last_updated) >= updateInterval) || updateNow) {
         updateNow = false;
-        $timeout( function () {
+        $timeout(function () {
           updateExpandedEntities();
           resizer();
         });
diff --git a/console/stand-alone/plugin/js/qdrOverview.js b/console/stand-alone/plugin/js/qdrOverview.js
index 11bebd5..0350464 100644
--- a/console/stand-alone/plugin/js/qdrOverview.js
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -86,6 +86,8 @@ export class OverviewController {
       if (!gridApi.selection)
         return;
       gridApi.selection.on.rowSelectionChanged($scope, function (row) {
+        if (row.cancelEvent)
+          return;
         let treeKey = row.grid.options.treeKey;
         if (treeKey && row.entity[treeKey]) {
           let key = row.entity[treeKey];
@@ -797,6 +799,11 @@ export class OverviewController {
           field: 'authentication',
           displayName: 'authentication'
         },
+        {
+          field: 'Kill',
+          width: '4%',
+          cellTemplate: '<div><button class="btn btn-danger" ng-click="grid.appScope.killConnection(row, $event)">Kill</button></div>'
+        }
       ],
       enablePaging: true,
       showFooter: $scope.totalConnections > 50,
@@ -812,6 +819,52 @@ export class OverviewController {
       enableRowHeaderSelection: false,
       noUnselect: true
     };
+    $scope.killConnection = function (row, event) {
+      row.cancelEvent = true;
+      killDialog(row);
+      event.preventDefault();
+      return false;
+    };
+    $scope.killAConnection = function (connection) {
+      killDialog({ entity: connection.data.fields });
+    };
+    function killDialog(row) {
+      let d = $uibModal.open({
+        animation: true,
+        templateUrl: 'confirmKill.html',
+        controller: 'QDR.OverviewKillController',
+        resolve: {
+          entity: row.entity
+        }
+      });
+      d.result.then(function (confirmed) {
+        row.cancelEvent = false;
+        if (confirmed) {
+          console.log(`confirmed kill of ${row.entity.name}`);
+          QDRService.management.connection.sendMethod(
+            row.entity.routerId,
+            "connection",
+            { adminStatus: 'deleted', identity: row.entity.identity },
+            "UPDATE",
+            { adminStatus: 'deleted' }
+          ).then(results => {
+            let statusCode = results.context.message.application_properties.statusCode;
+            if (statusCode < 200 || statusCode >= 300) {
+              QDRCore.notification('error', results.context.message.application_properties.statusDescription);
+              QDRLog.error(`Error when killing ${row.entity.name}: ${results.context.message.application_properties.statusDescription}`);
+            } else {
+              QDRCore.notification('success', `Manually killed ${row.entity.name}`);
+              QDRLog.info(`Manually killed ${row.entity.name}`);
+            }
+            updateExpanded();
+          });
+        }
+        else
+          console.log(`cancelled kill order for ${row.entity.name}`);
+      }, function () {
+        QDRLog.debug(`cancelled kill of ${row.entity.name}`);
+      });
+    }
     // get info for a all connections
     var allConnectionInfo = function (connection, callback) {
       getAllConnectionFields([updateConnectionGrid, updateConnectionTree, function () { callback(null); }]);
diff --git a/console/stand-alone/plugin/js/qdrOverviewKillController.js b/console/stand-alone/plugin/js/qdrOverviewKillController.js
new file mode 100644
index 0000000..daa316d
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverviewKillController.js
@@ -0,0 +1,35 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+*/
+
+export class OverviewKillController {
+  constructor($scope, $uibModalInstance, entity) {
+    this.controllerName = 'QDR.OverviewKillController';
+
+    $scope.name = entity.name;
+
+    $scope.ok = function () {
+      $uibModalInstance.close(true);
+    };
+    $scope.cancel = function () {
+      $uibModalInstance.close(false);
+    };
+
+  }
+}
+OverviewKillController.$inject = ['$scope', '$uibModalInstance', 'entity'];


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org