2 * Copyright (c) 2017, The OpenThread Authors.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
31 .module('StarterApp', ['ngMaterial', 'ngMessages'])
32 .controller('AppCtrl', AppCtrl)
33 .service('sharedProperties', function() {
38 getIndex: function() {
41 setIndex: function(value) {
44 getNetworkInfo: function() {
47 setNetworkInfo: function(value) {
53 function AppCtrl($scope, $http, $mdDialog, $interval, sharedProperties) {
61 icon: 'add_circle_outline',
81 icon: 'add_circle_outline',
86 icon: 'add_circle_outline',
93 networkName: 'OpenThreadDemo',
94 extPanId: '1111111122222222',
97 masterKey: '00112233445566778899aabbccddeeff',
108 $scope.headerTitle = 'Home';
111 $scope.isLoading = false;
113 $scope.showScanAlert = function(ev) {
116 .parent(angular.element(document.querySelector('#popupContainer')))
117 .clickOutsideToClose(true)
118 .title('Information')
119 .textContent('There is no available Thread network currently, please \
120 wait a moment and retry it.')
121 .ariaLabel('Alert Dialog Demo')
125 $scope.showPanels = function(index) {
126 $scope.headerTitle = $scope.menu[index].title;
127 for (var i = 0; i < 7; i++) {
128 $scope.menu[i].show = false;
130 $scope.menu[index].show = true;
132 $scope.isLoading = true;
133 $http.get('/available_network').then(function(response) {
134 $scope.isLoading = false;
135 if (response.data.error == 0) {
136 $scope.networksInfo = response.data.result;
138 $scope.showScanAlert(event);
143 $http.get('/get_properties').then(function(response) {
144 console.log(response);
145 if (response.data.error == 0) {
146 var statusJson = response.data.result;
148 for (var i = 0; i < Object.keys(statusJson).length; i++) {
150 name: Object.keys(statusJson)[i],
151 value: statusJson[Object.keys(statusJson)[i]],
152 icon: 'res/img/icon-info.png',
160 $scope.showTopology();
164 $scope.showJoinDialog = function(ev, index, item) {
165 sharedProperties.setIndex(index);
166 sharedProperties.setNetworkInfo(item);
167 $scope.index = index;
169 controller: DialogController,
170 templateUrl: 'join.dialog.html',
171 parent: angular.element(document.body),
173 clickOutsideToClose: true,
174 fullscreen: $scope.customFullscreen,
178 function DialogController($scope, $mdDialog, $http, $interval, sharedProperties) {
179 var index = sharedProperties.getIndex();
180 $scope.isDisplay = false;
182 masterKey: '00112233445566778899aabbccddeeff',
187 $scope.showAlert = function(ev, result) {
190 .parent(angular.element(document.querySelector('#popupContainer')))
191 .clickOutsideToClose(true)
192 .title('Information')
193 .textContent('Join operation is ' + result)
194 .ariaLabel('Alert Dialog Demo')
200 $scope.join = function(valid) {
206 if ($scope.thread.defaultRoute == null) {
207 $scope.thread.defaultRoute = false;
209 $scope.isDisplay = true;
211 masterKey: $scope.thread.masterKey,
212 prefix: $scope.thread.prefix,
213 defaultRoute: $scope.thread.defaultRoute,
216 var httpRequest = $http({
218 url: '/join_network',
222 httpRequest.then(function successCallback(response) {
223 $scope.res = response.data.result;
224 if (response.data.result == 'successful') {
227 $scope.isDisplay = false;
228 $scope.showAlert(event, response.data.result);
232 $scope.cancel = function() {
238 $scope.showConfirm = function(ev, valid) {
244 var confirm = $mdDialog.confirm()
245 .title('Are you sure you want to Form the Thread Network?')
251 $mdDialog.show(confirm).then(function() {
252 if ($scope.thread.defaultRoute == null) {
253 $scope.thread.defaultRoute = false;
256 masterKey: $scope.thread.masterKey,
257 prefix: $scope.thread.prefix,
258 defaultRoute: $scope.thread.defaultRoute,
259 extPanId: $scope.thread.extPanId,
260 panId: $scope.thread.panId,
261 passphrase: $scope.thread.passphrase,
262 channel: $scope.thread.channel,
263 networkName: $scope.thread.networkName,
265 $scope.isForming = true;
266 var httpRequest = $http({
268 url: '/form_network',
272 httpRequest.then(function successCallback(response) {
273 $scope.res = response.data.result;
274 if (response.data.result == 'successful') {
277 $scope.isForming = false;
278 $scope.showAlert(event, 'FORM', response.data.result);
285 $scope.showAlert = function(ev, operation, result) {
288 .parent(angular.element(document.querySelector('#popupContainer')))
289 .clickOutsideToClose(true)
290 .title('Information')
291 .textContent(operation + ' operation is ' + result)
292 .ariaLabel('Alert Dialog Demo')
298 $scope.showAddConfirm = function(ev) {
299 var confirm = $mdDialog.confirm()
300 .title('Are you sure you want to Add this On-Mesh Prefix?')
306 $mdDialog.show(confirm).then(function() {
307 if ($scope.setting.defaultRoute == null) {
308 $scope.setting.defaultRoute = false;
311 prefix: $scope.setting.prefix,
312 defaultRoute: $scope.setting.defaultRoute,
314 var httpRequest = $http({
320 httpRequest.then(function successCallback(response) {
321 $scope.showAlert(event, 'Add', response.data.result);
328 $scope.showDeleteConfirm = function(ev) {
329 var confirm = $mdDialog.confirm()
330 .title('Are you sure you want to Delete this On-Mesh Prefix?')
336 $mdDialog.show(confirm).then(function() {
338 prefix: $scope.setting.prefix,
340 var httpRequest = $http({
342 url: '/delete_prefix',
346 httpRequest.then(function successCallback(response) {
347 $scope.showAlert(event, 'Delete', response.data.result);
354 $scope.startCommission = function(ev) {
356 pskd: $scope.commission.pskd,
357 passphrase: $scope.commission.passphrase,
359 var httpRequest = $http({
365 ev.target.disabled = true;
367 httpRequest.then(function successCallback(response) {
368 if (response.data.error == 0) {
369 $scope.showAlert(event, 'Commission', 'success');
371 $scope.showAlert(event, 'Commission', 'failed');
373 ev.target.disabled = false;
377 $scope.restServerPort = '8081';
378 $scope.ipAddr = window.location.hostname + ':' + $scope.restServerPort;
380 // Basic information line
382 'NetworkName' : 'Unknown',
383 'LeaderData' :{'LeaderRouterId' : 'Unknown'}
385 // Num of router calculated by diagnostic
386 $scope.NumOfRouter = 'Unknown';
388 // Diagnostic information for detailed display
389 $scope.nodeDetailInfo = 'Unknown';
390 // For response of Diagnostic
391 $scope.networksDiagInfo = '';
392 $scope.graphisReady = false;
393 $scope.detailList = {
394 'ExtAddress': { 'title': false, 'content': true },
395 'Rloc16': { 'title': false, 'content': true },
396 'Mode': { 'title': false, 'content': false },
397 'Connectivity': { 'title': false, 'content': false },
398 'Route': { 'title': false, 'content': false },
399 'LeaderData': { 'title': false, 'content': false },
400 'NetworkData': { 'title': false, 'content': true },
401 'IP6Address List': { 'title': false, 'content': true },
402 'MACCounters': { 'title': false, 'content': false },
403 'ChildTable': { 'title': false, 'content': false },
404 'ChannelPages': { 'title': false, 'content': false }
411 $scope.dataInit = function() {
413 $http.get('http://' + $scope.ipAddr + '/node').then(function(response) {
415 $scope.basicInfo = response.data;
416 console.log(response.data);
417 $scope.basicInfo.Rloc16 = $scope.intToHexString($scope.basicInfo.Rloc16,4);
418 $scope.basicInfo.LeaderData.LeaderRouterId = '0x' + $scope.intToHexString($scope.basicInfo.LeaderData.LeaderRouterId,2);
421 $scope.isObject = function(obj) {
422 return obj.constructor === Object;
424 $scope.isArray = function(arr) {
425 return !!arr && arr.constructor === Array;
428 $scope.clickList = function(key) {
429 $scope.detailList[key]['content'] = !$scope.detailList[key]['content']
432 $scope.intToHexString = function(num, len){
434 value = num.toString(16);
436 while( value.length < len ){
441 $scope.showTopology = function() {
443 var count, src, dist, rloc, child, rlocOfParent, rlocOfChild, diagOfNode, linkNode, childInfo;
445 $scope.graphisReady = false;
450 $http.get('http://' + $scope.ipAddr + '/diagnostics').then(function(response) {
453 $scope.networksDiagInfo = response.data;
454 for (diagOfNode of $scope.networksDiagInfo){
456 diagOfNode['RouteId'] = '0x' + $scope.intToHexString(diagOfNode['Rloc16'] >> 10,2);
458 diagOfNode['Rloc16'] = '0x' + $scope.intToHexString(diagOfNode['Rloc16'],4);
460 diagOfNode['LeaderData']['LeaderRouterId'] = '0x' + $scope.intToHexString(diagOfNode['LeaderData']['LeaderRouterId'],2);
461 for (linkNode of diagOfNode['Route']['RouteData']){
462 linkNode['RouteId'] = '0x' + $scope.intToHexString(linkNode['RouteId'],2);
468 for (diagOfNode of $scope.networksDiagInfo) {
469 if ('ChildTable' in diagOfNode) {
471 rloc = parseInt(diagOfNode['Rloc16'],16).toString(16);
472 nodeMap[rloc] = count;
474 if ( diagOfNode['RouteId'] == diagOfNode['LeaderData']['LeaderRouterId']) {
475 diagOfNode['Role'] = 'Leader';
477 diagOfNode['Role'] = 'Router';
480 $scope.graphInfo.nodes.push(diagOfNode);
482 if (diagOfNode['Rloc16'] === $scope.basicInfo.rloc16) {
483 $scope.nodeDetailInfo = diagOfNode
488 // Num of Router is based on the diagnostic information
489 $scope.NumOfRouter = count;
491 // Index for a second loop
494 for (diagOfNode of $scope.networksDiagInfo) {
495 if ('ChildTable' in diagOfNode) {
496 // Link bewtwen routers
497 for (linkNode of diagOfNode['Route']['RouteData']) {
498 rloc = ( parseInt(linkNode['RouteId'],16) << 10).toString(16);
499 if (rloc in nodeMap) {
500 dist = nodeMap[rloc];
502 $scope.graphInfo.links.push({
508 'inQuality': linkNode['LinkQualityIn'],
509 'outQuality': linkNode['LinkQualityOut']
516 // Link between router and child
517 for (childInfo of diagOfNode['ChildTable']) {
519 rlocOfParent = parseInt(diagOfNode['Rloc16'],16).toString(16);
520 rlocOfChild = (parseInt(diagOfNode['Rloc16'],16) + childInfo['ChildId']).toString(16);
522 src = nodeMap[rlocOfParent];
524 child['Rloc16'] = '0x' + rlocOfChild;
525 child['RouteId'] = diagOfNode['RouteId'];
526 nodeMap[rlocOfChild] = count;
527 child['Role'] = 'Child';
528 $scope.graphInfo.nodes.push(child);
529 $scope.graphInfo.links.push({
535 'Timeout': childInfo['Timeout'],
536 'Mode': childInfo['Mode']
552 $scope.updateDetailLabel = function() {
553 for (var detailInfoKey in $scope.detailList) {
554 $scope.detailList[detailInfoKey]['title'] = false;
556 for (var diagInfoKey in $scope.nodeDetailInfo) {
557 if (diagInfoKey in $scope.detailList) {
558 $scope.detailList[diagInfoKey]['title'] = true;
565 $scope.drawGraph = function() {
566 var json, svg, tooltip, force;
569 document.getElementById('topograph').innerHTML = '';
570 scale = $scope.graphInfo.nodes.length;
571 len = 125 * Math.sqrt(scale);
574 svg = d3.select('.d3graph').append('svg')
575 .attr('preserveAspectRatio', 'xMidYMid meet')
576 .attr('viewBox', '0, 0, ' + len.toString(10) + ', ' + (len / (3 / 2)).toString(10));
581 .attr('cy',10).attr('r', 3)
582 .style('fill', "#7e77f8")
583 .style('stroke', '#484e46')
584 .style('stroke-width', '0.4px');
590 .style('fill', '#03e2dd')
591 .style('stroke', '#484e46')
592 .style('stroke-width', '0.4px');
598 .style('fill', '#aad4b0')
599 .style('stroke', '#484e46')
600 .style('stroke-width', '0.4px')
601 .style('stroke-dasharray','2 1');
605 .attr('cy',10).attr('r', 3)
606 .style('fill', '#ffffff')
607 .style('stroke', '#f39191')
608 .style('stroke-width', '0.4px');
614 .style('font-size', '4px')
615 .attr('alignment-baseline','middle');
621 .style('font-size', '4px')
622 .attr('alignment-baseline','middle');
628 .style('font-size', '4px')
629 .attr('alignment-baseline','middle');
635 .style('font-size', '4px')
636 .attr('alignment-baseline','middle');
638 // Tooltip style for each node
639 tooltip = d3.select('body')
641 .attr('class', 'tooltip')
642 .style('position', 'absolute')
643 .style('z-index', '10')
644 .style('visibility', 'hidden')
645 .text('a simple tooltip');
647 force = d3.layout.force()
649 .size([len, len / (3 / 2)]);
652 json = $scope.graphInfo;
660 var link = svg.selectAll('.link')
662 .enter().append('line')
663 .attr('class', 'link')
664 .style('stroke', '#908484')
665 // Dash line for link between child and parent
666 .style('stroke-dasharray', function(item) {
667 if ('Timeout' in item.linkInfo) return '4 4';
670 // Line width representing link quality
671 .style('stroke-width', function(item) {
672 if ('inQuality' in item.linkInfo)
673 return Math.sqrt(item.linkInfo.inQuality/2);
674 else return Math.sqrt(0.5)
676 // Effect of mouseover on a line
677 .on('mouseover', function(item) {
678 return tooltip.style('visibility', 'visible')
679 .text(item.linkInfo);
681 .on('mousemove', function() {
682 return tooltip.style('top', (d3.event.pageY - 10) + 'px')
683 .style('left', (d3.event.pageX + 10) + 'px');
685 .on('mouseout', function() {
686 return tooltip.style('visibility', 'hidden');
690 var node = svg.selectAll('.node')
693 .attr('class', function(item) {
697 // Tooltip effect of mouseover on a node
698 .on('mouseover', function(item) {
699 return tooltip.style('visibility', 'visible')
702 .on('mousemove', function() {
703 return tooltip.style('top', (d3.event.pageY - 10) + 'px')
704 .style('left', (d3.event.pageX + 10) + 'px');
706 .on('mouseout', function() {
707 return tooltip.style('visibility', 'hidden');
710 d3.selectAll('.Child')
713 .attr('fill', '#aad4b0')
714 .style('stroke', '#484e46')
715 .style('stroke-dasharray','2 1')
716 .style('stroke-width', '0.5px')
717 .attr('class', function(item) {
720 .on('mouseover', function(item) {
721 return tooltip.style('visibility', 'visible')
724 .on('mousemove', function() {
725 return tooltip.style('top', (d3.event.pageY - 10) + 'px')
726 .style('left', (d3.event.pageX + 10) + 'px');
728 .on('mouseout', function() {
729 return tooltip.style('visibility', 'hidden');
733 d3.selectAll('.Leader')
736 .attr('fill', '#7e77f8')
737 .style('stroke', '#484e46')
738 .style('stroke-width', '1px')
739 .attr('class', function(item) {
742 // Effect that node will become bigger when mouseover
743 .on('mouseover', function(item) {
747 return tooltip.style('visibility', 'visible')
750 .on('mousemove', function() {
751 return tooltip.style('top', (d3.event.pageY - 10) + 'px')
752 .style('left', (d3.event.pageX + 10) + 'px');
754 .on('mouseout', function() {
755 d3.select(this).transition().attr('r','8');
756 return tooltip.style('visibility', 'hidden');
758 // Effect that node will have a yellow edge when clicked
759 .on('click', function(item) {
760 d3.selectAll('.Stroke')
761 .style('stroke', '#484e46')
762 .style('stroke-width', '1px');
764 .style('stroke', '#f39191')
765 .style('stroke-width', '1px');
766 $scope.$apply(function() {
767 $scope.nodeDetailInfo = item;
768 $scope.updateDetailLabel();
771 d3.selectAll('.Router')
774 .style('stroke', '#484e46')
775 .style('stroke-width', '1px')
776 .attr('fill', '#03e2dd')
777 .attr('class','Stroke')
778 .on('mouseover', function(item) {
782 return tooltip.style('visibility', 'visible')
785 .on('mousemove', function() {
786 return tooltip.style('top', (d3.event.pageY - 10) + 'px')
787 .style('left', (d3.event.pageX + 10) + 'px');
789 .on('mouseout', function() {
793 return tooltip.style('visibility', 'hidden');
795 // The same effect as Leader
796 .on('click', function(item) {
797 d3.selectAll('.Stroke')
798 .style('stroke', '#484e46')
799 .style('stroke-width', '1px');
801 .style('stroke', '#f39191')
802 .style('stroke-width', '1px');
803 $scope.$apply(function() {
804 $scope.nodeDetailInfo = item;
805 $scope.updateDetailLabel();
809 force.on('tick', function() {
810 link.attr('x1', function(item) { return item.source.x; })
811 .attr('y1', function(item) { return item.source.y; })
812 .attr('x2', function(item) { return item.target.x; })
813 .attr('y2', function(item) { return item.target.y; });
814 node.attr('transform', function(item) {
815 return 'translate(' + item.x + ',' + item.y + ')';
819 $scope.updateDetailLabel();
820 $scope.graphisReady = true;