luci-app-tailscale-community: major version update
authorHan Yiming <redacted>
Thu, 5 Mar 2026 03:22:45 +0000 (11:22 +0800)
committerPaul Donald <redacted>
Tue, 17 Mar 2026 17:04:26 +0000 (18:04 +0100)
refactor: backend core code
Reconstruct the saved code to conform to the
default behavior of OpenWrt and support automatic
rollback in case of errors.

refactor: frontend device list code
add support: Peer Relay server

Signed-off-by: Han Yiming <redacted>
applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js
applications/luci-app-tailscale-community/po/es/community.po
applications/luci-app-tailscale-community/po/templates/community.pot
applications/luci-app-tailscale-community/po/zh_Hans/community.po
applications/luci-app-tailscale-community/root/etc/init.d/tailscale-settings [new file with mode: 0644]
applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json
applications/luci-app-tailscale-community/root/usr/share/rpcd/ucode/tailscale.uc

index b7f2d7bdc7e40a990e55ea068286e671d764f499..f0d1f2c9a183eb272666ec50c17f9be3c8232588 100644 (file)
@@ -8,7 +8,6 @@
 
 const callGetStatus = rpc.declare({ object: 'tailscale', method: 'get_status' });
 const callGetSettings = rpc.declare({ object: 'tailscale', method: 'get_settings' });
-const callSetSettings = rpc.declare({ object: 'tailscale', method: 'set_settings', params: ['form_data'] });
 const callDoLogin = rpc.declare({ object: 'tailscale', method: 'do_login', params: ['form_data'] });
 const callDoLogout = rpc.declare({ object: 'tailscale', method: 'do_logout' });
 const callGetSubroutes = rpc.declare({ object: 'tailscale', method: 'get_subroutes' });
@@ -24,7 +23,8 @@ const tailscaleSettingsConf = [
        [form.Flag, 'nosnat', _('Disable SNAT'), _('Disable Source NAT (SNAT) for traffic to advertised routes. Most users should leave this unchecked.'), { rmempty: false }],
        [form.Flag, 'shields_up', _('Shields Up'), _('When enabled, blocks all inbound connections from the Tailscale network.'), { rmempty: false }],
        [form.Flag, 'ssh', _('Enable Tailscale SSH'), _('Allow connecting to this device through the SSH function of Tailscale.'), { rmempty: false }],
-       [form.Flag, 'disable_magic_dns', _('Disable MagicDNS'), _('Use system DNS instead of MagicDNS.'), { rmempty: false }]
+       [form.Flag, 'disable_magic_dns', _('Disable MagicDNS'), _('Use system DNS instead of MagicDNS.'), { rmempty: false }],
+       [form.Flag, 'enable_relay', _('Enable Peer Relay'), _('Enable this device as a Peer Relay server. Requires a public IP and an UDP port open on the router.'), { rmempty: false }]
 ];
 
 const accountConf = [];        // dynamic created in render function
@@ -235,69 +235,66 @@ function renderStatus(status) {
                E('tr', {}, statusData.map(item => E('td', { 'style': 'padding-right: 20px;' }, item.value)))
        ]);
 
-       // --- Part 3: Render the Peers/Network Devices table ---
+       return statusTable;
+}
 
-       const peers = status.peers;
-       let peersContent;
+function renderDevices(status) {
+       if (!status || !status.hasOwnProperty('status')) {
+               return E('em', {}, _('Collecting data ...'));
+       }
+
+       if (status.status != 'running') {
+               return E('em', {}, _('Tailscale status error'));
+       }
 
+       if (Object.keys(regionCodeMap).length === 0) {
+               initializeRegionMap();
+       }
+
+       const peers = status.peers;
        if (!peers || Object.keys(peers).length === 0) {
-               // Display a message if no peers are found.
-               peersContent = E('p', {}, _('No peer devices found.'));
-       } else {
-               // Define headers for the peers table.
-               const peerTableHeaders = [
-                       { text: _('Status'), style: 'width: 80px;' },
-                       { text: _('Hostname') },
-                       { text: _('Tailscale IP') },
-                       { text: _('OS') },
-                       { text: _('Connection Info') },
-                       { text: _('RX') },
-                       { text: _('TX') },
-                       { text: _('Last Seen') }
-               ];
-
-               // Build the peers table.
-               peersContent = E('table', { 'class': 'cbi-table' }, [
-                       // Table Header Row
-                       E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => {
-                               let th_style = 'padding-right: 20px; text-align: left;';
-                               if (header.style) {
-                                       th_style += header.style;
-                               }
-                               return E('th', { 'class': 'cbi-table-cell', 'style': th_style }, header.text);
-                       })),
-
-                       // Table Body Rows (one for each peer)
-                       ...Object.entries(peers).map(([peerid, peer]) => {
-                               const td_style = 'padding-right: 20px;';
-
-                               return E('tr', { 'class': 'cbi-rowstyle-1' }, [
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style },
-                                               E('span', {
-                                                       'style': `color:${peer.exit_node ? 'blue' : (peer.online ? 'green' : 'gray')};`,
-                                                       'title': (peer.exit_node ? _('Exit Node') + ' ' : '') + (peer.online ? _('Online') : _('Offline'))
-                                               }, peer.online ? '●' : '○')
-                                       ),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, E('strong', {}, peer.hostname + (peer.exit_node_option ? ' (ExNode)' : ''))),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ip || 'N/A'),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ostype || 'N/A'),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatConnectionInfo(peer.linkadress || '-')),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.rx)),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.tx)),
-                                       E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatLastSeen(peer.lastseen))
-                               ]);
-                       })
-               ]);
+               return E('p', {}, _('No peer devices found.'));
        }
 
-       // Combine all parts into a single DocumentFragment.
-       // Using E() without a tag name creates a fragment, which is perfect for grouping elements.
-       return E([
-               statusTable,
-               E('div', { 'style': 'margin-top: 25px;' }, [
-                       E('h4', {}, _('Network Devices')),
-                       peersContent
-               ])
+       const peerTableHeaders = [
+               { text: _('Status'), style: 'width: 80px;' },
+               { text: _('Hostname') },
+               { text: _('Tailscale IP') },
+               { text: _('OS') },
+               { text: _('Connection Info') },
+               { text: _('RX') },
+               { text: _('TX') },
+               { text: _('Last Seen') }
+       ];
+
+       return E('table', { 'class': 'cbi-table' }, [
+               E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => {
+                       let th_style = 'padding-right: 20px; text-align: left;';
+                       if (header.style) {
+                               th_style += header.style;
+                       }
+                       return E('th', { 'class': 'cbi-table-cell', 'style': th_style }, header.text);
+               })),
+
+               ...Object.entries(peers).map(([peerid, peer]) => {
+                       const td_style = 'padding-right: 20px;';
+
+                       return E('tr', { 'class': 'cbi-rowstyle-1' }, [
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style },
+                                       E('span', {
+                                               'style': `color:${peer.exit_node ? 'blue' : (peer.online ? 'green' : 'gray')};`,
+                                               'title': (peer.exit_node ? _('Exit Node') + ' ' : '') + (peer.online ? _('Online') : _('Offline'))
+                                       }, peer.online ? '●' : '○')
+                               ),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, E('strong', {}, peer.hostname + (peer.exit_node_option ? ' (ExNode)' : ''))),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ip || 'N/A'),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ostype || 'N/A'),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatConnectionInfo(peer.linkadress || '-')),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.rx)),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.tx)),
+                               E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatLastSeen(peer.lastseen))
+                       ]);
+               })
        ]);
 }
 
@@ -353,6 +350,11 @@ return view.extend({
                                                        view.replaceChildren(content);
                                                }
 
+                                               const devicesView = document.getElementById("tailscale_devices_display");
+                                               if (devicesView) {
+                                                       devicesView.replaceChildren(renderDevices(res));
+                                               }
+
                                                // login button only available when logged out
                                                const login_btn=document.getElementsByClassName('cbi-button cbi-button-apply')[0];
                                                if(login_btn) { login_btn.disabled=(res.status != 'logout'); }
@@ -365,7 +367,7 @@ return view.extend({
                }
 
                // Bind settings to the 'settings' section of uci
-               s = map.section(form.NamedSection, 'settings', 'settings', _('Settings'));
+               s = map.section(form.NamedSection, 'settings', 'settings', null);
                s.dynamic = true;
 
                // Create the "General Settings" tab and apply tailscaleSettingsConf
@@ -373,6 +375,14 @@ return view.extend({
 
                defTabOpts(s, 'general', tailscaleSettingsConf, { optional: false });
 
+               const relayPort = s.taboption('general', form.Value, 'relay_server_port', _('Peer Relay Port'),
+                       _('UDP port for the Peer Relay service. Open this port on your router firewall/NAT.')
+               );
+               relayPort.datatype = 'port';
+               relayPort.placeholder = '40000';
+               relayPort.rmempty = false;
+               relayPort.depends('enable_relay', '1');
+
                const en = s.taboption('general', form.ListValue, 'exit_node', _('Exit Node'), _('Select an exit node from the list. If enabled, Allow LAN Access is enabled implicitly.'));
                en.value('', _('None'));
                if (status.peers) {
@@ -541,6 +551,12 @@ return view.extend({
                        ui.showModal(_('Confirm Logout'), confirmationContent);
                };
 
+               s.tab('devices', _('Devices List'));
+               const devicesSection = s.taboption('devices', form.DummyValue, '_devices');
+               devicesSection.render = function () {
+                       return E('div', { 'id': 'tailscale_devices_display', 'class': 'cbi-value' }, renderDevices(status));
+               };
+
                // Create the "Daemon Settings" tab and apply daemonConf
                //s.tab('daemon', _('Daemon Settings'));
                //defTabOpts(s, 'daemon', daemonConf, { optional: false });
@@ -548,40 +564,12 @@ return view.extend({
                return map.render();
        },
 
-       // The handleSaveApply function is executed after clicking "Save & Apply"
-       handleSaveApply(ev) {
+       // The handleSaveApply function saves UCI changes then applies them via the
+       // standard OpenWrt apply mechanism, which triggers /etc/init.d/tailscale-settings
+       // and provides automatic rollback protection if the device becomes unreachable.
+       handleSaveApply(ev, mode) {
                return map.save().then(function () {
-                       const data = map.data.get('tailscale', 'settings');
-
-                       // fix empty value issue
-                       if(!data.advertise_exit_node) data.advertise_exit_node = '';
-                       if(!data.advertise_routes) data.advertise_routes = '';
-                       if(!data.exit_node) data.exit_node = '';
-                       if(!data.custom_login_url) data.custom_login_url = '';
-                       if(!data.custom_login_AuthKey) data.custom_login_AuthKey = '';
-
-                       ui.showModal(_('Applying changes...'), E('em', {}, _('Please wait.')));
-
-                       return callSetSettings(data).then(function (response) {
-                               if (response.success) {
-                                       ui.hideModal();
-                                       setTimeout(function() {
-                                                       ui.addTimeLimitedNotification(null, [ E('p', _('Tailscale settings applied successfully.')) ], 5000, 'info');
-                                       }, 1000);
-                                       try {
-                                               L.ui.changes.revert();
-                                       } catch (error) {
-                                               ui.addTimeLimitedNotification(null, [ E('p', _('Error saving settings: %s').format(error || _('Unknown error'))) ], 7000, 'error');
-                                       }
-                               } else {
-                                       ui.hideModal();
-                                       ui.addTimeLimitedNotification(null, [ E('p', _('Error applying settings: %s').format(response.error || _('Unknown error'))) ], 7000, 'error');
-                               }
-                       });
-               }).catch(function(err) {
-                       ui.hideModal();
-                       //console.error('Save failed:', err);
-                       ui.addTimeLimitedNotification(null, [ E('p', _('Failed to save settings: %s').format(err.message)) ], 7000, 'error');
+                       return ui.changes.apply(mode == '0');
                });
        },
 
index ae06c4fc7ee02b6d80a888ebdcc9de5979b925da..45cc00c5e66f8089df0269fefebed1d2b2f76462 100644 (file)
@@ -15,51 +15,51 @@ msgstr ""
 msgid "(Experimental) Reduce Memory Usage"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:432
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:442
 msgid "1. Select \"Accept Routes\" (to access remote devices)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:433
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:443
 msgid ""
 "2. In \"Advertise Routes\", select your local subnet (to allow remote "
 "devices to access this LAN)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:434
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
 msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Accept Routes"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:441
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
 msgid "Account Settings"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Advertise Exit Node"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid "Advertise Routes"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid ""
 "Advertise subnet routes behind this device. Select from the detected subnets "
 "below or enter custom routes (comma-separated)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "Allow LAN Access"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Allow accepting routes announced by other nodes."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Allow connecting to this device through the SSH function of Tailscale."
 msgstr ""
 
@@ -67,70 +67,71 @@ msgstr ""
 msgid "Allow user access to tailscale"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
-msgid "Applying changes..."
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
 msgid "Are you sure you want to log out?"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:408
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:418
 msgid "Auto Configure Firewall"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
 msgid "Cancel"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:466
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:476
 msgid "Click to Log out account on this device."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
 msgid "Click to get a login URL for this device."
 msgstr ""
 
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:166
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:363
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:243
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:365
 msgid "Collecting data ..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:551
 msgid "Confirm Logout"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:253
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
 msgid "Connection Info"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
 msgid ""
 "Could not open a new tab. Please check if your browser or an extension "
 "blocked the pop-up."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
 msgid "Custom Login Server"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:458
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:468
 msgid "Custom Login Server Auth Key"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Declare this device as an Exit Node."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:554
+msgid "Devices List"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Disable MagicDNS"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid "Disable SNAT"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid ""
 "Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
 "should leave this unchecked."
@@ -140,18 +141,28 @@ msgstr ""
 msgid "Disabled"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:467
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:477
 msgid "Disconnect from Tailscale and expire current node key."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid "Enable Peer Relay"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Enable Tailscale SSH"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid "Enable Web Interface"
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid ""
+"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
+"port open on the router."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226
 msgid "Enabled"
 msgstr ""
@@ -162,14 +173,10 @@ msgid ""
 "performance (set GOGC=10)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:513
 msgid "Error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
-msgid "Error applying settings: %s"
-msgstr ""
-
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 msgid "Error caching DERP region map: %s"
 msgstr ""
@@ -182,72 +189,64 @@ msgstr ""
 msgid "Error reading cached DERP region map: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-msgid "Error saving settings: %s"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:409
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:419
 msgid ""
 "Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
 "features."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid "Exit Node"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid ""
 "Expose a web interface on port 5252 for managing this node over Tailscale."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:430
 msgid "Failed to configure firewall: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:514
 msgid "Failed to get login URL. You may close this tab."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
 msgid "Failed to get login URL: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:505
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
 msgid "Failed to get login URL: Invalid response from server."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:584
-msgid "Failed to save settings: %s"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid "Firewall Mode"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:417
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
 msgid "Firewall configuration applied."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:372
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:374
 msgid "General Settings"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:250
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:261
 msgid "Hostname"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
 msgid "How to enable Site-to-Site?"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:456
 msgid ""
 "If the timeout is displayed, you can refresh the page and click Login again."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
 msgid ""
 "If you are using custom login server but not providing an Auth Key, will "
 "redirect to the login page without pre-filling the key."
@@ -257,7 +256,7 @@ msgstr ""
 msgid "Invalid Date"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
 msgid ""
 "It automatically creates the tailscale interface, sets up firewall zones for "
 "LAN <-> Tailscale forwarding,"
@@ -271,28 +270,28 @@ msgstr ""
 msgid "LOGGED OUT"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:267
 msgid "Last Seen"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:462
 msgid "Leave blank for default Tailscale control plane."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Logging out..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
 msgid "Login"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:465
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:475
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:548
 msgid "Logout"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Logout failed: %s"
 msgstr ""
 
@@ -304,15 +303,11 @@ msgstr ""
 msgid "NOT RUNNING"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:298
-msgid "Network Devices"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
 msgid "No peer devices found."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:377
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:387
 msgid "None"
 msgstr ""
 
@@ -320,38 +315,41 @@ msgstr ""
 msgid "Now"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:252
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:263
 msgid "OS"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Offline"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Online"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:461
 msgid ""
 "Optional: Specify a custom control server URL (e.g., a Headscale instance, "
 "%s)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:459
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
 msgid ""
 "Optional: Specify an authentication key for the custom control server. Leave "
 "blank if not required."
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:378
+msgid "Peer Relay Port"
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:207
 msgid "Please use the login button in the settings below to authenticate."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Please wait."
 msgstr ""
 
@@ -359,26 +357,26 @@ msgstr ""
 msgid "RUNNING"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:254
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:265
 msgid "RX"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
 msgid "Requesting Login URL..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:485
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
 msgid "Requesting Tailscale login URL... Please wait."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid ""
 "Select an exit node from the list. If enabled, Allow LAN Access is enabled "
 "implicitly."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid ""
 "Select the firewall backend for Tailscale to use. Requires service restart "
 "to take effect."
@@ -391,19 +389,15 @@ msgstr ""
 msgid "Service Status"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
-msgid "Settings"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid "Shields Up"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:249
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
 msgid "Status"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
 msgid "Successfully logged out."
 msgstr ""
 
@@ -415,7 +409,7 @@ msgstr ""
 msgid "TUN Mode"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:255
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:266
 msgid "TX"
 msgstr ""
 
@@ -423,7 +417,7 @@ msgstr ""
 msgid "Tailnet Name"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 #: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
 msgid "Tailscale"
 msgstr ""
@@ -432,7 +426,7 @@ msgstr ""
 msgid "Tailscale Health Check: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:251
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:262
 msgid "Tailscale IP"
 msgstr ""
 
@@ -444,42 +438,46 @@ msgstr ""
 msgid "Tailscale IPv6"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:484
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
 msgid "Tailscale Login"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 msgid ""
 "Tailscale is a mesh VPN solution that makes it easy to connect your devices "
 "securely. This configuration page allows you to manage Tailscale settings on "
 "your OpenWrt device."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
-msgid "Tailscale settings applied successfully."
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:247
+msgid "Tailscale status error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:486
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
 msgid "This can take up to 30 seconds."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:516
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:526
 msgid ""
 "This will disconnect this device from your Tailnet and require you to re-"
 "authenticate."
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:379
+msgid ""
+"UDP port for the Peer Relay service. Open this port on your router firewall/"
+"NAT."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:120
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Unknown error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Use system DNS instead of MagicDNS."
 msgstr ""
 
@@ -487,16 +485,16 @@ msgstr ""
 msgid "Version"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid ""
 "When enabled, blocks all inbound connections from the Tailscale network."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "When using the exit node, access to the local LAN is allowed."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:435
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
 msgid ""
 "[Important] Log in to the Tailscale admin console and manually enable "
 "\"Subnet Routes\" for this device."
@@ -506,7 +504,7 @@ msgstr ""
 msgid "ago"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:411
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:421
 msgid ""
 "and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
 "connections."
index 12882f266d4d005b09950cb721a4955cefe0458d..d7fe8cec25f8db2b9cdbc20776b7c13ae75d0916 100644 (file)
@@ -5,51 +5,51 @@ msgstr "Content-Type: text/plain; charset=UTF-8"
 msgid "(Experimental) Reduce Memory Usage"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:432
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:442
 msgid "1. Select \"Accept Routes\" (to access remote devices)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:433
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:443
 msgid ""
 "2. In \"Advertise Routes\", select your local subnet (to allow remote "
 "devices to access this LAN)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:434
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
 msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Accept Routes"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:441
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
 msgid "Account Settings"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Advertise Exit Node"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid "Advertise Routes"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid ""
 "Advertise subnet routes behind this device. Select from the detected subnets "
 "below or enter custom routes (comma-separated)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "Allow LAN Access"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Allow accepting routes announced by other nodes."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Allow connecting to this device through the SSH function of Tailscale."
 msgstr ""
 
@@ -57,70 +57,71 @@ msgstr ""
 msgid "Allow user access to tailscale"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
-msgid "Applying changes..."
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
 msgid "Are you sure you want to log out?"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:408
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:418
 msgid "Auto Configure Firewall"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
 msgid "Cancel"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:466
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:476
 msgid "Click to Log out account on this device."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
 msgid "Click to get a login URL for this device."
 msgstr ""
 
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:166
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:363
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:243
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:365
 msgid "Collecting data ..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:551
 msgid "Confirm Logout"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:253
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
 msgid "Connection Info"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
 msgid ""
 "Could not open a new tab. Please check if your browser or an extension "
 "blocked the pop-up."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
 msgid "Custom Login Server"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:458
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:468
 msgid "Custom Login Server Auth Key"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Declare this device as an Exit Node."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:554
+msgid "Devices List"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Disable MagicDNS"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid "Disable SNAT"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid ""
 "Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
 "should leave this unchecked."
@@ -130,18 +131,28 @@ msgstr ""
 msgid "Disabled"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:467
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:477
 msgid "Disconnect from Tailscale and expire current node key."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid "Enable Peer Relay"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Enable Tailscale SSH"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid "Enable Web Interface"
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid ""
+"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
+"port open on the router."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226
 msgid "Enabled"
 msgstr ""
@@ -152,14 +163,10 @@ msgid ""
 "performance (set GOGC=10)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:513
 msgid "Error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
-msgid "Error applying settings: %s"
-msgstr ""
-
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 msgid "Error caching DERP region map: %s"
 msgstr ""
@@ -172,72 +179,64 @@ msgstr ""
 msgid "Error reading cached DERP region map: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-msgid "Error saving settings: %s"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:409
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:419
 msgid ""
 "Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
 "features."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid "Exit Node"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid ""
 "Expose a web interface on port 5252 for managing this node over Tailscale."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:430
 msgid "Failed to configure firewall: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:514
 msgid "Failed to get login URL. You may close this tab."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
 msgid "Failed to get login URL: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:505
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
 msgid "Failed to get login URL: Invalid response from server."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:584
-msgid "Failed to save settings: %s"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid "Firewall Mode"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:417
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
 msgid "Firewall configuration applied."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:372
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:374
 msgid "General Settings"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:250
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:261
 msgid "Hostname"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
 msgid "How to enable Site-to-Site?"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:456
 msgid ""
 "If the timeout is displayed, you can refresh the page and click Login again."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
 msgid ""
 "If you are using custom login server but not providing an Auth Key, will "
 "redirect to the login page without pre-filling the key."
@@ -247,7 +246,7 @@ msgstr ""
 msgid "Invalid Date"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
 msgid ""
 "It automatically creates the tailscale interface, sets up firewall zones for "
 "LAN <-> Tailscale forwarding,"
@@ -261,28 +260,28 @@ msgstr ""
 msgid "LOGGED OUT"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:267
 msgid "Last Seen"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:462
 msgid "Leave blank for default Tailscale control plane."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Logging out..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
 msgid "Login"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:465
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:475
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:548
 msgid "Logout"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Logout failed: %s"
 msgstr ""
 
@@ -294,15 +293,11 @@ msgstr ""
 msgid "NOT RUNNING"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:298
-msgid "Network Devices"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
 msgid "No peer devices found."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:377
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:387
 msgid "None"
 msgstr ""
 
@@ -310,38 +305,41 @@ msgstr ""
 msgid "Now"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:252
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:263
 msgid "OS"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Offline"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Online"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:461
 msgid ""
 "Optional: Specify a custom control server URL (e.g., a Headscale instance, "
 "%s)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:459
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
 msgid ""
 "Optional: Specify an authentication key for the custom control server. Leave "
 "blank if not required."
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:378
+msgid "Peer Relay Port"
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:207
 msgid "Please use the login button in the settings below to authenticate."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Please wait."
 msgstr ""
 
@@ -349,26 +347,26 @@ msgstr ""
 msgid "RUNNING"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:254
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:265
 msgid "RX"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
 msgid "Requesting Login URL..."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:485
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
 msgid "Requesting Tailscale login URL... Please wait."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid ""
 "Select an exit node from the list. If enabled, Allow LAN Access is enabled "
 "implicitly."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid ""
 "Select the firewall backend for Tailscale to use. Requires service restart "
 "to take effect."
@@ -381,19 +379,15 @@ msgstr ""
 msgid "Service Status"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
-msgid "Settings"
-msgstr ""
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid "Shields Up"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:249
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
 msgid "Status"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
 msgid "Successfully logged out."
 msgstr ""
 
@@ -405,7 +399,7 @@ msgstr ""
 msgid "TUN Mode"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:255
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:266
 msgid "TX"
 msgstr ""
 
@@ -413,7 +407,7 @@ msgstr ""
 msgid "Tailnet Name"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 #: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
 msgid "Tailscale"
 msgstr ""
@@ -422,7 +416,7 @@ msgstr ""
 msgid "Tailscale Health Check: %s"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:251
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:262
 msgid "Tailscale IP"
 msgstr ""
 
@@ -434,42 +428,46 @@ msgstr ""
 msgid "Tailscale IPv6"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:484
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
 msgid "Tailscale Login"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 msgid ""
 "Tailscale is a mesh VPN solution that makes it easy to connect your devices "
 "securely. This configuration page allows you to manage Tailscale settings on "
 "your OpenWrt device."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
-msgid "Tailscale settings applied successfully."
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:247
+msgid "Tailscale status error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:486
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
 msgid "This can take up to 30 seconds."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:516
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:526
 msgid ""
 "This will disconnect this device from your Tailnet and require you to re-"
 "authenticate."
 msgstr ""
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:379
+msgid ""
+"UDP port for the Peer Relay service. Open this port on your router firewall/"
+"NAT."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:120
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Unknown error"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Use system DNS instead of MagicDNS."
 msgstr ""
 
@@ -477,16 +475,16 @@ msgstr ""
 msgid "Version"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid ""
 "When enabled, blocks all inbound connections from the Tailscale network."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "When using the exit node, access to the local LAN is allowed."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:435
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
 msgid ""
 "[Important] Log in to the Tailscale admin console and manually enable "
 "\"Subnet Routes\" for this device."
@@ -496,7 +494,7 @@ msgstr ""
 msgid "ago"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:411
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:421
 msgid ""
 "and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
 "connections."
index 35592671e0194475552b214aba41b58473dc3cdc..73e1beddb91fd61e20539e35dd38d3ce0eb88275 100644 (file)
@@ -5,52 +5,52 @@ msgstr "Content-Type: text/plain; charset=UTF-8\n"
 msgid "(Experimental) Reduce Memory Usage"
 msgstr "(实验性) 减少内存使用"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:432
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:442
 msgid "1. Select \"Accept Routes\" (to access remote devices)."
 msgstr "1. 勾选 接受路由 (接受远程设备访问)。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:433
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:443
 msgid ""
 "2. In \"Advertise Routes\", select your local subnet (to allow remote "
 "devices to access this LAN)."
 msgstr "2. 在 通告路由 选择本设备的局域网段 (让远程设备能访问你)。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:434
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
 msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)."
 msgstr "3. 点击 自动配置防火墙 (打通防火墙转发)。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Accept Routes"
 msgstr "接受路由"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:441
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
 msgid "Account Settings"
 msgstr "账户设置"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Advertise Exit Node"
 msgstr "通告出口节点"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid "Advertise Routes"
 msgstr "通告路由"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
 msgid ""
 "Advertise subnet routes behind this device. Select from the detected subnets "
 "below or enter custom routes (comma-separated)."
 msgstr ""
 "通告此设备后的子网路由。从下面的子网中选择,或输入自定义路由 (逗号分隔)。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "Allow LAN Access"
 msgstr "允许局域网访问"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
 msgid "Allow accepting routes announced by other nodes."
 msgstr "允许接受由其他节点通告的路由。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Allow connecting to this device through the SSH function of Tailscale."
 msgstr "允许通过 Tailscale 的 SSH 功能连接到此设备。"
 
@@ -58,70 +58,71 @@ msgstr "允许通过 Tailscale 的 SSH 功能连接到此设备。"
 msgid "Allow user access to tailscale"
 msgstr "允许用户访问 Tailscale"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
-msgid "Applying changes..."
-msgstr "正在应用更改..."
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525
 msgid "Are you sure you want to log out?"
 msgstr "您确定要登出吗?"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:408
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:418
 msgid "Auto Configure Firewall"
 msgstr "自动配置防火墙"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
 msgid "Cancel"
 msgstr "取消"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:466
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:476
 msgid "Click to Log out account on this device."
 msgstr "点击以登出此设备上的账户。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455
 msgid "Click to get a login URL for this device."
 msgstr "点击获取此设备的登录 URL。"
 
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:166
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:363
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:243
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:365
 msgid "Collecting data ..."
 msgstr "正在收集数据..."
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:551
 msgid "Confirm Logout"
 msgstr "确认登出"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:253
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264
 msgid "Connection Info"
 msgstr "连接信息"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
 msgid ""
 "Could not open a new tab. Please check if your browser or an extension "
 "blocked the pop-up."
 msgstr "无法打开新标签页。请检查您的浏览器或扩展程序是否阻止了弹出窗口。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:450
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
 msgid "Custom Login Server"
 msgstr "自定义登录服务器"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:458
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:468
 msgid "Custom Login Server Auth Key"
 msgstr "自定义登录服务器认证密钥"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20
 msgid "Declare this device as an Exit Node."
 msgstr "将此设备声明为出口节点。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:554
+msgid "Devices List"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Disable MagicDNS"
 msgstr "禁用 MagicDNS"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid "Disable SNAT"
 msgstr "禁用 SNAT"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
 msgid ""
 "Disable Source NAT (SNAT) for traffic to advertised routes. Most users "
 "should leave this unchecked."
@@ -131,18 +132,28 @@ msgstr "为通告路由的流量禁用源地址转换 (SNAT)。大多数用户
 msgid "Disabled"
 msgstr "已禁用"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:467
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:477
 msgid "Disconnect from Tailscale and expire current node key."
 msgstr "从 Tailscale 断开连接并使当前节点密钥过期。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid "Enable Peer Relay"
+msgstr ""
+
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
 msgid "Enable Tailscale SSH"
 msgstr "启用 Tailscale SSH"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid "Enable Web Interface"
 msgstr "启用 Web 界面"
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+msgid ""
+"Enable this device as a Peer Relay server. Requires a public IP and an UDP "
+"port open on the router."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226
 msgid "Enabled"
 msgstr "已启用"
@@ -153,14 +164,10 @@ msgid ""
 "performance (set GOGC=10)."
 msgstr "启用此选项可以减少内存使用,但可能会牺牲一些性能 (设置 GOGC=10)。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:513
 msgid "Error"
 msgstr "错误"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
-msgid "Error applying settings: %s"
-msgstr "应用设置时出错: %s"
-
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 msgid "Error caching DERP region map: %s"
 msgstr "缓存 DERP 区域地图时出错: %s"
@@ -173,72 +180,64 @@ msgstr "获取 DERP 区域地图时出错: %s"
 msgid "Error reading cached DERP region map: %s"
 msgstr "读取缓存的 DERP 区域地图时出错: %s"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-msgid "Error saving settings: %s"
-msgstr "保存设置时出错: %s"
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:409
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:419
 msgid ""
 "Essential configuration for Subnet Routing (Site-to-Site) and Exit Node "
 "features."
 msgstr "子网路由和出口节点的基本配置。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid "Exit Node"
 msgstr "出口节点"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
 msgid ""
 "Expose a web interface on port 5252 for managing this node over Tailscale."
 msgstr "在端口 5252 上暴露一个 Web 界面,用于通过 Tailscale 管理此节点。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:430
 msgid "Failed to configure firewall: %s"
 msgstr "获取防火墙设置失败: %s"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:514
 msgid "Failed to get login URL. You may close this tab."
 msgstr "获取登录 URL 失败。您可以关闭此标签页。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
 msgid "Failed to get login URL: %s"
 msgstr "获取登录 URL 失败: %s"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:505
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515
 msgid "Failed to get login URL: Invalid response from server."
 msgstr "获取登录 URL 失败: 服务器响应无效。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:584
-msgid "Failed to save settings: %s"
-msgstr "保存设置失败: %s"
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid "Firewall Mode"
 msgstr "防火墙模式"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:417
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
 msgid "Firewall configuration applied."
 msgstr "已应用防火墙配置"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:372
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:374
 msgid "General Settings"
 msgstr "常规设置"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:250
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:261
 msgid "Hostname"
 msgstr "主机名"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437
 msgid "How to enable Site-to-Site?"
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:456
 msgid ""
 "If the timeout is displayed, you can refresh the page and click Login again."
 msgstr "如果显示超时,您可以刷新页面并再次点击登录。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:460
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470
 msgid ""
 "If you are using custom login server but not providing an Auth Key, will "
 "redirect to the login page without pre-filling the key."
@@ -250,7 +249,7 @@ msgstr ""
 msgid "Invalid Date"
 msgstr "无效日期"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:420
 msgid ""
 "It automatically creates the tailscale interface, sets up firewall zones for "
 "LAN <-> Tailscale forwarding,"
@@ -266,28 +265,28 @@ msgstr "刚才"
 msgid "LOGGED OUT"
 msgstr "已登出"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:267
 msgid "Last Seen"
 msgstr "上次在线"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:462
 msgid "Leave blank for default Tailscale control plane."
 msgstr "留空以使用默认的 Tailscale 控制平面。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Logging out..."
 msgstr "正在登出..."
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:454
 msgid "Login"
 msgstr "登录"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:465
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:475
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:548
 msgid "Logout"
 msgstr "登出"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Logout failed: %s"
 msgstr "登出失败: %s"
 
@@ -299,15 +298,11 @@ msgstr "N/A"
 msgid "NOT RUNNING"
 msgstr "未运行"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:298
-msgid "Network Devices"
-msgstr "网络设备"
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:245
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256
 msgid "No peer devices found."
 msgstr "未找到对等设备。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:377
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:387
 msgid "None"
 msgstr ""
 
@@ -315,38 +310,41 @@ msgstr ""
 msgid "Now"
 msgstr "现在"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:252
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:263
 msgid "OS"
 msgstr "操作系统"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Offline"
 msgstr "离线"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:286
 msgid "Online"
 msgstr "在线"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:461
 msgid ""
 "Optional: Specify a custom control server URL (e.g., a Headscale instance, "
 "%s)."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:459
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469
 msgid ""
 "Optional: Specify an authentication key for the custom control server. Leave "
 "blank if not required."
 msgstr "可选:为自定义控制服务器指定一个认证密钥。如果不需要,请留空。"
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:378
+msgid "Peer Relay Port"
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:207
 msgid "Please use the login button in the settings below to authenticate."
 msgstr "请使用下方设置中的登录按钮进行认证。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:563
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:538
 msgid "Please wait."
 msgstr "请稍候。"
 
@@ -354,26 +352,26 @@ msgstr "请稍候。"
 msgid "RUNNING"
 msgstr "正在运行"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:254
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:265
 msgid "RX"
 msgstr "接收"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:498
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504
 msgid "Requesting Login URL..."
 msgstr "正在请求登录 URL..."
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:485
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495
 msgid "Requesting Tailscale login URL... Please wait."
 msgstr "正在请求 Tailscale 登录 URL... 请稍候。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:386
 msgid ""
 "Select an exit node from the list. If enabled, Allow LAN Access is enabled "
 "implicitly."
 msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:18
 msgid ""
 "Select the firewall backend for Tailscale to use. Requires service restart "
 "to take effect."
@@ -386,19 +384,15 @@ msgstr "选择 Tailscale 使用的防火墙后端。需要重启服务才能生
 msgid "Service Status"
 msgstr "服务状态"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:368
-msgid "Settings"
-msgstr "设置"
-
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid "Shields Up"
 msgstr "开启防护 (Shields Up)"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:249
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260
 msgid "Status"
 msgstr "状态"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542
 msgid "Successfully logged out."
 msgstr "登出成功。"
 
@@ -410,7 +404,7 @@ msgstr "未找到 TAILSCALE"
 msgid "TUN Mode"
 msgstr "TUN 模式"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:255
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:266
 msgid "TX"
 msgstr "发送"
 
@@ -418,7 +412,7 @@ msgstr "发送"
 msgid "Tailnet Name"
 msgstr "Tailnet 名称"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 #: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3
 msgid "Tailscale"
 msgstr "Tailscale"
@@ -427,7 +421,7 @@ msgstr "Tailscale"
 msgid "Tailscale Health Check: %s"
 msgstr "Tailscale 健康检查: %s"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:251
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:262
 msgid "Tailscale IP"
 msgstr "Tailscale IP"
 
@@ -439,11 +433,11 @@ msgstr "Tailscale IPv4"
 msgid "Tailscale IPv6"
 msgstr "Tailscale IPv6"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:484
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:494
 msgid "Tailscale Login"
 msgstr "Tailscale 登录"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:339
 msgid ""
 "Tailscale is a mesh VPN solution that makes it easy to connect your devices "
 "securely. This configuration page allows you to manage Tailscale settings on "
@@ -452,31 +446,35 @@ msgstr ""
 "Tailscale 是一个网状 VPN 解决方案,可以轻松地安全连接您的设备。此配置页面允许"
 "您在 OpenWrt 设备上管理 Tailscale 设置。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569
-msgid "Tailscale settings applied successfully."
-msgstr "Tailscale 设置已成功应用。"
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:247
+msgid "Tailscale status error"
+msgstr ""
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:486
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496
 msgid "This can take up to 30 seconds."
 msgstr "此过程最多可能需要 30 秒。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:516
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:526
 msgid ""
 "This will disconnect this device from your Tailnet and require you to re-"
 "authenticate."
 msgstr "这将使此设备从您的 Tailnet 断开连接,并需要您重新进行身份验证。"
 
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:379
+msgid ""
+"UDP port for the Peer Relay service. Open this port on your router firewall/"
+"NAT."
+msgstr ""
+
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:120
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:147
 #: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:509
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:574
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:519
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:545
 msgid "Unknown error"
 msgstr "未知错误"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26
 msgid "Use system DNS instead of MagicDNS."
 msgstr "使用系统 DNS 而不是 MagicDNS。"
 
@@ -484,16 +482,16 @@ msgstr "使用系统 DNS 而不是 MagicDNS。"
 msgid "Version"
 msgstr "版本"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24
 msgid ""
 "When enabled, blocks all inbound connections from the Tailscale network."
 msgstr "启用后,将阻止来自 Tailscale 网络的所有入站连接。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21
 msgid "When using the exit node, access to the local LAN is allowed."
 msgstr "使用出口节点时,允许访问本地局域网。"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:435
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445
 msgid ""
 "[Important] Log in to the Tailscale admin console and manually enable "
 "\"Subnet Routes\" for this device."
@@ -503,7 +501,7 @@ msgstr "【重要】 登录 Tailscale 控制平面,在设备设置中手动授
 msgid "ago"
 msgstr "前"
 
-#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:411
+#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:421
 msgid ""
 "and enables Masquerading and MSS Clamping (MTU fix) to ensure stable "
 "connections."
@@ -515,3 +513,24 @@ msgstr "并启用地址伪装和 MSS钳制确保连接稳定。"
 #~ msgstr ""
 #~ "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例,https://"
 #~ "example.com)。"
+
+#~ msgid "Applying changes..."
+#~ msgstr "正在应用更改..."
+
+#~ msgid "Error applying settings: %s"
+#~ msgstr "应用设置时出错: %s"
+
+#~ msgid "Error saving settings: %s"
+#~ msgstr "保存设置时出错: %s"
+
+#~ msgid "Failed to save settings: %s"
+#~ msgstr "保存设置失败: %s"
+
+#~ msgid "Network Devices"
+#~ msgstr "网络设备"
+
+#~ msgid "Settings"
+#~ msgstr "设置"
+
+#~ msgid "Tailscale settings applied successfully."
+#~ msgstr "Tailscale 设置已成功应用。"
diff --git a/applications/luci-app-tailscale-community/root/etc/init.d/tailscale-settings b/applications/luci-app-tailscale-community/root/etc/init.d/tailscale-settings
new file mode 100644 (file)
index 0000000..abfbb05
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh /etc/rc.common
+
+USE_PROCD=1
+START=99
+STOP=10
+
+NAME=tailscale-settings
+
+# Compare version strings: return 0 (true) if $1 >= $2
+version_gte() {
+       awk -v a="${1%%-*}" -v b="${2%%-*}" 'BEGIN {
+               split(a, av, ".")
+               split(b, bv, ".")
+               for (i=1; i<=3; i++) {
+                       ai = int(av[i]+0); bi = int(bv[i]+0)
+                       if (ai > bi) { exit 0 }
+                       if (ai < bi) { exit 1 }
+               }
+               exit 0
+       }'
+       return $?
+}
+
+apply_settings() {
+       local ts_bin
+       if [ -x /usr/sbin/tailscale ]; then
+               ts_bin=/usr/sbin/tailscale
+       elif [ -x /usr/bin/tailscale ]; then
+               ts_bin=/usr/bin/tailscale
+       else
+               logger -t "$NAME" "tailscale binary not found, skipping settings apply"
+               return 0
+       fi
+
+       config_load tailscale
+
+       local accept_routes advertise_exit_node exit_node exit_node_allow_lan_access
+       local ssh disable_magic_dns shields_up runwebclient nosnat hostname
+       local enable_relay relay_server_port
+
+       config_get accept_routes              settings accept_routes              '0'
+       config_get advertise_exit_node        settings advertise_exit_node        '0'
+       config_get exit_node                  settings exit_node                  ''
+       config_get exit_node_allow_lan_access settings exit_node_allow_lan_access '0'
+       config_get ssh                        settings ssh                        '0'
+       config_get disable_magic_dns          settings disable_magic_dns          '0'
+       config_get shields_up                 settings shields_up                 '0'
+       config_get runwebclient               settings runwebclient               '0'
+       config_get nosnat                     settings nosnat                     '0'
+       config_get hostname                   settings hostname                   ''
+       config_get enable_relay               settings enable_relay               '0'
+       config_get relay_server_port          settings relay_server_port          '40000'
+
+       # Collect advertise_routes UCI list into comma-separated string
+       local routes=""
+       append_route() {
+               if [ -z "$routes" ]; then
+                       routes="$1"
+               else
+                       routes="$routes,$1"
+               fi
+       }
+       config_list_foreach settings advertise_routes append_route
+
+       # Build argument list for 'tailscale set'
+       set -- set
+
+       # --accept-routes
+       [ "$accept_routes" = "1" ] \
+               && set -- "$@" --accept-routes=true \
+               || set -- "$@" --accept-routes=false
+
+       # --advertise-exit-node (only when no exit node is selected)
+       if [ "$advertise_exit_node" = "1" ] && [ -z "$exit_node" ]; then
+               set -- "$@" --advertise-exit-node=true
+       else
+               set -- "$@" --advertise-exit-node=false
+       fi
+
+       # --exit-node-allow-lan-access (only when no exit node is selected)
+       if [ -z "$exit_node" ]; then
+               [ "$exit_node_allow_lan_access" = "1" ] \
+                       && set -- "$@" --exit-node-allow-lan-access=true \
+                       || set -- "$@" --exit-node-allow-lan-access=false
+       fi
+
+       # --ssh
+       [ "$ssh" = "1" ] \
+               && set -- "$@" --ssh=true \
+               || set -- "$@" --ssh=false
+
+       # --accept-dns (inverse of disable_magic_dns)
+       [ "$disable_magic_dns" = "1" ] \
+               && set -- "$@" --accept-dns=false \
+               || set -- "$@" --accept-dns=true
+
+       # --shields-up
+       [ "$shields_up" = "1" ] \
+               && set -- "$@" --shields-up=true \
+               || set -- "$@" --shields-up=false
+
+       # --webclient
+       [ "$runwebclient" = "1" ] \
+               && set -- "$@" --webclient=true \
+               || set -- "$@" --webclient=false
+
+       # --snat-subnet-routes (inverse of nosnat)
+       [ "$nosnat" = "1" ] \
+               && set -- "$@" --snat-subnet-routes=false \
+               || set -- "$@" --snat-subnet-routes=true
+
+       # --advertise-routes (comma-separated, empty clears routes)
+       set -- "$@" "--advertise-routes=$routes"
+
+       # --exit-node (empty clears the exit node)
+       set -- "$@" "--exit-node=$exit_node"
+
+       # when an exit node is configured, always allow LAN access
+       [ -n "$exit_node" ] && set -- "$@" --exit-node-allow-lan-access=true
+
+       # --hostname (only set if non-empty)
+       [ -n "$hostname" ] && set -- "$@" "--hostname=$hostname"
+
+       # --relay-server-port (requires tailscale >= 1.90.5)
+       local ts_ver
+       ts_ver=$("$ts_bin" version 2>/dev/null | head -n1)
+       if version_gte "$ts_ver" "1.90.5"; then
+               if [ "$enable_relay" = "1" ]; then
+                       set -- "$@" "--relay-server-port=${relay_server_port:-40000}"
+               else
+                       set -- "$@" "--relay-server-port="
+               fi
+       fi
+
+       logger -t "$NAME" "Applying settings"
+       "$ts_bin" "$@"
+       local ret=$?
+       [ $ret -eq 0 ] \
+               && logger -t "$NAME" "Settings applied successfully" \
+               || logger -t "$NAME" "Failed to apply settings (exit code: $ret)"
+       return $ret
+}
+
+start_service() {
+       apply_settings
+}
+
+service_triggers() {
+       procd_add_reload_trigger "tailscale"
+}
+
+reload_service() {
+       apply_settings
+}
index f80185ff12b09080d1507edb4d6578cfc335bcbe..ce22368d0295cdb1a4a4a42d3ee4a91067fd4265 100644 (file)
@@ -16,8 +16,7 @@
                                "tailscale": [
                                        "do_login",
                                        "do_logout",
-                                       "setup_firewall",
-                                       "set_settings"
+                                       "setup_firewall"
                                ]
                        },
                        "uci": [ "tailscale" ]
index fe734a47c8ec4d9db7e7b031b12eb00ecf7d1f3c..e9e7e6dc4cb58db38d9646731109705e01ec72ce 100644 (file)
@@ -133,49 +133,6 @@ methods.get_settings = {
        }
 };
 
-methods.set_settings = {
-       args: { form_data: {} },
-       call: function(request) {
-               const form_data = request.args.form_data;
-               if (form_data == null || length(form_data) == 0) {
-                       return { error: 'Missing or invalid form_data parameter. Please provide settings data.' };
-               }
-               let args = ['set'];
-
-               push(args,'--accept-routes=' + (form_data.accept_routes == '1'));
-               push(args,'--advertise-exit-node=' + ((form_data.advertise_exit_node == '1')&&(form_data.exit_node == "")));
-               if (form_data.exit_node == "") push(args,'--exit-node-allow-lan-access=' + (form_data.exit_node_allow_lan_access == '1'));
-               push(args,'--ssh=' + (form_data.ssh == '1'));
-               push(args,'--accept-dns=' + (form_data.disable_magic_dns != '1'));
-               push(args,'--shields-up=' + (form_data.shields_up == '1'));
-               push(args,'--webclient=' + (form_data.runwebclient == '1'));
-               push(args,'--snat-subnet-routes=' + (form_data.nosnat != '1'));
-               push(args,'--advertise-routes ' + (shell_quote(join(',',form_data.advertise_routes)) || '\"\"'));
-               push(args,'--exit-node=' + (shell_quote(form_data.exit_node) || '\"\"'));
-               if (form_data.exit_node != "") push(args,' --exit-node-allow-lan-access=true');
-               push(args,'--hostname ' + (shell_quote(form_data.hostname) || '\"\"'));
-
-               let cmd_array = 'tailscale '+join(' ', args);
-               let set_result = exec(cmd_array);
-               if (set_result.code != 0) {
-                       return { error: 'Failed to apply node settings: ' + set_result.stderr };
-               }
-
-               uci.load('tailscale');
-               for (let key in form_data) {
-                       uci.set('tailscale', 'settings', key, form_data[key]);
-               }
-               uci.save('tailscale');
-               uci.commit('tailscale');
-
-               // process reduce memory https://github.com/GuNanOvO/openwrt-tailscale
-               // some new versions of Tailscale may not work well with this method
-               //if (form_data.daemon_mtu != "" || form_data.daemon_reduce_memory != "") {
-               //      popen('/bin/sh -c ". ' + env_script_path + ' && /etc/init.d/tailscale restart" &');
-               //}
-               return { success: true };
-       }
-};
 
 methods.do_login = {
        args: { form_data: {} },
git clone https://git.99rst.org/PROJECT