2 Copyright
2016-
2017 Dan Luedtke
<mail@danrl.com
>
3 Licensed to the public under the Apache License
2.0.
11 local function qr_clean(qr_type, value)
12 if not value or value ==
"" or value ==
"(none)" then
15 if qr_type ==
"privkey" then
16 return
"PrivateKey = " ..value
17 elseif qr_type ==
"pubkey" then
18 return
"PublicKey = " ..value
22 local wg_dump = io.popen(
"wg show all dump 2>/dev/null")
25 for line in wg_dump:lines() do
26 local line = string.split(line,
"\t")
27 if not (last_device == line[
1]) then
32 listen_port = line[
4],
36 qr_pubkey[line[
1]] = qr_clean(
"pubkey", line[
3])
42 latest_handshake = line[
6],
43 transfer_rx = line[
7],
44 transfer_tx = line[
8],
45 persistent_keepalive = line[
9]
47 if not (line[
4] == '(none)') then
49 for ipkey, ipvalue in pairs(string.split(line[
5],
",")) do
51 table.insert(peer['allowed_ips'], ipvalue)
55 table.insert(data[line[
1]].peers, peer)
60 if luci.http.formvalue(
"status") ==
"1" then
61 luci.http.prepare_content(
"application/json")
62 luci.http.write_json(data)
69 <script type=
"text/javascript">//<![CDATA[
71 function bytes_to_str(bytes) {
72 bytes = parseFloat(bytes);
73 if (bytes <
1) { return
"0 B"; }
74 var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
75 var i = parseInt(Math.floor(Math.log(bytes) / Math.log(
1024)));
76 return Math.round(bytes / Math.pow(
1024, i),
2) + ' ' + sizes[i];
79 function timestamp_to_str(timestamp) {
84 var seconds = (now.getTime() /
1000) - timestamp;
87 ago = parseInt(seconds) + '<%:s ago%
>';
88 } else if (seconds <
3600) {
89 ago = parseInt(seconds /
60) + '<%:m ago%
>';
90 } else if (seconds <
86401) {
91 ago = parseInt(seconds /
3600) + '<%:h ago%
>';
93 ago = '<%:over a day ago%
>';
95 var t = new Date(timestamp *
1000);
96 return t.toUTCString() + ' (' + ago + ')';
99 function toggle_qrcode(iface) {
100 var view = document.getElementById(iface.name);
101 if (view.style.display ===
"none") {
102 view.style.display =
"block";
104 view.style.display =
"none";
108 XHR.poll(-
1, '<%=REQUEST_URI%
>', { status:
1 },
110 for (var key in data) {
111 if (!data.hasOwnProperty(key)) { continue; }
113 var iface = data[key];
115 if (iface.public_key == '(none)') {
116 s += '
<em><%:Interface does not have a public key!%
></em>';
119 '
<strong><%:Public Key%
>:
</strong>%s',
123 if (iface.listen_port
> 0) {
125 '
<br /><strong><%:Listen Port%
>:
</strong>%s',
129 if (iface.fwmark != 'off') {
131 '
<br /><strong><%:Firewall Mark%
>:
</strong>%s',
135 document.getElementById(ifname +
"_info").innerHTML = s;
136 for (var i =
0, ilen = iface.peers.length; i < ilen; i++) {
137 var peer = iface.peers[i];
138 var s = String.format(
139 '
<strong><%:Public Key%
>:
</strong>%s',
142 if (peer.endpoint != '(none)') {
144 '
<br /><strong><%:Endpoint%
>:
</strong>%s',
148 if (peer.allowed_ips.length
> 0) {
149 s += '
<br /><strong><%:Allowed IPs%
>:
</strong>';
150 for (var k =
0, klen = peer.allowed_ips.length; k < klen; k++) {
151 s += '
<br />  • ' + peer.allowed_ips[k];
154 if (peer.persistent_keepalive != 'off') {
156 '
<br /><strong><%:Persistent Keepalive%
>:
</strong>%ss',
157 peer.persistent_keepalive
160 var icon = '
<img src=
"<%=resource%>/icons/tunnel_disabled.png" />';
161 var now = new Date();
162 if (((now.getTime() /
1000) - peer.latest_handshake) <
140) {
163 icon = '
<img src=
"<%=resource%>/icons/tunnel.png" />';
166 '
<br /><strong><%:Latest Handshake%
>:
</strong>%s',
167 timestamp_to_str(peer.latest_handshake)
170 '
<br /><strong><%:Data Received%
>:
</strong>%s' +
171 '
<br /><strong><%:Data Transmitted%
>:
</strong>%s',
172 bytes_to_str(peer.transfer_rx),
173 bytes_to_str(peer.transfer_tx),
175 document.getElementById(ifname +
"_" + peer.public_key +
"_icon").innerHTML = icon;
176 document.getElementById(ifname +
"_" + peer.public_key +
"_info").innerHTML = s;
182 <h2><%:WireGuard Status%
></h2>
184 <div class=
"cbi-section">
186 <% if next(data) == nil then %
>
187 <div class=
"table cbi-section-table">
188 <div class=
"tr cbi-section-table-row">
190 <em><%:This section contains no values yet%
></em>
198 for ikey, iface in pairs(data) do
200 <h3><%:Interface%
> <%=ikey%
></h3>
201 <div class=
"cbi-value" id=
"button" style=
"padding: 5px">
202 <input class=
"cbi-button cbi-button-apply" type=
"button" name=
"qrcode_<%=ikey%>" value=
"<%:Show/Hide QR-Code%>" onclick=
"toggle_qrcode(this)" />
208 if fs.access(
"/usr/bin/qrencode") then
209 qr_privkey = qr_clean(
"privkey", luci.sys.exec(
"wg genkey 2>/dev/null"))
210 if qr_pubkey[ikey] and qr_privkey then
211 qr_enc =
"[Interface]\n" ..qr_privkey..
"\n[Peer]\n" ..qr_pubkey[ikey]..
"\nAllowedIPs = 0.0.0.0/0, ::/0"
212 qr_code = luci.sys.exec(
"/usr/bin/qrencode --inline --8bit --type=SVG --output=- '" ..qr_enc..
"' 2>/dev/null")
214 qr_code =
"<em>The QR-Code could not be generated, the wg interface setup is incomplete!</em>"
217 qr_code =
"<em>For QR-Code support please install the package 'qrencode'!</em>"
220 <div class=
"cbi-section-node">
221 <span class=
"cbi-value" style=
"display: none" id=
"qrcode_<%=ikey%>">
222 <%:The QR-Code works per wg interface, it will be refreshed with every manual page reload and transfers the following information:%
><br />
223 • <%:[Interface] A random, on the fly generated 'PrivateKey', the key will not be saved on the router%
><br />
224 • <%:[Peer] The 'PublicKey' of that wg interface and the 'AllowedIPs' with the default of '
0.0.0.0/
0, ::/
0' to allow sending traffic to any IPv4 and IPv6 address%
><br />
225 <hr /><%=qr_code%
><br />
228 <div class=
"cbi-section-node">
229 <div class=
"table cbi-section-table">
230 <div class=
"tr cbi-section-table-row" style=
"text-align: left;">
231 <div class=
"td" style=
"text-align: left; vertical-align:top"><%:Configuration%
></div>
232 <div class=
"td" style=
"flex: 0 1 90%; text-align: left;">
233 <div class=
"table cbi-section-table" style=
"border: 0px;">
234 <div class=
"tr cbi-section-table-row" style=
"text-align: left; border: 0px;">
235 <div class=
"td" id=
"<%=ikey%>_icon" style=
"width: 22px; text-align: left; border-top: 0px; padding: 3px;"> </div>
236 <div class=
"td" id=
"<%=ikey%>_info" style=
"flex: 0 1 90%; text-align: left; vertical-align:middle; padding: 3px; border-top: 0px;"><em><%:Collecting data...%
></em></div>
242 local cur = uci.cursor()
244 for pkey, peer in pairs(iface.peers) do
246 cur:foreach(
"network",
"wireguard_" .. ikey, function(s)
247 local key, value, tmp_desc, pub_key
248 for key, value in pairs(s) do
249 if key ==
"description" then
252 if value == peer.public_key then
255 if pub_key and tmp_desc then
256 desc = ': ' ..tmp_desc
261 <div class=
"tr cbi-section-table-row" style=
"text-align: left;">
262 <div class=
"td" style=
"text-align: left; vertical-align:top"><%:Peer%
><%=desc%
></div>
263 <div class=
"td" style=
"flex: 0 1 90%; text-align: left;">
264 <div class=
"table cbi-section-table" style=
"border: 0px">
265 <div class=
"tr cbi-section-table-row" style=
"border: 0px;">
266 <div class=
"td" id=
"<%=ikey%>_<%=peer.public_key%>_icon" style=
"width:16px; text-align: left; padding: 3px;border-top: 0px;">
267 <img src=
"<%=resource%>/icons/tunnel_disabled.png" />
270 <div class=
"td" id=
"<%=ikey%>_<%=peer.public_key%>_info" style=
"flex: 0 1 90%; text-align: left; vertical-align:middle; padding: 3px;border-top: 0px;"><em><%:Collecting data...%
></em></div>