xref: /aosp_15_r20/external/ot-br-posix/src/openwrt/view/admin_thread/thread_view.htm (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
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">&#160;</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">&#160;</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