ONOS-1783 - GUI -- Refresh buttons for tabular views added. Minor table.js refactor.

Change-Id: Iee6c65fa8477b367e40a556c3c820ca454601a5f
diff --git a/web/gui/src/main/webapp/app/common.css b/web/gui/src/main/webapp/app/common.css
index d0d878e..1aef4fb 100644
--- a/web/gui/src/main/webapp/app/common.css
+++ b/web/gui/src/main/webapp/app/common.css
@@ -24,3 +24,63 @@
     padding-top: 20px;
     padding-bottom: 20px;
 }
+
+/* Tabular view upper right control buttons */
+
+div.ctrl-btns {
+    display: inline-block;
+    float: right;
+    height: 44px;
+    margin-right: 24px;
+    margin-top: 7px;
+}
+
+
+div.ctrl-btns div {
+    display: inline-block;
+    padding: 4px;
+    cursor: pointer;
+}
+
+/* Inactive */
+.light .ctrl-btns div g.icon rect,
+.light .ctrl-btns div:hover g.icon rect {
+    fill: #eee;
+}
+.dark .ctrl-btns div g.icon rect,
+.dark .ctrl-btns div:hover g.icon rect {
+    fill: #222;
+}
+
+.light .ctrl-btns div g.icon use {
+    fill: #ddd;
+}
+.dark .ctrl-btns div g.icon use {
+    fill: #333;
+}
+
+/* Active hover */
+.light .ctrl-btns div.active:hover g.icon rect {
+    fill: #800;
+}
+
+.dark .ctrl-btns div.active:hover g.icon rect {
+    fill: #CE5650;
+}
+
+/* Active */
+.light .ctrl-btns div.active g.icon use {
+    fill: #fff;
+}
+.dark .ctrl-btns div.active g.icon use {
+    fill: #eee;
+}
+
+.light .ctrl-btns div.active g.icon rect {
+    fill: #bbb;
+}
+.dark .ctrl-btns div.active g.icon rect {
+    fill: #444;
+}
+
+
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index 737a8a8..2dfd0bb 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -37,6 +37,8 @@
         play: 'play',
         stop: 'stop',
 
+        crown: 'crown',
+
         upArrow: 'triangleUp',
         downArrow: 'triangleDown',
 
@@ -191,7 +193,7 @@
         return g;
     }
 
-    function createSortIcon() {
+    function sortIcons() {
         function sortAsc(div) {
             div.style('display', 'inline-block');
             loadEmbeddedIcon(div, 'upArrow', 10);
@@ -236,7 +238,7 @@
                 addDeviceIcon: addDeviceIcon,
                 addHostIcon: addHostIcon,
                 iconConfig: function () { return config; },
-                createSortIcon: createSortIcon
+                sortIcons: sortIcons
             };
         }]);
 
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.js b/web/gui/src/main/webapp/app/fw/widget/table.js
index 6b94dd2..d9fddda 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.js
+++ b/web/gui/src/main/webapp/app/fw/widget/table.js
@@ -27,11 +27,15 @@
     var tableIconTdSize = 33,
         pdg = 12,
         colWidth = 'col-width',
-        tableIcon = 'table-icon';
+        tableIcon = 'table-icon',
+        asc = 'asc',
+        desc = 'desc',
+        none = 'none';
 
     // internal state
     var currCol = {},
-        prevCol = {};
+        prevCol = {},
+        sortIconAPI;
 
     // Functions for creating a fixed header on a table (Angular Directive)
 
@@ -115,48 +119,49 @@
         setTableHeight(th, tb);
     }
 
-    // Functions for sorting table rows by header and choosing appropriate icon
+    // Functions for sorting table rows by header
 
-    function updateSortingIcons(thElem, api) {
-        var div;
+    function updateSortDirection(thElem) {
+        sortIconAPI.sortNone(thElem.select('div'));
+        currCol.div = thElem.append('div');
         currCol.colId = thElem.attr('colId');
 
         if (currCol.colId === prevCol.colId) {
-            (currCol.icon === 'downArrow') ?
-                currCol.icon = 'upArrow' :
-                currCol.icon = 'downArrow';
-            prevCol.icon = currCol.icon;
+            (currCol.dir === desc) ? currCol.dir = asc : currCol.dir = desc;
+            prevCol.dir = currCol.dir;
         } else {
-            currCol.icon = 'upArrow';
-            prevCol.icon = 'tableColSortNone';
+            currCol.dir = asc;
+            prevCol.dir = none;
         }
+        (currCol.dir === asc) ?
+            sortIconAPI.sortAsc(currCol.div) : sortIconAPI.sortDesc(currCol.div);
 
-        div = thElem.select('div');
-        api.sortNone(div);
-        div = thElem.append('div');
-
-        if (currCol.icon === 'upArrow') {
-            api.sortAsc(div);
-        } else {
-            api.sortDesc(div);
-        }
-
-        if (prevCol.colId !== undefined &&
-            prevCol.icon === 'tableColSortNone') {
-            api.sortNone(prevCol.elem.select('div'));
+        if (prevCol.colId && prevCol.dir === none) {
+            sortIconAPI.sortNone(prevCol.div);
         }
 
         prevCol.colId = currCol.colId;
-        prevCol.elem = thElem;
+        prevCol.div = currCol.div;
     }
 
     function sortRequestParams() {
         return {
             sortCol: currCol.colId,
-            sortDir: (currCol.icon === 'upArrow' ? 'asc' : 'desc')
+            sortDir: currCol.dir
         };
     }
 
+    function resetSortIcons() {
+        if (currCol.div) {
+            sortIconAPI.sortNone(currCol.div);
+        }
+        if (prevCol.div) {
+            sortIconAPI.sortNone(prevCol.div);
+        }
+        currCol = {};
+        prevCol = {};
+    }
+
     angular.module('onosWidget')
         .directive('onosFixedHeader', ['$window', 'FnService', 'MastService',
             function (_$window_, _fs_, _mast_) {
@@ -210,8 +215,8 @@
                 link: function (scope, element) {
                     $log = _$log_;
                     is = _is_;
-                    var table = d3.select(element[0]),
-                        sortIconAPI = is.createSortIcon();
+                    var table = d3.select(element[0]);
+                        sortIconAPI = is.sortIcons();
 
                     // when a header is clicked, change its icon tag
                     // and get sorting order to send to the server.
@@ -219,7 +224,7 @@
                         var thElem = d3.select(this);
 
                         if (thElem.attr('sortable') === '') {
-                            updateSortingIcons(thElem, sortIconAPI);
+                            updateSortDirection(thElem);
                             scope.ctrlCallback({
                                 requestParams: sortRequestParams()
                             });
@@ -227,6 +232,16 @@
                     });
                 }
             };
+        }])
+
+        .factory('TableService', ['$log', 'IconService',
+
+            function ($log, is) {
+                sortIconAPI = is.sortIcons();
+
+                return {
+                    resetSortIcons: resetSortIcons
+                };
         }]);
 
 }());
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index a44b219..c2d61cc 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -23,59 +23,11 @@
 }
 
 #ov-app div.ctrl-btns {
-    display:inline-block;
-    float: right;
-    width: 200px;
-    height: 44px;
-    margin-right: 24px;
-    margin-top: 7px;
+    width: 290px;
 }
 
-div.ctrl-btns div {
-    display: inline-block;
-    padding: 4px;
-    cursor: pointer;
+#ov-app div.ctrl-btns div.separator  {
+    cursor: auto;
+    width: 24px;
+    border: none;
 }
-
-
-/* Inactive */
-.light .ctrl-btns div g.icon rect,
-.light .ctrl-btns div:hover g.icon rect {
-    fill: #eee;
-}
-.dark .ctrl-btns div g.icon rect,
-.dark .ctrl-btns div:hover g.icon rect {
-    fill: #222;
-}
-
-.light .ctrl-btns div g.icon use {
-    fill: #ddd;
-}
-.dark .ctrl-btns div g.icon use {
-    fill: #333;
-}
-
-/* Active hover */
-.light .ctrl-btns div.active:hover g.icon rect {
-    fill: #800;
-}
-
-.dark .ctrl-btns div.active:hover g.icon rect {
-    fill: #CE5650;
-}
-
-/* Active */
-.light .ctrl-btns div.active g.icon use {
-    fill: #fff;
-}
-.dark .ctrl-btns div.active g.icon use {
-    fill: #eee;
-}
-
-.light .ctrl-btns div.active g.icon rect {
-    fill: #bbb;
-}
-.dark .ctrl-btns div.active g.icon rect {
-    fill: #444;
-}
-
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index e1e1647..01ab4bc 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -3,6 +3,10 @@
     <div class="tabular-header">
         <h2>Applications ({{ctrl.tableData.length}} total)</h2>
         <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+            <div class="separator"></div>
             <div id="app-install"    icon icon-size="36" icon-id="plus" class="active"></div>
             <div id="app-activate"   icon icon-size="36" icon-id="play"></div>
             <div id="app-deactivate" icon icon-size="36" icon-id="stop"></div>
diff --git a/web/gui/src/main/webapp/app/view/app/app.js b/web/gui/src/main/webapp/app/view/app/app.js
index 236099d..d0b561b 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -25,9 +25,9 @@
 
     angular.module('ovApp', [])
     .controller('OvAppCtrl',
-        ['$log', '$scope', 'TableBuilderService', 'WebSocketService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService', 'WebSocketService',
 
-    function ($log, $scope, tbs, wss) {
+    function ($log, $scope, ts, tbs, wss) {
         function selCb($event, row) {
             selRow = angular.element($event.currentTarget);
             selection = row;
@@ -45,6 +45,12 @@
             document.getElementById('file').dispatchEvent(evt);
         });
 
+        $scope.refresh = function () {
+            $log.debug('Refreshing application page');
+            ts.resetSortIcons();
+            $scope.sortCallback();
+        };
+
         function appAction(action) {
             if (selection) {
                 $log.debug('Initiating uninstall of', selection);
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.css b/web/gui/src/main/webapp/app/view/cluster/cluster.css
index 5025062..83ba8d8 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.css
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.css
@@ -18,5 +18,10 @@
  ONOS GUI -- Cluster View -- CSS file
  */
 
-#ov-cluster td {
+#ov-cluster h2 {
+    display: inline-block;
+}
+
+#ov-cluster div.ctrl-btns {
+    width: 45px;
 }
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.html b/web/gui/src/main/webapp/app/view/cluster/cluster.html
index b08f4ac..f418601 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.html
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.html
@@ -18,6 +18,11 @@
 <div id="ov-cluster">
     <div class="tabular-header">
         <h2>Cluster Nodes ({{ctrl.tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
 
     <table class="summary-list"
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.js b/web/gui/src/main/webapp/app/view/cluster/cluster.js
index 0f4a4f8..d88c03c 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.js
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.js
@@ -23,15 +23,21 @@
 
     angular.module('ovCluster', [])
         .controller('OvClusterCtrl',
-        ['$log', '$scope', 'TableBuilderService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService',
 
-            function ($log, $scope, tbs) {
+            function ($log, $scope, ts, tbs) {
                 tbs.buildTable({
                     self: this,
                     scope: $scope,
                     tag: 'cluster'
                 });
 
+                $scope.refresh = function () {
+                    $log.debug('Refreshing cluster nodes page');
+                    ts.resetSortIcons();
+                    $scope.sortCallback();
+                };
+
                 $log.log('OvClusterCtrl has been created');
             }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/device/device.css b/web/gui/src/main/webapp/app/view/device/device.css
index a0894bb..9a1a1c8 100644
--- a/web/gui/src/main/webapp/app/view/device/device.css
+++ b/web/gui/src/main/webapp/app/view/device/device.css
@@ -18,6 +18,14 @@
  ONOS GUI -- Device View -- CSS file
  */
 
+#ov-device h2 {
+    display: inline-block;
+}
+
+#ov-device div.ctrl-btns {
+    width: 45px;
+}
+
 /* More in generic panel.css */
 
 #device-details-panel.floatpanel {
diff --git a/web/gui/src/main/webapp/app/view/device/device.html b/web/gui/src/main/webapp/app/view/device/device.html
index d68dbed..7fad0ad 100644
--- a/web/gui/src/main/webapp/app/view/device/device.html
+++ b/web/gui/src/main/webapp/app/view/device/device.html
@@ -2,6 +2,11 @@
 <div id="ov-device">
     <div class="tabular-header">
         <h2>Devices ({{ctrl.tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
 
     <table class="summary-list"
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index c600e89..38953d7 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -203,12 +203,12 @@
 
     angular.module('ovDevice', [])
     .controller('OvDeviceCtrl',
-        ['$log', '$scope', 'TableBuilderService', 'FnService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService', 'FnService',
             'MastService', 'PanelService', 'WebSocketService', 'IconService',
             'ButtonService', 'NavService', 'TooltipService',
 
         function (_$log_, _$scope_,
-                  tbs, _fs_, _mast_, _ps_, _wss_, _is_, _bns_, _ns_, _ttip_) {
+                  ts, tbs, _fs_, _mast_, _ps_, _wss_, _is_, _bns_, _ns_, _ttip_) {
             $log = _$log_;
             $scope = _$scope_;
             fs = _fs_;
@@ -243,6 +243,12 @@
                 tag: 'device',
                 selCb: selCb
             });
+
+            $scope.refresh = function () {
+                $log.debug('Refreshing devices page');
+                ts.resetSortIcons();
+                $scope.sortCallback();
+            };
             createDetailsPane();
 
             // details panel handlers
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.css b/web/gui/src/main/webapp/app/view/flow/flow.css
index 7a91837..ecbd217 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.css
+++ b/web/gui/src/main/webapp/app/view/flow/flow.css
@@ -18,6 +18,14 @@
  ONOS GUI -- Flow View -- CSS file
  */
 
+#ov-flow h2 {
+    display: inline-block;
+}
+
+#ov-flow div.ctrl-btns {
+    width: 45px;
+}
+
 .light #ov-flow tr:nth-child(6n + 2),
 .light #ov-flow tr:nth-child(6n + 3),
 .light #ov-flow tr:nth-child(6n + 4) {
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.html b/web/gui/src/main/webapp/app/view/flow/flow.html
index 40b6946..540ee9c 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.html
+++ b/web/gui/src/main/webapp/app/view/flow/flow.html
@@ -2,9 +2,14 @@
 <div id="ov-flow">
     <div class="tabular-header">
         <h2>
-            Flows for Device {{ctrl.devId || "none"}}
+            Flows for Device {{ctrl.devId || "(No device selected)"}}
             ({{ctrl.tableData.length}} total)
         </h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
 
     <table class="summary-list"
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.js b/web/gui/src/main/webapp/app/view/flow/flow.js
index 239ebda..bcd471b 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -22,19 +22,21 @@
     'use strict';
 
     // injected references
-    var $log, $scope, $location, fs, tbs;
+    var $log, $scope, $location, fs, ts, tbs;
 
     angular.module('ovFlow', [])
     .controller('OvFlowCtrl',
-        ['$log', '$scope', '$location', 'FnService', 'TableBuilderService',
+        ['$log', '$scope', '$location',
+            'FnService', 'TableService', 'TableBuilderService',
 
-        function (_$log_, _$scope_, _$location_, _fs_, _tbs_) {
+        function (_$log_, _$scope_, _$location_, _fs_, _ts_, _tbs_) {
             var self = this,
                 params;
             $log = _$log_;
             $scope = _$scope_;
             $location = _$location_;
             fs = _fs_;
+            ts = _ts_;
             tbs = _tbs_;
 
             params = $location.search();
@@ -48,6 +50,12 @@
                 tag: 'flow',
                 query: params
             });
+
+            $scope.refresh = function () {
+                $log.debug('Refreshing flows page');
+                ts.resetSortIcons();
+                $scope.sortCallback();
+            };
             
             $log.log('OvFlowCtrl has been created');
         }]);
diff --git a/web/gui/src/main/webapp/app/view/host/host.css b/web/gui/src/main/webapp/app/view/host/host.css
index 87174f9..6067275 100644
--- a/web/gui/src/main/webapp/app/view/host/host.css
+++ b/web/gui/src/main/webapp/app/view/host/host.css
@@ -18,5 +18,10 @@
  ONOS GUI -- Host View -- CSS file
  */
 
-#ov-host td {
+#ov-host h2 {
+    display: inline-block;
+}
+
+#ov-host div.ctrl-btns {
+    width: 45px;
 }
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/host/host.html b/web/gui/src/main/webapp/app/view/host/host.html
index 5ebc9ae..94fceda 100644
--- a/web/gui/src/main/webapp/app/view/host/host.html
+++ b/web/gui/src/main/webapp/app/view/host/host.html
@@ -2,6 +2,11 @@
 <div id="ov-host">
     <div class="tabular-header">
         <h2>Hosts ({{ctrl.tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
 
     <table class="summary-list"
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index 470f78a..a3e43aa 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -23,14 +23,20 @@
 
     angular.module('ovHost', [])
     .controller('OvHostCtrl',
-        ['$log', '$scope', 'TableBuilderService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService',
 
-        function ($log, $scope, tbs) {
+        function ($log, $scope, ts, tbs) {
             tbs.buildTable({
                 self: this,
                 scope: $scope,
                 tag: 'host'
             });
+
+            $scope.refresh = function () {
+                $log.debug('Refreshing hosts page');
+                ts.resetSortIcons();
+                $scope.sortCallback();
+            };
             
             $log.log('OvHostCtrl has been created');
         }]);
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.css b/web/gui/src/main/webapp/app/view/intent/intent.css
index 4aac0bb..4f9ea8a 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.css
+++ b/web/gui/src/main/webapp/app/view/intent/intent.css
@@ -18,6 +18,14 @@
  ONOS GUI -- Intent View -- CSS file
  */
 
+#ov-intent h2 {
+    display: inline-block;
+}
+
+#ov-intent div.ctrl-btns {
+    width: 45px;
+}
+
 .light #ov-intent tr:nth-child(6n + 2),
 .light #ov-intent tr:nth-child(6n + 3),
 .light #ov-intent tr:nth-child(6n + 4) {
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.html b/web/gui/src/main/webapp/app/view/intent/intent.html
index cc48a7c..09c7f2b 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.html
+++ b/web/gui/src/main/webapp/app/view/intent/intent.html
@@ -18,6 +18,11 @@
 <div id="ov-intent">
     <div class="tabular-header">
         <h2>Intents ({{ctrl.tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
     <table class="summary-list"
            onos-fixed-header
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.js b/web/gui/src/main/webapp/app/view/intent/intent.js
index 16f9fb8..4e23aec 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.js
+++ b/web/gui/src/main/webapp/app/view/intent/intent.js
@@ -23,15 +23,21 @@
 
     angular.module('ovIntent', [])
         .controller('OvIntentCtrl',
-        ['$log', '$scope', 'TableBuilderService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService',
 
-            function ($log, $scope, tbs) {
+            function ($log, $scope, ts, tbs) {
                 tbs.buildTable({
                     self: this,
                     scope: $scope,
                     tag: 'intent'
                 });
 
+                $scope.refresh = function () {
+                    $log.debug('Refreshing intents page');
+                    ts.resetSortIcons();
+                    $scope.sortCallback();
+                };
+
                 $log.log('OvIntentCtrl has been created');
             }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/link/link.css b/web/gui/src/main/webapp/app/view/link/link.css
index 8bfd5bf..4f049cd 100644
--- a/web/gui/src/main/webapp/app/view/link/link.css
+++ b/web/gui/src/main/webapp/app/view/link/link.css
@@ -18,5 +18,10 @@
  ONOS GUI -- Link View -- CSS file
  */
 
-#ov-link td {
+#ov-link h2 {
+    display: inline-block;
+}
+
+#ov-link div.ctrl-btns {
+    width: 45px;
 }
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/link/link.html b/web/gui/src/main/webapp/app/view/link/link.html
index 18f3e1f..151ac3a 100644
--- a/web/gui/src/main/webapp/app/view/link/link.html
+++ b/web/gui/src/main/webapp/app/view/link/link.html
@@ -18,6 +18,11 @@
 <div id="ov-link">
     <div class="tabular-header">
         <h2>Links ({{ctrl.tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh active"
+                 icon icon-size="36" icon-id="crown"
+                 ng-click="refresh()"></div>
+        </div>
     </div>
 
     <table class="summary-list"
diff --git a/web/gui/src/main/webapp/app/view/link/link.js b/web/gui/src/main/webapp/app/view/link/link.js
index b4ba632..bc69430 100644
--- a/web/gui/src/main/webapp/app/view/link/link.js
+++ b/web/gui/src/main/webapp/app/view/link/link.js
@@ -23,14 +23,20 @@
 
     angular.module('ovLink', [])
     .controller('OvLinkCtrl',
-        ['$log', '$scope', 'TableBuilderService',
+        ['$log', '$scope', 'TableService', 'TableBuilderService',
 
-        function ($log, $scope, tbs) {
+        function ($log, $scope, ts, tbs) {
             tbs.buildTable({
                 self: this,
                 scope: $scope,
                 tag: 'link'
             });
+
+            $scope.refresh = function () {
+                $log.debug('Refreshing links page');
+                ts.resetSortIcons();
+                $scope.sortCallback();
+            };
             
             $log.log('OvLinkCtrl has been created');
         }]);
diff --git a/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js b/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
index 184d29d..693f42d 100644
--- a/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
@@ -19,7 +19,7 @@
  */
 describe('factory: fw/widget/table.js', function () {
     var $log, $compile, $rootScope,
-        fs, mast, is,
+        fs, ts, mast, is,
         scope, compiled,
         table, thead, tbody, mockHeader,
         mockH2Height = 20,
@@ -99,11 +99,12 @@
     });
 
     beforeEach(inject(function (_$log_, _$compile_, _$rootScope_,
-                                FnService, MastService, IconService) {
+                                FnService, TableService, MastService, IconService) {
         $log = _$log_;
         $compile = _$compile_;
         $rootScope = _$rootScope_;
         fs = FnService;
+        ts = TableService;
         mast = MastService;
         is = IconService;
     }));
@@ -127,6 +128,16 @@
         mockHeader.remove();
     });
 
+    it('should define TableBuilderService', function () {
+        expect(ts).toBeDefined();
+    });
+
+    it('should define api functions', function () {
+        expect(fs.areFunctions(ts, [
+            'resetSortIcons'
+        ])).toBeTruthy();
+    });
+
     function compileTable() {
         compiled = $compile(table);
         compiled(scope);
@@ -204,26 +215,22 @@
         thElems[1].click();
         currentTh = angular.element(thElems[1]);
         div = currentTh.find('div');
-        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
-                                'width="10" height="10" viewBox="0 0 50 50">' +
-                                '<g class="icon upArrow">' +
-                                '<rect width="50" height="50" rx="5"></rect>' +
-                                '<use width="50" height="50" class="glyph" ' +
-                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
-                                'xlink:href="#triangleUp">' +
-                                '</use>' +
-                                '</g></svg>');
+        expect(div.html()).toBe(
+            '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+            '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
+            'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+            'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
+            '</use></g></svg>'
+        );
         thElems[1].click();
         div = currentTh.find('div');
-        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
-                                'width="10" height="10" viewBox="0 0 50 50">' +
-                                '<g class="icon downArrow">' +
-                                '<rect width="50" height="50" rx="5"></rect>' +
-                                '<use width="50" height="50" class="glyph" ' +
-                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
-                                'xlink:href="#triangleDown">' +
-                                '</use>' +
-                                '</g></svg>');
+        expect(div.html()).toBe(
+            '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+            '50 50"><g class="icon downArrow"><rect width="50" height="50" ' +
+            'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+            'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleDown">' +
+            '</use></g></svg>'
+        );
 
         thElems[2].click();
         div = currentTh.children();
@@ -233,15 +240,13 @@
         // the new element should have the ascending icon
         currentTh = angular.element(thElems[2]);
         div = currentTh.children();
-        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
-                                'width="10" height="10" viewBox="0 0 50 50">' +
-                                '<g class="icon upArrow">' +
-                                '<rect width="50" height="50" rx="5"></rect>' +
-                                '<use width="50" height="50" class="glyph" ' +
-                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
-                                'xlink:href="#triangleUp">' +
-                                '</use>' +
-                                '</g></svg>');
+        expect(div.html()).toBe(
+            '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+            '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
+            'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+            'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
+            '</use></g></svg>'
+        );
     }
 
     it('should affirm that onos-fixed-header is working', function () {
@@ -264,8 +269,7 @@
 
         compileTable();
         verifyGivenTags('onos-sortable-header');
-        // ctrlCallback functionality is tested in device-spec
-        // only checking that it has been called correctly in the directive
+
         scope.sortCallback = jasmine.createSpy('sortCallback');
 
         thElems = thead.find('th');
@@ -273,4 +277,8 @@
         verifyIcons(thElems);
     });
 
+    // Note: testing resetSortIcons isn't feasible because due to the nature of
+    //       directive compilation: they are jQuery elements, not d3 elements,
+    //       so the function doesn't work.
+
 });