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