Bri Prebilic Cole
Committed by Gerrit Code Review

ONOS-2325 - GUI -- Table View rows now flash yellow when their information updat…

…es. Minor device details panel bug fix.

Change-Id: I78eb0f90af00ce4484255d7e9e0c3c8a10a0eda7
...@@ -172,6 +172,32 @@ ...@@ -172,6 +172,32 @@
172 return true; 172 return true;
173 } 173 }
174 174
175 + // returns true if the two objects have all the same properties
176 + function sameObjProps(obj1, obj2) {
177 + var key;
178 + for (key in obj1) {
179 + if (obj1.hasOwnProperty(key)) {
180 + if (!(obj1[key] === obj2[key])) {
181 + return false;
182 + }
183 + }
184 + }
185 + return true;
186 + }
187 +
188 + // returns true if the array contains the object
189 + // does NOT use strict object reference equality,
190 + // instead checks each property individually for equality
191 + function containsObj(arr, obj) {
192 + var i;
193 + for (i = 0; i < arr.length; i++) {
194 + if (sameObjProps(arr[i], obj)) {
195 + return true;
196 + }
197 + }
198 + return false;
199 + }
200 +
175 // return the given string with the first character capitalized. 201 // return the given string with the first character capitalized.
176 function cap(s) { 202 function cap(s) {
177 return s.toLowerCase().replace(/^[a-z]/, function (m) { 203 return s.toLowerCase().replace(/^[a-z]/, function (m) {
...@@ -227,6 +253,8 @@ ...@@ -227,6 +253,8 @@
227 inArray: inArray, 253 inArray: inArray,
228 removeFromArray: removeFromArray, 254 removeFromArray: removeFromArray,
229 isEmptyObject: isEmptyObject, 255 isEmptyObject: isEmptyObject,
256 + sameObjProps: sameObjProps,
257 + containsObj: containsObj,
230 cap: cap, 258 cap: cap,
231 noPx: noPx, 259 noPx: noPx,
232 noPxStyle: noPxStyle, 260 noPxStyle: noPxStyle,
......
...@@ -63,6 +63,17 @@ div.summary-list tr.no-data td { ...@@ -63,6 +63,17 @@ div.summary-list tr.no-data td {
63 background-color: #304860; 63 background-color: #304860;
64 } 64 }
65 65
66 +/* highlighting */
67 +div.summary-list tr {
68 + transition: background-color 500ms;
69 +}
70 +.light div.summary-list tr.data-change {
71 + background-color: #FDFFDC;
72 +}
73 +.dark div.summary-list tr.data-change {
74 + background-color: #5A5600;
75 +}
76 +
66 div.summary-list td { 77 div.summary-list td {
67 padding: 6px; 78 padding: 6px;
68 text-align: left; 79 text-align: left;
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
26 // constants 26 // constants
27 var tableIconTdSize = 33, 27 var tableIconTdSize = 33,
28 pdg = 22, 28 pdg = 22,
29 + flashTime = 2000,
29 colWidth = 'col-width', 30 colWidth = 'col-width',
30 tableIcon = 'table-icon', 31 tableIcon = 'table-icon',
31 asc = 'asc', 32 asc = 'asc',
...@@ -208,7 +209,46 @@ ...@@ -208,7 +209,46 @@
208 scope.$on('$destroy', function () { 209 scope.$on('$destroy', function () {
209 resetSort(); 210 resetSort();
210 }); 211 });
211 - } 212 + };
213 + }])
214 +
215 + .directive('onosFlashChanges', ['$log', '$parse', '$timeout',
216 + function ($log, $parse, $timeout) {
217 + return function (scope, element, attrs) {
218 + var rowData = $parse(attrs.row)(scope),
219 + id = attrs.rowId,
220 + tr = d3.select(element[0]),
221 + multiRows = d3.selectAll('.multi-row'),
222 + promise;
223 +
224 + scope.$watchCollection('changedData', function (newData) {
225 + angular.forEach(newData, function (item) {
226 + function classMultiRows(b) {
227 + if (!multiRows.empty()) {
228 + multiRows.each(function () {
229 + d3.select(this).classed('data-change', b);
230 + });
231 + }
232 + }
233 +
234 + if (rowData[id] === item[id]) {
235 + tr.classed('data-change', true);
236 + classMultiRows(true);
237 +
238 + promise = $timeout(function () {
239 + tr.classed('data-change', false);
240 + classMultiRows(false);
241 + }, flashTime);
242 + }
243 +
244 + });
245 + });
246 + scope.$on('$destroy', function () {
247 + if (promise) {
248 + $timeout.cancel(promise);
249 + }
250 + });
251 + };
212 }]); 252 }]);
213 253
214 }()); 254 }());
......
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
30 // { 30 // {
31 // scope: $scope, <- controller scope 31 // scope: $scope, <- controller scope
32 // tag: 'device', <- table identifier 32 // tag: 'device', <- table identifier
33 - // selCb: selCb <- row selection callback (optional) 33 + // selCb: selCb, <- row selection callback (optional)
34 + // respCb: respCb, <- websocket response callback (optional)
34 // query: params <- query parameters in URL (optional) 35 // query: params <- query parameters in URL (optional)
35 // } 36 // }
36 // Note: selCb() is passed the row data model of the selected row, 37 // Note: selCb() is passed the row data model of the selected row,
...@@ -45,31 +46,54 @@ ...@@ -45,31 +46,54 @@
45 resp = o.tag + 'DataResponse', 46 resp = o.tag + 'DataResponse',
46 onSel = fs.isF(o.selCb), 47 onSel = fs.isF(o.selCb),
47 onResp = fs.isF(o.respCb), 48 onResp = fs.isF(o.respCb),
49 + oldTableData = [],
48 promise; 50 promise;
49 51
50 o.scope.tableData = []; 52 o.scope.tableData = [];
53 + o.scope.changedData = [];
51 o.scope.sortParams = {}; 54 o.scope.sortParams = {};
52 o.scope.autoRefresh = true; 55 o.scope.autoRefresh = true;
53 o.scope.autoRefreshTip = 'Toggle auto refresh'; 56 o.scope.autoRefreshTip = 'Toggle auto refresh';
54 57
58 + // === websocket functions --------------------
59 + // response
55 function respCb(data) { 60 function respCb(data) {
56 o.scope.tableData = data[root]; 61 o.scope.tableData = data[root];
57 onResp && onResp(); 62 onResp && onResp();
58 o.scope.$apply(); 63 o.scope.$apply();
64 +
65 + // checks if data changed for row flashing
66 + if (!angular.equals(o.scope.tableData, oldTableData)) {
67 + o.scope.changedData = [];
68 + // only flash the row if the data already exists
69 + if (oldTableData.length) {
70 + angular.forEach(o.scope.tableData, function (item) {
71 + if (!fs.containsObj(oldTableData, item)) {
72 + o.scope.changedData.push(item);
73 + }
74 + });
75 + }
76 + angular.copy(o.scope.tableData, oldTableData);
77 + }
59 } 78 }
79 + handlers[resp] = respCb;
80 + wss.bindHandlers(handlers);
60 81
82 + // request
61 function sortCb(params) { 83 function sortCb(params) {
62 var p = angular.extend({}, params, o.query); 84 var p = angular.extend({}, params, o.query);
63 wss.sendEvent(req, p); 85 wss.sendEvent(req, p);
64 } 86 }
65 o.scope.sortCallback = sortCb; 87 o.scope.sortCallback = sortCb;
66 88
89 + // === selecting a row functions ----------------
67 function selCb($event, selRow) { 90 function selCb($event, selRow) {
68 o.scope.selId = (o.scope.selId === selRow.id) ? null : selRow.id; 91 o.scope.selId = (o.scope.selId === selRow.id) ? null : selRow.id;
69 onSel && onSel($event, selRow); 92 onSel && onSel($event, selRow);
70 } 93 }
71 o.scope.selectCallback = selCb; 94 o.scope.selectCallback = selCb;
72 95
96 + // === autoRefresh functions ------------------
73 function startRefresh() { 97 function startRefresh() {
74 promise = $interval(function () { 98 promise = $interval(function () {
75 if (fs.debugOn('widget')) { 99 if (fs.debugOn('widget')) {
...@@ -92,10 +116,7 @@ ...@@ -92,10 +116,7 @@
92 } 116 }
93 o.scope.toggleRefresh = toggleRefresh; 117 o.scope.toggleRefresh = toggleRefresh;
94 118
95 - handlers[resp] = respCb; 119 + // === Cleanup on destroyed scope -----------------
96 - wss.bindHandlers(handlers);
97 -
98 - // Cleanup on destroyed scope
99 o.scope.$on('$destroy', function () { 120 o.scope.$on('$destroy', function () {
100 wss.unbindHandlers(handlers); 121 wss.unbindHandlers(handlers);
101 stopRefresh(); 122 stopRefresh();
......
...@@ -60,7 +60,8 @@ ...@@ -60,7 +60,8 @@
60 60
61 <tr ng-repeat="app in tableData track by $index" 61 <tr ng-repeat="app in tableData track by $index"
62 ng-click="selectCallback($event, app)" 62 ng-click="selectCallback($event, app)"
63 - ng-class="{selected: app.id === selId}"> 63 + ng-class="{selected: app.id === selId}"
64 + onos-flash-changes row="{{app}}" row-id="id">
64 <td class="table-icon"> 65 <td class="table-icon">
65 <div icon icon-id="{{app._iconid_state}}"></div> 66 <div icon icon-id="{{app._iconid_state}}"></div>
66 </td> 67 </td>
......
...@@ -48,7 +48,8 @@ ...@@ -48,7 +48,8 @@
48 </td> 48 </td>
49 </tr> 49 </tr>
50 50
51 - <tr ng-repeat="node in tableData track by $index"> 51 + <tr ng-repeat="node in tableData track by $index"
52 + onos-flash-changes row="{{node}}" row-id="id">
52 <td class="table-icon"> 53 <td class="table-icon">
53 <div icon icon-id="{{node._iconid_state}}"></div> 54 <div icon icon-id="{{node._iconid_state}}"></div>
54 </td> 55 </td>
......
...@@ -54,7 +54,8 @@ ...@@ -54,7 +54,8 @@
54 54
55 <tr ng-repeat="dev in tableData track by $index" 55 <tr ng-repeat="dev in tableData track by $index"
56 ng-click="selectCallback($event, dev)" 56 ng-click="selectCallback($event, dev)"
57 - ng-class="{selected: dev.id === selId}"> 57 + ng-class="{selected: dev.id === selId}"
58 + onos-flash-changes row="{{dev}}" row-id="id">
58 <td class="table-icon"> 59 <td class="table-icon">
59 <div icon icon-id="{{dev._iconid_available}}"></div> 60 <div icon icon-id="{{dev._iconid_available}}"></div>
60 </td> 61 </td>
......
...@@ -250,6 +250,7 @@ ...@@ -250,6 +250,7 @@
250 .directive('deviceDetailsPanel', ['$rootScope', '$window', 250 .directive('deviceDetailsPanel', ['$rootScope', '$window',
251 function ($rootScope, $window) { 251 function ($rootScope, $window) {
252 return function (scope) { 252 return function (scope) {
253 + var unbindWatch;
253 254
254 function heightCalc() { 255 function heightCalc() {
255 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height') 256 pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
...@@ -268,7 +269,7 @@ ...@@ -268,7 +269,7 @@
268 } 269 }
269 }); 270 });
270 271
271 - $rootScope.$watchCollection( 272 + unbindWatch = $rootScope.$watchCollection(
272 function () { 273 function () {
273 return { 274 return {
274 h: $window.innerHeight, 275 h: $window.innerHeight,
...@@ -283,6 +284,7 @@ ...@@ -283,6 +284,7 @@
283 ); 284 );
284 285
285 scope.$on('$destroy', function () { 286 scope.$on('$destroy', function () {
287 + unbindWatch();
286 ps.destroyPanel(pName); 288 ps.destroyPanel(pName);
287 }); 289 });
288 }; 290 };
......
...@@ -61,6 +61,24 @@ ...@@ -61,6 +61,24 @@
61 background-color: #333; 61 background-color: #333;
62 } 62 }
63 63
64 +/* highlighted color */
65 +.light #ov-flow tr:nth-child(6n + 1).data-change,
66 +.light #ov-flow tr:nth-child(6n + 2).data-change,
67 +.light #ov-flow tr:nth-child(6n + 3).data-change,
68 +.light #ov-flow tr:nth-child(6n + 4).data-change,
69 +.light #ov-flow tr:nth-child(6n + 5).data-change,
70 +.light #ov-flow tr:nth-child(6n).data-change {
71 + background-color: #FDFFDC;
72 +}
73 +.dark #ov-flow tr:nth-child(6n + 1).data-change,
74 +.dark #ov-flow tr:nth-child(6n + 2).data-change,
75 +.dark #ov-flow tr:nth-child(6n + 3).data-change,
76 +.dark #ov-flow tr:nth-child(6n + 4).data-change,
77 +.dark #ov-flow tr:nth-child(6n + 5).data-change,
78 +.dark #ov-flow tr:nth-child(6n).data-change {
79 + background-color: #5A5600;
80 +}
81 +
64 #ov-flow td.selector, 82 #ov-flow td.selector,
65 #ov-flow td.treatment { 83 #ov-flow td.treatment {
66 padding-left: 36px; 84 padding-left: 36px;
......
...@@ -55,7 +55,8 @@ ...@@ -55,7 +55,8 @@
55 </td> 55 </td>
56 </tr> 56 </tr>
57 57
58 - <tr ng-repeat-start="flow in tableData track by $index"> 58 + <tr ng-repeat-start="flow in tableData track by $index"
59 + onos-flash-changes row="{{flow}}" row-id="id">
59 <td>{{flow.id}}</td> 60 <td>{{flow.id}}</td>
60 <td>{{flow.appId}}</td> 61 <td>{{flow.appId}}</td>
61 <td>{{flow.groupId}}</td> 62 <td>{{flow.groupId}}</td>
...@@ -67,10 +68,10 @@ ...@@ -67,10 +68,10 @@
67 <td>{{flow.packets}}</td> 68 <td>{{flow.packets}}</td>
68 <td>{{flow.bytes}}</td> 69 <td>{{flow.bytes}}</td>
69 </tr> 70 </tr>
70 - <tr> 71 + <tr class="multi-row">
71 <td class="selector" colspan="10">{{flow.selector}}</td> 72 <td class="selector" colspan="10">{{flow.selector}}</td>
72 </tr> 73 </tr>
73 - <tr ng-repeat-end> 74 + <tr class="multi-row" ng-repeat-end>
74 <td class="treatment" colspan="10">{{flow.treatment}}</td> 75 <td class="treatment" colspan="10">{{flow.treatment}}</td>
75 </tr> 76 </tr>
76 </table> 77 </table>
......
...@@ -57,6 +57,20 @@ ...@@ -57,6 +57,20 @@
57 background-color: #333; 57 background-color: #333;
58 } 58 }
59 59
60 +/* highlighted color */
61 +.light #ov-group tr:nth-child(4n + 1).data-change,
62 +.light #ov-group tr:nth-child(4n + 2).data-change,
63 +.light #ov-group tr:nth-child(4n + 3).data-change,
64 +.light #ov-group tr:nth-child(4n).data-change {
65 + background-color: #FDFFDC;
66 +}
67 +.dark #ov-group tr:nth-child(4n + 1).data-change,
68 +.dark #ov-group tr:nth-child(4n + 2).data-change,
69 +.dark #ov-group tr:nth-child(4n + 3).data-change,
70 +.dark #ov-group tr:nth-child(4n).data-change {
71 + background-color: #5A5600;
72 +}
73 +
60 #ov-group td.buckets { 74 #ov-group td.buckets {
61 padding-left: 36px; 75 padding-left: 36px;
62 opacity: 0.65; 76 opacity: 0.65;
......
...@@ -67,7 +67,8 @@ ...@@ -67,7 +67,8 @@
67 </td> 67 </td>
68 </tr> 68 </tr>
69 69
70 - <tr ng-repeat-start="group in tableData track by $index"> 70 + <tr ng-repeat-start="group in tableData track by $index"
71 + onos-flash-changes row="{{group}}" row-id="id">
71 <td>{{group.id}}</td> 72 <td>{{group.id}}</td>
72 <td>{{group.app_id}}</td> 73 <td>{{group.app_id}}</td>
73 <td>{{group.state}}</td> 74 <td>{{group.state}}</td>
...@@ -75,7 +76,7 @@ ...@@ -75,7 +76,7 @@
75 <td>{{group.packets}}</td> 76 <td>{{group.packets}}</td>
76 <td>{{group.bytes}}</td> 77 <td>{{group.bytes}}</td>
77 </tr> 78 </tr>
78 - <tr ng-repeat-end> 79 + <tr class="multi-row" ng-repeat-end>
79 <td class="buckets" colspan="6" 80 <td class="buckets" colspan="6"
80 ng-bind-html="group.buckets"></td> 81 ng-bind-html="group.buckets"></td>
81 </tr> 82 </tr>
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
33 </td> 33 </td>
34 </tr> 34 </tr>
35 35
36 - <tr ng-repeat="host in tableData track by $index"> 36 + <tr ng-repeat="host in tableData track by $index"
37 + onos-flash-changes row="{{host}}" row-id="id">
37 <td class="table-icon"> 38 <td class="table-icon">
38 <div icon icon-id="{{host._iconid_type}}"></div> 39 <div icon icon-id="{{host._iconid_type}}"></div>
39 </td> 40 </td>
......
...@@ -47,6 +47,23 @@ ...@@ -47,6 +47,23 @@
47 background-color: #333; 47 background-color: #333;
48 } 48 }
49 49
50 +.light #ov-intent tr:nth-child(6n + 1).data-change,
51 +.light #ov-intent tr:nth-child(6n + 2).data-change,
52 +.light #ov-intent tr:nth-child(6n + 3).data-change,
53 +.light #ov-intent tr:nth-child(6n + 4).data-change,
54 +.light #ov-intent tr:nth-child(6n + 5).data-change,
55 +.light #ov-intent tr:nth-child(6n).data-change {
56 + background-color: #FDFFDC;
57 +}
58 +.dark #ov-intent tr:nth-child(6n + 1).data-change,
59 +.dark #ov-intent tr:nth-child(6n + 2).data-change,
60 +.dark #ov-intent tr:nth-child(6n + 3).data-change,
61 +.dark #ov-intent tr:nth-child(6n + 4).data-change,
62 +.dark #ov-intent tr:nth-child(6n + 5).data-change,
63 +.dark #ov-intent tr:nth-child(6n).data-change {
64 + background-color: #5A5600;
65 +}
66 +
50 #ov-intent td.resources, 67 #ov-intent td.resources,
51 #ov-intent td.details { 68 #ov-intent td.details {
52 padding-left: 36px; 69 padding-left: 36px;
......
...@@ -48,17 +48,18 @@ ...@@ -48,17 +48,18 @@
48 </td> 48 </td>
49 </tr> 49 </tr>
50 50
51 - <tr ng-repeat-start="intent in tableData track by $index"> 51 + <tr ng-repeat-start="intent in tableData track by $index"
52 + onos-flash-changes row="{{intent}}" row-id="key">
52 <td>{{intent.appId}}</td> 53 <td>{{intent.appId}}</td>
53 <td>{{intent.key}}</td> 54 <td>{{intent.key}}</td>
54 <td>{{intent.type}}</td> 55 <td>{{intent.type}}</td>
55 <td>{{intent.priority}}</td> 56 <td>{{intent.priority}}</td>
56 <td>{{intent.state}}</td> 57 <td>{{intent.state}}</td>
57 </tr> 58 </tr>
58 - <tr> 59 + <tr class="multi-row">
59 <td class="resources" colspan="5">{{intent.resources}}</td> 60 <td class="resources" colspan="5">{{intent.resources}}</td>
60 </tr> 61 </tr>
61 - <tr ng-repeat-end> 62 + <tr class="multi-row" ng-repeat-end>
62 <td class="details" colspan="5">{{intent.details}}</td> 63 <td class="details" colspan="5">{{intent.details}}</td>
63 </tr> 64 </tr>
64 </table> 65 </table>
......
...@@ -49,7 +49,8 @@ ...@@ -49,7 +49,8 @@
49 </td> 49 </td>
50 </tr> 50 </tr>
51 51
52 - <tr ng-repeat="link in tableData track by $index"> 52 + <tr ng-repeat="link in tableData track by $index"
53 + onos-flash-changes row="{{link}}" row-id="one">
53 <td class="table-icon"> 54 <td class="table-icon">
54 <div icon icon-id="{{link._iconid_state}}"></div> 55 <div icon icon-id="{{link._iconid_state}}"></div>
55 </td> 56 </td>
......
...@@ -69,7 +69,8 @@ ...@@ -69,7 +69,8 @@
69 </td> 69 </td>
70 </tr> 70 </tr>
71 71
72 - <tr ng-repeat="port in tableData track by $index"> 72 + <tr ng-repeat="port in tableData track by $index"
73 + onos-flash-changes row="{{port}}" row-id="id">
73 <td>{{port.id}}</td> 74 <td>{{port.id}}</td>
74 <td>{{port.pkt_rx}}</td> 75 <td>{{port.pkt_rx}}</td>
75 <td>{{port.pkt_tx}}</td> 76 <td>{{port.pkt_tx}}</td>
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
33 </td> 33 </td>
34 </tr> 34 </tr>
35 35
36 - <tr ng-repeat="prop in tableData track by $index"> 36 + <tr ng-repeat="prop in tableData track by $index"
37 + onos-flash-changes row="{{prop}}" row-id="id">
37 <td>{{prop.component}}</td> 38 <td>{{prop.component}}</td>
38 <td>{{prop.id}}</td> 39 <td>{{prop.id}}</td>
39 <td>{{prop.type}}</td> 40 <td>{{prop.type}}</td>
......