1+ <?php
2+
3+ namespace Tailscale ;
4+
5+ $ docroot = $ docroot ?? $ _SERVER ['DOCUMENT_ROOT ' ] ?: '/usr/local/emhttp ' ;
6+ require_once "{$ docroot }/plugins/tailscale/include/common.php " ;
7+
8+ $ tailscaleConfig = $ tailscaleConfig ?? new Config ();
9+ $ tr = $ tr ?? new Translator ();
10+
11+ if ( ! $ tailscaleConfig ->Enable ) {
12+ echo ($ tr ->tr ("tailscale_disabled " ));
13+ return ;
14+ }
15+
16+ $ tailscaleInfo = $ tailscaleInfo ?? new Info ($ tr );
17+ ?>
18+
19+ <script src="/webGui/javascript/jquery.tablesorter.widgets.js"></script>
20+
21+ <script>
22+
23+ function tailscaleControlsDisabled(val) {
24+ $('#configTable_refresh').prop('disabled', val);
25+ }
26+ function showTailscaleConfig() {
27+ tailscaleControlsDisabled(true);
28+ $.post('/plugins/tailscale/include/data/Config.php',{action: 'get'},function(data){
29+ clearTimeout(timers.refresh);
30+ $("#configTable").trigger("destroy");
31+ $('#configTable').html(data.config);
32+ $("#routesTable").trigger("destroy");
33+ $('#routesTable').html(data.routes);
34+ $("#connectionTable").trigger("destroy");
35+ $('#connectionTable').html(data.connection);
36+ $('div.spinner.fixed').hide('fast');
37+ tailscaleControlsDisabled(false);
38+ validateTailscaleRoute();
39+ },"json");
40+ }
41+ async function setFeature(feature, enable) {
42+ $('div.spinner.fixed').show('fast');
43+ tailscaleControlsDisabled(true);
44+ var res = await $.post('/plugins/tailscale/include/data/Config.php',{action: 'set-feature', feature: feature, enable: enable});
45+ showTailscaleConfig();
46+ }
47+ async function tailscaleUp() {
48+ $('div.spinner.fixed').show('fast');
49+ tailscaleControlsDisabled(true);
50+ var res = await $.post('/plugins/tailscale/include/data/Config.php',{action: 'up'});
51+ $('#tailscaleUpLink').attr('href', res);
52+ $('#tailscaleUpLink').text(res);
53+ window.open(res);
54+ $('div.spinner.fixed').hide('fast');
55+ tailscaleControlsDisabled(false);
56+ }
57+ async function setTailscaleExitNode() {
58+ $('div.spinner.fixed').show('fast');
59+ tailscaleControlsDisabled(true);
60+ var res = await $.post('/plugins/tailscale/include/data/Config.php',{action: 'exit-node', node: $('#exitNodeSelect').val()});
61+ showTailscaleConfig();
62+ }
63+
64+ async function removeTailscaleRoute(route) {
65+ $('div.spinner.fixed').show('fast');
66+ tailscaleControlsDisabled(true);
67+ var res = await $.post('/plugins/tailscale/include/data/Config.php',{action: 'remove-route', route: route});
68+ showTailscaleConfig();
69+ }
70+ async function addTailscaleRoute() {
71+ $('div.spinner.fixed').show('fast');
72+ tailscaleControlsDisabled(true);
73+ var res = await $.post('/plugins/tailscale/include/data/Config.php',{action: 'add-route', route: $('#tailscaleRoute').val()});
74+ showTailscaleConfig();
75+ }
76+ function isValidCIDR(ip) {
77+ if (ip === undefined) {
78+ return false;
79+ }
80+
81+ var parts = ip.split('/');
82+ if (parts.length != 2) {
83+ return false;
84+ }
85+
86+ var mask = parseInt(parts[1]);
87+ if (isNaN(mask) || mask < 0) {
88+ return false;
89+ }
90+
91+ const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
92+ const ipv6Pattern = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/gm;
93+
94+ if(ipv4Pattern.test(parts[0])) {
95+ // IPv4
96+ if(mask > 32) {
97+ return false;
98+ }
99+ } else if (ipv6Pattern.test(parts[0])) {
100+ // IPv6
101+ if(mask > 128) {
102+ return false;
103+ }
104+ } else {
105+ return false;
106+ }
107+
108+ return true;
109+ }
110+
111+ function validateTailscaleRoute() {
112+ if (! $('#tailscaleRoute').length) {
113+ return;
114+ }
115+
116+ if (isValidCIDR($('#tailscaleRoute').val())) {
117+ $('#addTailscaleRoute').prop('disabled', false);
118+ } else {
119+ $('#addTailscaleRoute').prop('disabled', true);
120+ }
121+ }
122+
123+ showTailscaleConfig();
124+ </script>
125+
126+ <!-- TODO: Get these warnings with the table -->
127+ <?= Utils::formatWarning ($ tailscaleInfo ->getTailscaleLockWarning ()); ?>
128+ <?= Utils::formatWarning ($ tailscaleInfo ->getTailscaleNetbiosWarning ()); ?>
129+ <?= Utils::formatWarning ($ tailscaleInfo ->getKeyExpirationWarning ()); ?>
130+
131+ <table id='connectionTable' class="unraid statusTable tablesorter"><tr><td><div class="spinner"></div></td></tr></table><br>
132+ <table id='configTable' class="unraid statusTable tablesorter"><tr><td><div class="spinner"></div></td></tr></table><br>
133+ <table id='routesTable' class="unraid statusTable tablesorter"><tr><td><div class="spinner"></div></td></tr></table><br>
134+ <table>
135+ <tr>
136+ <td style="vertical-align: top">
137+ <input type="button" id="configTable_refresh" value="Refresh" onclick="showTailscaleConfig()">
138+ </td>
139+ </tr>
140+ </table>
0 commit comments