luci-app-wireguard: add QR Code support plus fixes
[oweals/luci.git] / applications / luci-app-wireguard / luasrc / view / wireguard.htm
1 <%#
2         Copyright 2016-2017 Dan Luedtke <mail@danrl.com>
3         Licensed to the public under the Apache License 2.0.
4 -%>
5
6 <%
7         local uci = uci.cursor()
8         local data = { }
9         local last_device = ""
10         local enc = { }
11
12         local wg_dump = io.popen("wg show all dump")
13         if wg_dump then
14                 local line
15                 for line in wg_dump:lines() do
16                         local line = string.split(line, "\t")
17                         if not (last_device == line[1]) then
18                                 last_device = line[1]
19                                 data[line[1]] = {
20                                         name            = line[1],
21                                         public_key      = line[3],
22                                         listen_port     = line[4],
23                                         fwmark          = line[5],
24                                         peers           = { }
25                                 }
26                                 local s = uci:get_list("network", line[1], "addresses")
27                                 local address = ""
28                                 local key, value
29                                 for key, value in pairs(s) do
30                                         if address ~= "" then
31                                                 address = address.. ", " ..value
32                                         else
33                                                 address = value
34                                         end
35                                 end
36                                 enc[line[1]] = "[Interface]\nPrivateKey = " ..line[2].. "\nAddress = " ..address
37                         else
38                                 local peer = {
39                                         public_key              = line[2],
40                                         endpoint                = line[4],
41                                         allowed_ips             = { },
42                                         latest_handshake        = line[6],
43                                         transfer_rx             = line[7],
44                                         transfer_tx             = line[8],
45                                         persistent_keepalive    = line[9]
46                                 }
47                                 if not (line[4] == '(none)') then
48                                         local ipkey, ipvalue
49                                         for ipkey, ipvalue in pairs(string.split(line[5], ",")) do
50                                                 if #ipvalue > 0 then
51                                                         table.insert(peer['allowed_ips'], ipvalue)
52                                                 end
53                                         end
54                                 end
55                                 table.insert(data[line[1]].peers, peer)
56                                 enc[line[1]] = enc[line[1]].. "\n\n[Peer]\nEndpoint = " ..line[4].. "\nPublicKey = " ..line[2].. "\nAllowedIPs = " ..line[5]
57                         end
58                 end
59         end
60
61         if luci.http.formvalue("status") == "1" then
62                 luci.http.prepare_content("application/json")
63                 luci.http.write_json(data)
64                 return
65         end
66 -%>
67
68 <%+header%>
69
70 <script type="text/javascript">//<![CDATA[
71
72         function bytes_to_str(bytes) {
73                 bytes = parseFloat(bytes);
74                 if (bytes < 1) { return "0 B"; }
75                 var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
76                 var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
77                 return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
78         };
79
80         function timestamp_to_str(timestamp) {
81                 if (timestamp < 1) {
82                         return '<%:Never%>';
83                 }
84                 var now = new Date();
85                 var seconds = (now.getTime() / 1000) - timestamp;
86                 var ago = "";
87                 if (seconds < 60) {
88                         ago = parseInt(seconds) + '<%:s ago%>';
89                 } else if (seconds < 3600) {
90                         ago = parseInt(seconds / 60) + '<%:m ago%>';
91                 } else if (seconds < 86401) {
92                         ago = parseInt(seconds / 3600) + '<%:h ago%>';
93                 } else {
94                         ago = '<%:over a day ago%>';
95                 }
96                 var t = new Date(timestamp * 1000);
97                 return t.toUTCString() + ' (' + ago + ')';
98         }
99
100         function toggle_qrcode(iface) {
101                 var view = document.getElementById(iface.name);
102                 if (view.style.display === "none") {
103                         view.style.display = "block";
104                 } else {
105                         view.style.display = "none";
106                 }
107         }
108
109         XHR.poll(5, '<%=REQUEST_URI%>', { status: 1 },
110         function(x, data) {
111                 for (var key in data) {
112                         if (!data.hasOwnProperty(key)) { continue; }
113                         var ifname = key;
114                         var iface = data[key];
115                         var s = "";
116                         if (iface.public_key == '(none)') {
117                                 s += '<em><%:Interface does not have a public key!%></em>';
118                         } else {
119                                 s += String.format(
120                                         '<strong><%:Public Key%>: </strong>%s',
121                                         iface.public_key
122                                 );
123                         }
124                         if (iface.listen_port > 0) {
125                                 s += String.format(
126                                         '<br /><strong><%:Listen Port%>: </strong>%s',
127                                         iface.listen_port
128                                 );
129                         }
130                         if (iface.fwmark != 'off') {
131                                 s += String.format(
132                                         '<br /><strong><%:Firewall Mark%>: </strong>%s',
133                                         iface.fwmark
134                                 );
135                         }
136                         document.getElementById(ifname + "_info").innerHTML = s;
137                         for (var i = 0, ilen = iface.peers.length; i < ilen; i++) {
138                                 var peer = iface.peers[i];
139                                 var s = String.format(
140                                         '<strong><%:Public Key%>: </strong>%s',
141                                         peer.public_key
142                                 );
143                                 if (peer.endpoint != '(none)') {
144                                         s += String.format(
145                                                 '<br /><strong><%:Endpoint%>: </strong>%s',
146                                                 peer.endpoint
147                                         );
148                                 }
149                                 if (peer.allowed_ips.length > 0) {
150                                         s += '<br /><strong><%:Allowed IPs%>:</strong>';
151                                         for (var k = 0, klen = peer.allowed_ips.length; k < klen; k++) {
152                                                 s += '<br />&#160;&#160;&#8226;&#160;' + peer.allowed_ips[k];
153                                         }
154                                 }
155                                 if (peer.persistent_keepalive != 'off') {
156                                         s += String.format(
157                                                 '<br /><strong><%:Persistent Keepalive%>: </strong>%ss',
158                                                 peer.persistent_keepalive
159                                         );
160                                 }
161                                 var icon = '<img src="<%=resource%>/icons/tunnel_disabled.png" />';
162                                 var now = new Date();
163                                 if (((now.getTime() / 1000) - peer.latest_handshake) < 140) {
164                                         icon = '<img src="<%=resource%>/icons/tunnel.png" />';
165                                 }
166                                 s += String.format(
167                                         '<br /><strong><%:Latest Handshake%>: </strong>%s',
168                                         timestamp_to_str(peer.latest_handshake)
169                                 );
170                                 s += String.format(
171                                         '<br /><strong><%:Data Received%>: </strong>%s' +
172                                         '<br /><strong><%:Data Transmitted%>: </strong>%s',
173                                         bytes_to_str(peer.transfer_rx),
174                                         bytes_to_str(peer.transfer_tx),
175                                 );
176                                 document.getElementById(ifname + "_" + peer.public_key + "_icon").innerHTML = icon;
177                                 document.getElementById(ifname + "_" + peer.public_key + "_info").innerHTML = s;
178                         }
179                 }
180         });
181 //]]></script>
182
183 <h2>WireGuard Status</h2>
184
185 <div class="cbi-section">
186 <%-
187 local ikey, iface
188 for ikey, iface in pairs(data) do
189 -%>
190         <h3><%:Interface%> <%=ikey%></h3>
191         <div class="cbi-value" id="button" style="padding: 5px">
192                 <input class="cbi-button cbi-button-apply" type="button" name="qrcode_<%=ikey%>" value="<%:Show/Hide QR-Code%>" onclick="toggle_qrcode(this)" />
193         </div>
194 <%-
195         local qrcode
196         if fs.access("/usr/bin/qrencode") then
197                 if enc[ikey]:sub(26,31) ~= "(none)" then
198                         qrcode = luci.sys.exec("/usr/bin/qrencode --inline --8bit --type=SVG --output=- '" ..enc[ikey].. "'")
199                 end
200         else
201                 qrcode = "<em>For QR-Code support please install package 'qrencode'!</em>"
202         end
203 -%>
204         <div class="cbi-value-title">
205                 <span class="cbi-value" style="display: none" id="qrcode_<%=ikey%>"><%=qrcode%></span>
206         </div>
207         <div class="cbi-section-node">
208                 <div class="table cbi-section-table">
209                         <div class="tr cbi-section-table-row" style="text-align: left;">
210                                 <div class="td" style="text-align: left; vertical-align:top"><%:Configuration%></div>
211                                 <div class="td" style="flex: 0 1 90%; text-align: left;">
212                                         <div class="table cbi-section-table" style="border: 0px;">
213                                                 <div class="tr cbi-section-table-row" style="text-align: left; border: 0px;">
214                                                         <div class="td" id="<%=ikey%>_icon" style="width: 22px; text-align: left; border-top: 0px; padding: 3px;">&#160;</div>
215                                                         <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>
216                                                 </div>
217                                         </div>
218                                 </div>
219                         </div>
220         <%-
221         local cur = uci.cursor()
222         local pkey, peer
223         for pkey, peer in pairs(iface.peers) do
224                 local desc
225                 cur:foreach("network", "wireguard_" .. ikey, function(s)
226                         local key, value, tmp_desc, pub_key
227                         for key, value in pairs(s) do
228                                 if key == "description" then
229                                         tmp_desc = value
230                                 end
231                                 if value == peer.public_key then
232                                         pub_key = value
233                                 end
234                                 if pub_key and tmp_desc then
235                                         desc = ': ' ..tmp_desc
236                                 end
237                         end
238                 end)
239         -%>
240                         <div class="tr cbi-section-table-row" style="text-align: left;">
241                                 <div class="td" style="text-align: left; vertical-align:top"><%:Peer%><%=desc%></div>
242                                 <div class="td" style="flex: 0 1 90%; text-align: left;">
243                                         <div class="table cbi-section-table" style="border: 0px">
244                                                 <div class="tr cbi-section-table-row" style="border: 0px;">
245                                                         <div class="td" id="<%=ikey%>_<%=peer.public_key%>_icon" style="width:16px; text-align: left; padding: 3px;border-top: 0px;">
246                                                                 <img src="<%=resource%>/icons/tunnel_disabled.png" />
247                                                                 <small>?</small>
248                                                         </div>
249                                                         <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>
250                                                 </div>
251                                         </div>
252                                 </div>
253                         </div>
254                 <%-
255         end
256         -%>
257                 </div>
258         </div>
259         <%-
260 end
261 -%>
262 </div>
263
264 <%+footer%>