1<%- 2 local ubus = require "ubus" 3 local sys = require "luci.sys" 4 local utl = require "luci.util" 5 6 function connect_ubus(methods) 7 local result 8 local conn = ubus.connect() 9 10 if not conn then 11 error("Failed to connect to ubusd") 12 end 13 14 result = conn:call("otbr", methods, {}) 15 16 return result 17 end 18 19 function threadget(action) 20 local result = connect_ubus(action) 21 22 return result 23 end 24-%> 25 26<%+header%> 27 28<h2><%:Thread View: %><%=threadget("networkname").NetworkName%><%: (wpan0)%></h2> 29<div>This is the list and topograph of your thread network.</div> 30<br /> 31 32<ul class="cbi-tabmenu"> 33 <li class="cbi-tab" id="listtab" style="width:15%;text-align:center;"><a href="javascript:showlist();"><%:List%></a></li> 34 <li class="cbi-tab-disabled" id="graphtab" style="width:15%;text-align:center;"><a href="javascript:showgraph();"><%:Topology Graph%></a></li> 35</ul> 36 37<!-- list div --> 38<div style="width:100%;" id="listdiv"> 39 <!-- leader list --> 40 <h3><%: Leader Situation of Network%></h3><br /> 41 <div class="cbi-map" style="width:90%;margin-left:5%;"> 42 <div class="cbi-section"> 43 <div class="table"> 44 <div class="tr table-titles" style="background-color:#eee;"> 45 <div class="th col-3 center"><%:Leader Router Id%></div> 46 <div class="th col-3 center"><%:Partition Id%></div> 47 <div class="th col-2 center"><%:Weighting%></div> 48 <div class="th col-2 center"><%:Data Version%></div> 49 <div class="th col-2 center"><%:Stable Data Version%></div> 50 </div> 51 52 <!-- leader situatioin --> 53 <% leader = threadget("leaderdata").leaderdata %> 54 <div class="tr cbi-rowstyle-2%>" style="border:solid 1px #ddd; border-top:hidden;"> 55 <div class="td col-3 center"><%=leader.LeaderRouterId%></div> 56 <div class="td col-3 center"><%=leader.PartitionId%></div> 57 <div class="td col-2 center"><%=leader.Weighting%></div> 58 <div class="td col-2 center"><%=leader.DataVersion%></div> 59 <div class="td col-2 center"><%=leader.StableDataVersion%></div> 60 61 </div> 62 </div> 63 </div> 64 </div> 65 <br /> 66 <h3><%: Neighbor Situation of Network%></h3><br /> 67 <!-- neighbor list --> 68 <div class="table" id="neighbors" style="width:90%;margin-left:5%;"> 69 <div class="tr table-titles" style="background-color:#eee;"> 70 <div class="th col-2 center"><%:RLOC16%></div> 71 <div class="th col-2 center"><%:Role%></div> 72 <div class="th col-2 center"><%:Age%></div> 73 <div class="th col-2 center"><%:Avg RSSI%></div> 74 <div class="th col-2 center"><%:Last RSSI%></div> 75 <div class="th col-2 center"><%:Mode%></div> 76 <div class="th col-4 center"><%:Extended MAC%></div> 77 <div class="th cbi-section-actions"> </div> 78 </div> 79 <div class="tr placeholder"> 80 <div class="td"><em><%:Collecting data...%></em></div> 81 </div> 82 </div> 83 <!--/neighbor list--> 84 85 <!-- parent list --> 86 <div class="table" id="parent" style="width:90%;margin-left:5%;display:none;"> 87 <div class="tr table-titles" style="background-color:#eee;"> 88 <div class="th col-2 center"><%:RLOC16%></div> 89 <div class="th col-2 center"><%:Role%></div> 90 <div class="th col-2 center"><%:Age%></div> 91 <div class="th col-2 center"><%:LinkQualityIn%></div> 92 <div class="th col-4 center"><%:ExtAddress%></div> 93 <div class="th cbi-section-actions"> </div> 94 </div> 95 <div class="tr placeholder"> 96 <div class="td"><em><%:Collecting data...%></em></div> 97 </div> 98 </div> 99 <!--/parent list--> 100</div> 101 102<!-- graph div --> 103<div style="width:100%;margin-left:5%;display:none;" id="graphdiv"> 104 <div style="width:20%"><svg id="topologyLegend"></svg></div> 105 <svg width="960" height="500" id="graph"></svg> 106</div> 107 108<div class="cbi-page-actions right" style="margin-top:10%;"> 109 <form class="inline" action="<%=url('admin/network/thread')%>" method="get"> 110 <input class="cbi-button cbi-button-neutral" type="submit" value="<%:Back to overview%>" /> 111 </form> 112 <form class="inline" action="<%=url('admin/network/thread_add')%>" method="post"> 113 <input type="hidden" name="token" value="<%=token%>" /> 114 <input class="cbi-button cbi-button-add" type="submit" value="<%:Add%>" /> 115 </form> 116</div> 117<%+footer%> 118 119<script src='//d3js.org/d3.v4.min.js'></script> 120<script type="text/javascript" src="/luci-static/resources/handle_error.js"></script> 121<script type="text/javascript">//<![CDATA[ 122 handle_error(GetURLParameter('error')); 123 124 var svg = d3.select("#graph"), 125 width = +svg.attr("width"), 126 height = +svg.attr("height"), 127 color = d3.scaleOrdinal(d3.schemeCategory10); 128 129 function getRloc(rloc, localrloc) { 130 if(rloc == localrloc) return rloc + " ( your device )"; 131 else return rloc; 132 } 133 function getColor(role) { 134 if(role == 'ftd') return "#90EE90"; 135 else if(role == 'mtd') return "#FFDAB9"; 136 else if(role == 'router') return "#87CEFA"; 137 else if(role == 'leader') return "#FFA07A"; 138 else if(role == 'joiner') return "#778899"; 139 } 140 var nodes, links; 141 var simulation = d3.forceSimulation(nodes) 142 .force("charge", d3.forceManyBody().strength(-1000)) 143 .force("link", d3.forceLink(links).distance(200)) 144 .force("x", d3.forceX()) 145 .force("y", d3.forceY()) 146 .alphaTarget(1) 147 .on("tick", ticked); 148 149 var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"), 150 link = g.append("g").attr("stroke", "#eee").attr("stroke-width", 1.5).selectAll(".link"), 151 node = g.append("g").attr("stroke", "#fff").attr("stroke-width", 1.5).selectAll(".node"), 152 text = g.append("g").selectAll(".text"); 153 154 var legend = d3.select("#topologyLegend"); 155 156 legend.append("circle").attr("cx",50).attr("cy",30).attr("r", 6).style("fill", getColor("leader")); 157 legend.append("circle").attr("cx",50).attr("cy",50).attr("r", 6).style("fill", getColor("router")); 158 legend.append("circle").attr("cx",50).attr("cy",70).attr("r", 4).style("fill", getColor("ftd")); 159 legend.append("circle").attr("cx",50).attr("cy",90).attr("r", 4).style("fill", getColor("mtd")); 160 legend.append("circle").attr("cx",50).attr("cy",110).attr("r", 4).style("fill", getColor("joiner")); 161 legend.append("text").attr("x", 60).attr("y", 35).text("leader").style("font-size", "15px").attr("alignment-baseline","middle"); 162 legend.append("text").attr("x", 60).attr("y", 55).text("router").style("font-size", "15px").attr("alignment-baseline","middle"); 163 legend.append("text").attr("x", 60).attr("y", 75).text("FTD child").style("font-size", "15px").attr("alignment-baseline","middle"); 164 legend.append("text").attr("x", 60).attr("y", 95).text("MTD child").style("font-size", "15px").attr("alignment-baseline","middle"); 165 legend.append("text").attr("x", 60).attr("y", 115).text("new joiner").style("font-size", "15px").attr("alignment-baseline","middle"); 166 167 function update_graph(nodes, links, localrloc) { 168 node = node.data(nodes, function(d) { return d.rloc;}); 169 node.exit().remove(); 170 node = node.enter().append("circle") 171 .attr("fill", function(d) { return getColor(d.role); }) 172 .attr("r", function(d) { 173 return (d.role == 'router' || d.role == 'leader' ? 10 : 7); 174 }) 175 .merge(node); 176 177 link = link.data(links, function(d) { return d.source.rloc + "-" + d.target.rloc; }); 178 link.exit().remove(); 179 link = link.enter().append("line").merge(link); 180 181 text = text.data(nodes, function(d) { return getRloc(d.rloc, localrloc); }); 182 text.exit().remove(); 183 text = text.enter().append('text') 184 .attr("fill", "black") 185 .attr("dx", 20) 186 .attr("dy", 8) 187 .text(function(d) { return getRloc(d.rloc, localrloc); }) 188 .merge(text); 189 190 simulation.nodes(nodes); 191 simulation.force("link").links(links); 192 simulation.alpha(1).restart(); 193 } 194 195 function ticked() { 196 node.attr("cx", function(d) { return d.x; }) 197 .attr("cy", function(d) { return d.y; }) 198 link.attr("x1", function(d) { return d.source.x; }) 199 .attr("y1", function(d) { return d.source.y; }) 200 .attr("x2", function(d) { return d.target.x; }) 201 .attr("y2", function(d) { return d.target.y; }); 202 text.attr("x",function(d){ return d.x; }) 203 .attr("y",function(d){ return d.y; }); 204 } 205 206 function showlist() { 207 document.getElementById('listdiv').style.display = "block"; 208 document.getElementById('graphdiv').style.display = "none"; 209 document.getElementById('listtab').className = "cbi-tab"; 210 document.getElementById('graphtab').className = "cbi-tab-disabled"; 211 } 212 213 function showgraph() { 214 document.getElementById('listdiv').style.display = "none"; 215 document.getElementById('graphdiv').style.display = "block"; 216 document.getElementById('listtab').className = "cbi-tab-disabled"; 217 document.getElementById('graphtab').className = "cbi-tab"; 218 } 219 220 function getRole(rloc, leader) { 221 if(parseInt(rloc) == leader) return 'leader'; 222 else if((parseInt(rloc) & 0xff) == 0) return 'router'; 223 else return 'ftd'; 224 } 225 226 XHR.poll(5, '<%=url('admin/network/thread_graph')%>', null, 227 function(x, st) 228 { 229 if(st) 230 { 231 nodes = []; 232 links = []; 233 234 var leaderRloc = st.leader << 10; 235 var localrloc = st.rloc16; 236 // get local informatioin 237 st.connect.forEach(function(bss) { 238 var localIndex = getNodesIndex(bss.rloc); 239 if(localIndex == -1) 240 { 241 nodes.push( { 242 rloc: bss.rloc, 243 role: getRole(bss.rloc, leaderRloc) 244 } ); 245 localIndex = getNodesIndex(bss.rloc); 246 } 247 bss.childdata.forEach(function(child) { 248 if(getNodesIndex(child.rloc) == -1) 249 { 250 nodes.push( { 251 rloc: child.rloc, 252 role: (((child.mode & 0x2) >> 1 == 1) ? 'ftd' : 'mtd') 253 } ); 254 } 255 links.push( { 256 source: localIndex, 257 target: getNodesIndex(child.rloc) 258 } ); 259 }); 260 bss.routedata.forEach(function(router) { 261 if(getNodesIndex(router.rloc) == -1) 262 { 263 nodes.push( { 264 rloc: router.rloc, 265 role: getRole(router.rloc, leaderRloc) 266 } ); 267 } 268 links.push( { 269 source: localIndex, 270 target: getNodesIndex(router.rloc) 271 } ); 272 }); 273 }); 274 275 var i; 276 for(i = 0;i < st.joinernum;i++) { 277 nodes.push( { 278 rloc: "new joiner" + i.toString(), 279 role: 'joiner' 280 } ); 281 } 282 283 update_graph(nodes, links, localrloc); 284 } 285 }); 286 287 function getNodesIndex(targetRloc) 288 { 289 var i; 290 for (i = 0; i < nodes.length; i++) 291 { 292 if(nodes[i].rloc == targetRloc) 293 return Number(i); 294 } 295 return Number(-1); 296 } 297 298 XHR.poll(2, '<%=url('admin/network/thread_neighbors')%>', null, 299 function(x, st) 300 { 301 if (st && st.state == 'child') 302 { 303 var tb = document.getElementById('parent'); 304 document.getElementById('neighbors').style.display = "none"; 305 if(tb) 306 { 307 var rows = []; 308 309 st.neighbor.forEach(function(bss) { 310 rows.push([ 311 '<div class="col-2 center"> %s </div>'.format(bss.Rloc16), 312 '<div class="col-2 center"> %s </div>'.format(transRole(bss.Role)), 313 '<div class="col-2 center"> %s </div>'.format(bss.Age), 314 '<div class="col-2 center"> %s </div>'.format(bss.LinkQualityIn), 315 '<div class="col-4 center"> %s </div>'.format(bss.ExtAddress), 316 ]); 317 }); 318 var joiner; 319 for (joiner = 0; joiner < st.joinernum; joiner++) { 320 rows.push([ 321 '<div class="col-2 center"> Pending </div>', 322 '<div class="col-2 center"> New Joiner </div>', 323 '<div class="col-2 center"> Pending </div>', 324 '<div class="col-2 center"> Pending </div>', 325 '<div class="col-4 center"> %s </div>'.format(st.joinerlist[joiner].isAny ? "*" : st.joinerlist[joiner].eui64), 326 '<div class="th cbi-section-actions">' + 327 '<form action="<%=url('admin/network/joiner_remove')%>" method="post">' + 328 '<input type="hidden" name="token" value="<%=token%>" />' + 329 '<input type="hidden" name="isAny" value="%d" />'.format(st.joinerlist[joiner].isAny) + 330 '<input type="hidden" name="eui64" value="%s" />'.format(st.joinerlist[joiner].isAny ? "*" : st.joinerlist[joiner].eui64) + 331 '<input class="cbi-button cbi-button-reset" type="submit" value="<%:Remove%>" />' + 332 '</form>' + 333 '</div>' 334 ]); 335 } 336 cbi_update_table(tb, rows, '<center><em><%:No information available%></em></center>'); 337 tb.style.display = "table"; 338 } 339 } 340 else if(st) 341 { 342 var tb = document.getElementById('neighbors'); 343 document.getElementById('parent').style.display = "none"; 344 if(tb) 345 { 346 var rows = []; 347 348 st.neighbor.forEach(function(bss) { 349 rows.push([ 350 '<div class="col-2 center"> %s </div>'.format(bss.Rloc16), 351 '<div class="col-2 center"> %s </div>'.format(transRole(bss.Role)), 352 '<div class="col-2 center"> %s </div>'.format(bss.Age), 353 '<div class="col-2 center"> %s </div>'.format(bss.AvgRssi), 354 '<div class="col-2 center"> %s </div>'.format(bss.LastRssi), 355 '<div class="col-2 center"> %s </div>'.format(bss.Mode), 356 '<div class="col-4 center"> %s </div>'.format(bss.ExtAddress) 357 ]); 358 }); 359 var joiner; 360 for (joiner = 0; joiner < st.joinernum; joiner++) { 361 rows.push([ 362 '<div class="col-2 center"> Pending </div>', 363 '<div class="col-2 center"> New Joiner </div>', 364 '<div class="col-2 center"> Pending </div>', 365 '<div class="col-2 center"> Pending </div>', 366 '<div class="col-2 center"> Pending </div>', 367 '<div class="col-2 center"> Pending </div>', 368 '<div class="col-4 center"> %s </div>'.format(st.joinerlist[joiner].isAny ? "*" : st.joinerlist[joiner].eui64), 369 '<div class="th cbi-section-actions">' + 370 '<form action="<%=url('admin/network/joiner_remove')%>" method="post">' + 371 '<input type="hidden" name="token" value="<%=token%>" />' + 372 '<input type="hidden" name="isAny" value="%d" />'.format(st.joinerlist[joiner].isAny) + 373 '<input type="hidden" name="eui64" value="%s" />'.format(st.joinerlist[joiner].isAny ? "*" : st.joinerlist[joiner].eui64) + 374 '<input class="cbi-button cbi-button-reset" type="submit" value="<%:Remove%>" />' + 375 '</form>' + 376 '</div>' 377 ]); 378 } 379 cbi_update_table(tb, rows, '<center><em><%:No information available%></em></center>'); 380 tb.style.display = "table"; 381 } 382 } 383 }); 384 385 function transRole(info) { 386 if(info == "C") return 'Child'; 387 else if(info == "R") return "Router"; 388 else return "Pending"; 389 } 390//]]></script> 391