2 local ubus = require "ubus"
3 local sys = require "luci.sys"
4 local utl = require "luci.util"
6 function connect_ubus(methods)
8 local conn = ubus.connect()
11 error("Failed to connect to ubusd")
14 result = conn:call("otbr", methods, {})
19 function threadget(action)
20 local result = connect_ubus(action)
28 <h2><%:Thread View: %><%=threadget("networkname").NetworkName%><%: (wpan0)%></h2>
29 <div>This is the list and topograph of your thread network.</div>
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>
38 <div style="width:100%;" id="listdiv">
40 <h3><%: Leader Situation of Network%></h3><br />
41 <div class="cbi-map" style="width:90%;margin-left:5%;">
42 <div class="cbi-section">
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>
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>
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>
79 <div class="tr placeholder">
80 <div class="td"><em><%:Collecting data...%></em></div>
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>
95 <div class="tr placeholder">
96 <div class="td"><em><%:Collecting data...%></em></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>
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%>" />
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%>" />
119 <script src='http://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'));
124 var svg = d3.select("#graph"),
125 width = +svg.attr("width"),
126 height = +svg.attr("height"),
127 color = d3.scaleOrdinal(d3.schemeCategory10);
129 function getRloc(rloc, localrloc) {
130 if(rloc == localrloc) return rloc + " ( your device )";
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";
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())
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");
154 var legend = d3.select("#topologyLegend");
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");
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);
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);
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")
187 .text(function(d) { return getRloc(d.rloc, localrloc); })
190 simulation.nodes(nodes);
191 simulation.force("link").links(links);
192 simulation.alpha(1).restart();
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; });
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";
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";
220 function getRole(rloc, leader) {
221 if(parseInt(rloc) == leader) return 'leader';
222 else if((parseInt(rloc) & 0xff) == 0) return 'router';
226 XHR.poll(5, '<%=url('admin/network/thread_graph')%>', null,
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);
243 role: getRole(bss.rloc, leaderRloc)
245 localIndex = getNodesIndex(bss.rloc);
247 bss.childdata.forEach(function(child) {
248 if(getNodesIndex(child.rloc) == -1)
252 role: (((child.mode & 0x2) >> 1 == 1) ? 'ftd' : 'mtd')
257 target: getNodesIndex(child.rloc)
260 bss.routedata.forEach(function(router) {
261 if(getNodesIndex(router.rloc) == -1)
265 role: getRole(router.rloc, leaderRloc)
270 target: getNodesIndex(router.rloc)
276 for(i = 0;i < st.joinernum;i++) {
278 rloc: "new joiner" + i.toString(),
283 update_graph(nodes, links, localrloc);
287 function getNodesIndex(targetRloc)
290 for (i = 0; i < nodes.length; i++)
292 if(nodes[i].rloc == targetRloc)
298 XHR.poll(2, '<%=url('admin/network/thread_neighbors')%>', null,
301 if (st && st.state == 'child')
303 var tb = document.getElementById('parent');
304 document.getElementById('neighbors').style.display = "none";
309 st.neighbor.forEach(function(bss) {
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),
319 for (joiner = 0; joiner < st.joinernum; joiner++) {
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%>" />' +
336 cbi_update_table(tb, rows, '<center><em><%:No information available%></em></center>');
337 tb.style.display = "table";
342 var tb = document.getElementById('neighbors');
343 document.getElementById('parent').style.display = "none";
348 st.neighbor.forEach(function(bss) {
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)
360 for (joiner = 0; joiner < st.joinernum; joiner++) {
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%>" />' +
379 cbi_update_table(tb, rows, '<center><em><%:No information available%></em></center>');
380 tb.style.display = "table";
385 function transRole(info) {
386 if(info == "C") return 'Child';
387 else if(info == "R") return "Router";
388 else return "Pending";