diff --git a/luci-app-tailscale-community/Makefile b/luci-app-tailscale-community/Makefile deleted file mode 100644 index d0ba87cb1..000000000 --- a/luci-app-tailscale-community/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -include $(TOPDIR)/rules.mk - -LUCI_TITLE:=LuCI support for Tailscale -LUCI_URL:=https://github.com/tokisaki-galaxy/luci-app-tailscale-community -PKG_DESCRIPTION:=Provides a LuCI Web management interface for Tailscale, allowing viewing status, configuring nodes and daemons. -PKG_MAINTAINER:=Tokisaki-Galaxy -LUCI_DEPENDS:=+tailscale +ip +luci-base -LUCI_PKGARCH:=all - -include $(TOPDIR)/feeds/luci/luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js b/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js deleted file mode 100644 index b7f2d7bdc..000000000 --- a/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js +++ /dev/null @@ -1,590 +0,0 @@ -'use strict'; -'require view'; -'require form'; -'require rpc'; -'require ui'; -'require uci'; -'require tools.widgets as widgets'; - -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' }); -const callSetupFirewall = rpc.declare({ object: 'tailscale', method: 'setup_firewall' }); -let map; - -const tailscaleSettingsConf = [ - [form.ListValue, 'fw_mode', _('Firewall Mode'), _('Select the firewall backend for Tailscale to use. Requires service restart to take effect.'), {values: ['nftables','iptables'],rmempty: false}], - [form.Flag, 'accept_routes', _('Accept Routes'), _('Allow accepting routes announced by other nodes.'), { rmempty: false }], - [form.Flag, 'advertise_exit_node', _('Advertise Exit Node'), _('Declare this device as an Exit Node.'), { rmempty: false }], - [form.Flag, 'exit_node_allow_lan_access', _('Allow LAN Access'), _('When using the exit node, access to the local LAN is allowed.'), { rmempty: false }], - [form.Flag, 'runwebclient', _('Enable Web Interface'), _('Expose a web interface on port 5252 for managing this node over Tailscale.'), { rmempty: false }], - [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 }] -]; - -const accountConf = []; // dynamic created in render function - -const daemonConf = [ - //[form.Value, 'daemon_mtu', _('Daemon MTU'), _('Set a custom MTU for the Tailscale daemon. Leave blank to use the default value.'), { datatype: 'uinteger', placeholder: '1280' }, { rmempty: false }], - [form.Flag, 'daemon_reduce_memory', _('(Experimental) Reduce Memory Usage'), _('Enabling this option can reduce memory usage, but it may sacrifice some performance (set GOGC=10).'), { rmempty: false }] -]; - -const derpMapUrl = 'https://controlplane.tailscale.com/derpmap/default'; -let regionCodeMap = {}; - -// this function copy from luci-app-frpc. thx -function setParams(o, params) { - if (!params) return; - - for (const [key, val] of Object.entries(params)) { - if (key === 'values') { - [].concat(val).forEach(v => - o.value.apply(o, Array.isArray(v) ? v : [v]) - ); - } else if (key === 'depends') { - const arr = Array.isArray(val) ? val : [val]; - o.deps = arr.map(dep => Object.assign({}, ...o.deps, dep)); - } else { - o[key] = val; - } - } - - if (params.datatype === 'bool') - Object.assign(o, { enabled: 'true', disabled: 'false' }); -} - -// this function copy from luci-app-frpc. thx -function defTabOpts(s, t, opts, params) { - for (let i = 0; i < opts.length; i++) { - const opt = opts[i]; - const o = s.taboption(t, opt[0], opt[1], opt[2], opt[3]); - setParams(o, opt[4]); - setParams(o, params); - } -} - -function getRunningStatus() { - return L.resolveDefault(callGetStatus(), { running: false }).then(function (res) { - return res; - }); -} - -function formatBytes(bytes) { - const bytes_num = parseInt(bytes, 10); - if (isNaN(bytes_num) || bytes_num === 0) return '-'; - const k = 1000; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes_num) / Math.log(k)); - return parseFloat((bytes_num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -function formatLastSeen(d) { - if (!d) return _('N/A'); - if (d === '0001-01-01T00:00:00Z') return _('Now'); - const t = new Date(d); - if (isNaN(t)) return _('Invalid Date'); - const diff = (Date.now() - t) / 1000; - if (diff < 0) return t.toLocaleString(); - if (diff < 60) return _('Just now'); - - const mins = diff / 60, hrs = mins / 60, days = hrs / 24; - const fmt = (n, s, p) => `${Math.floor(n)} ${Math.floor(n) === 1 ? _(s) : _(p)} ${_('ago')}`; - - if (mins < 60) return fmt(mins, 'minute', 'minutes'); - if (hrs < 24) return fmt(hrs, 'hour', 'hours'); - if (days < 30) return fmt(days, 'day', 'days'); - - return t.toISOString().slice(0, 10); -} - -async function initializeRegionMap() { - const cacheKey = 'tailscale_derp_map_cache'; - const ttl = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds - - try { - const cachedItem = localStorage.getItem(cacheKey); - if (cachedItem) { - const cached = JSON.parse(cachedItem); - // Check if the cached data is still valid (not expired) - if (Date.now() - cached.timestamp < ttl) { - regionCodeMap = cached.data; - return; - } - } - } catch (e) { - ui.addTimeLimitedNotification(null, [ E('p', _('Error reading cached DERP region map: %s').format(e.message || _('Unknown error'))) ], 7000, 'error'); - } - - // If no valid cache, fetch from the network - try { - const response = await fetch(derpMapUrl); - if (!response.ok) { - return; - } - const data = await response.json(); - const newRegionMap = {}; - for (const regionId in data.Regions) { - const region = data.Regions[regionId]; - const code = (region.RegionCode || '').toLowerCase(); - const name = region.RegionName || region.RegionCode || `Region ${regionId}`; - newRegionMap[code] = name; - } - regionCodeMap = newRegionMap; - - // Save the newly fetched data to the cache - try { - const itemToCache = { - timestamp: Date.now(), - data: regionCodeMap - }; - localStorage.setItem(cacheKey, JSON.stringify(itemToCache)); - } catch (e) { - ui.addTimeLimitedNotification(null, [ E('p', _('Error caching DERP region map: %s').format(e.message || _('Unknown error'))) ], 7000, 'error'); - } - } catch (error) { - ui.addTimeLimitedNotification(null, [ E('p', _('Error fetching DERP region map: %s').format(error.message || _('Unknown error'))) ], 7000, 'error'); - } -} - -function formatConnectionInfo(info) { - if (!info) { return '-'; } - if (typeof info === 'string' && info.length === 3) { - const lowerCaseInfo = info.toLowerCase(); - return regionCodeMap[lowerCaseInfo] || info; - } - return info; -} - -function renderStatus(status) { - // If status object is not yet available, show a loading message. - if (!status || !status.hasOwnProperty('status')) { - return E('em', {}, _('Collecting data ...')); - } - - const notificationId = 'tailscale_health_notification'; - let notificationElement = document.getElementById(notificationId); - if (status.health != '') { - const message = _('Tailscale Health Check: %s').format(status.health); - if (notificationElement) { - notificationElement.textContent = message; - } - else { - let newNotificationContent = E('p', { 'id': notificationId }, message); - ui.addNotification(null, newNotificationContent, 'info'); - } - }else{ - try{ - notificationElement.parentNode.parentNode.remove(); - }catch(e){} - } - - if (Object.keys(regionCodeMap).length === 0) { - initializeRegionMap(); - } - - // --- Part 1: Handle non-running states --- - - // State: Tailscale binary not found. - if (status.status == 'not_installed') { - return E('dl', { 'class': 'cbi-value' }, [ - E('dt', {}, _('Service Status')), - E('dd', {}, E('span', { 'style': 'color:red;' }, E('strong', {}, _('TAILSCALE NOT FOUND')))) - ]); - } - - // State: Logged out, requires user action. - if (status.status == 'logout') { - return E('dl', { 'class': 'cbi-value' }, [ - E('dt', {}, _('Service Status')), - E('dd', {}, [ - E('span', { 'style': 'color:orange;' }, E('strong', {}, _('LOGGED OUT'))), - E('br'), - E('span', {}, _('Please use the login button in the settings below to authenticate.')) - ]) - ]); - } - - // State: Service is installed but not running. - if (status.status != 'running') { - return E('dl', { 'class': 'cbi-value' }, [ - E('dt', {}, _('Service Status')), - E('dd', {}, E('span', { 'style': 'color:red;' }, E('strong', {}, _('NOT RUNNING')))) - ]); - } - - // --- Part 2: Render the full status display for a running service --- - - // A helper array to define the data for the main status table. - const statusData = [ - { label: _('Service Status'), value: E('span', { 'style': 'color:green;' }, E('strong', {}, _('RUNNING'))) }, - { label: _('Version'), value: status.version || 'N/A' }, - { label: _('TUN Mode'), value: status.TUNMode ? _('Enabled') : _('Disabled') }, - { label: _('Tailscale IPv4'), value: status.ipv4 || 'N/A' }, - { label: _('Tailscale IPv6'), value: status.ipv6 || 'N/A' }, - { label: _('Tailnet Name'), value: status.domain_name || 'N/A' } - ]; - - // Build the horizontal status table using the data array. - const statusTable = E('table', { 'style': 'width: 100%; border-spacing: 0 5px;' }, [ - E('tr', {}, statusData.map(item => E('td', { 'style': 'padding-right: 20px;' }, E('strong', {}, item.label)))), - E('tr', {}, statusData.map(item => E('td', { 'style': 'padding-right: 20px;' }, item.value))) - ]); - - // --- Part 3: Render the Peers/Network Devices table --- - - const peers = status.peers; - let peersContent; - - 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)) - ]); - }) - ]); - } - - // 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 - ]) - ]); -} - -return view.extend({ - load() { - return Promise.all([ - L.resolveDefault(callGetStatus(), { running: '', peers: [] }), - L.resolveDefault(callGetSettings(), { accept_routes: false }), - L.resolveDefault(callGetSubroutes(), { routes: [] }) - ]) - .then(function([status, settings_from_rpc, subroutes]) { - return uci.load('tailscale').then(function() { - if (uci.get('tailscale', 'settings') === null) { - // No existing settings found; initialize UCI with RPC settings - uci.add('tailscale', 'settings', 'settings'); - uci.set('tailscale', 'settings', 'fw_mode', 'nftables'); - uci.set('tailscale', 'settings', 'accept_routes', (settings_from_rpc.accept_routes ? '1' : '0')); - uci.set('tailscale', 'settings', 'advertise_exit_node', ((settings_from_rpc.advertise_exit_node || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'advertise_routes', (settings_from_rpc.advertise_routes || []).join(', ')); - uci.set('tailscale', 'settings', 'exit_node', settings_from_rpc.exit_node || ''); - uci.set('tailscale', 'settings', 'exit_node_allow_lan_access', ((settings_from_rpc.exit_node_allow_lan_access || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'ssh', ((settings_from_rpc.ssh || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'shields_up', ((settings_from_rpc.shields_up || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'runwebclient', ((settings_from_rpc.runwebclient || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'nosnat', ((settings_from_rpc.nosnat || false) ? '1' : '0')); - uci.set('tailscale', 'settings', 'disable_magic_dns', ((settings_from_rpc.disable_magic_dns || false) ? '1' : '0')); - - uci.set('tailscale', 'settings', 'daemon_reduce_memory', '0'); - uci.set('tailscale', 'settings', 'daemon_mtu', ''); - return uci.save(); - } - }).then(function() { - return [status, settings_from_rpc, subroutes]; - }); - }); - }, - - render ([status = {}, settings = {}, subroutes_obj]) { - const subroutes = (subroutes_obj && subroutes_obj.routes) ? subroutes_obj.routes : []; - - let s; - map = new form.Map('tailscale', _('Tailscale'), _('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.')); - - s = map.section(form.NamedSection, '_status'); - s.anonymous = true; - s.render = function (section_id) { - L.Poll.add( - function () { - return getRunningStatus().then(function (res) { - const view = document.getElementById("service_status_display"); - if (view) { - const content = renderStatus(res); - view.replaceChildren(content); - } - - // 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'); } - }); - }, 10); - - return E('div', { 'id': 'service_status_display', 'class': 'cbi-value' }, - _('Collecting data ...') - ); - } - - // Bind settings to the 'settings' section of uci - s = map.section(form.NamedSection, 'settings', 'settings', _('Settings')); - s.dynamic = true; - - // Create the "General Settings" tab and apply tailscaleSettingsConf - s.tab('general', _('General Settings')); - - defTabOpts(s, 'general', tailscaleSettingsConf, { optional: false }); - - 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) { - Object.values(status.peers).forEach(function(peer) { - if (peer.exit_node_option) { - const primaryIp = peer.ip.split('
')[0]; - const label = peer.hostname ? `${peer.hostname} (${primaryIp})` : primaryIp; - en.value(primaryIp, label); - } - }); - } - en.rmempty = true; - en.cfgvalue = function(section_id) { - if (status && status.status === 'running' && status.peers) { - for (const id in status.peers) { - if (status.peers[id].exit_node) { - return status.peers[id].ip.split('
')[0]; - } - } - return ''; - } - return uci.get('tailscale', 'settings', 'exit_node') || ''; - }; - - const o = s.taboption('general', form.DynamicList, 'advertise_routes', _('Advertise Routes'),_('Advertise subnet routes behind this device. Select from the detected subnets below or enter custom routes (comma-separated).')); - if (subroutes.length > 0) { - subroutes.forEach(function(subnet) { - o.value(subnet, subnet); - }); - } - o.rmempty = true; - - const fwBtn = s.taboption('general', form.Button, '_setup_firewall', _('Auto Configure Firewall')); - fwBtn.description = _('Essential configuration for Subnet Routing (Site-to-Site) and Exit Node features.') - +'
'+_('It automatically creates the tailscale interface, sets up firewall zones for LAN <-> Tailscale forwarding,') - +'
'+_('and enables Masquerading and MSS Clamping (MTU fix) to ensure stable connections.'); - fwBtn.inputstyle = 'action'; - fwBtn.onclick = function() { - const btn = this; - btn.disabled = true; - return callSetupFirewall().then(function(res) { - const msg = res?.message || _('Firewall configuration applied.'); - ui.addNotification(null, E('p', {}, msg), 'info'); - }).catch(function(err) { - ui.addNotification(null, E('p', {}, _('Failed to configure firewall: %s').format(err?.message || err || 'Unknown error')), 'error'); - }).finally(function() { - btn.disabled = false; - }); - }; - - const helpTitle = s.taboption('general', form.DummyValue, '_help_title'); - helpTitle.title = _('How to enable Site-to-Site?'); - helpTitle.render = function() { - return E('div', { 'class': 'cbi-value', 'style': 'margin-top: 1em; border-top: 1px font-weight: bold;' }, [ - E('label', { 'class': 'cbi-value-title' }, this.title), - E('div', { 'class': 'cbi-value-field', 'style': 'line-height: 1.6em; font-size: 95%; color: #555;' }, [ - _('1. Select "Accept Routes" (to access remote devices).'), E('br'), - _('2. In "Advertise Routes", select your local subnet (to allow remote devices to access this LAN).'), E('br'), - _('3. Click "Auto Configure Firewall" (to allow traffic forwarding).'), E('br'), - E('strong', { 'style': 'color: #d9534f;' }, _('[Important] Log in to the Tailscale admin console and manually enable "Subnet Routes" for this device.')) - ]) - ]); - }; - - // Create the account settings - s.tab('account', _('Account Settings')); - defTabOpts(s, 'account', accountConf, { optional: false }); - - const loginBtn = s.taboption('account', form.Button, '_login', _('Login'), - _('Click to get a login URL for this device.') - +'
'+_('If the timeout is displayed, you can refresh the page and click Login again.')); - loginBtn.inputstyle = 'apply'; - - const customLoginUrl = s.taboption('account', form.Value, 'custom_login_url', - _('Custom Login Server'), - _('Optional: Specify a custom control server URL (e.g., a Headscale instance, %s).'.format('https://example.com')) - +'
'+_('Leave blank for default Tailscale control plane.') - ); - customLoginUrl.placeholder = ''; - customLoginUrl.rmempty = true; - - const customLoginAuthKey = s.taboption('account', form.Value, 'custom_login_AuthKey', - _('Custom Login Server Auth Key'), - _('Optional: Specify an authentication key for the custom control server. Leave blank if not required.') - +'
'+_('If you are using custom login server but not providing an Auth Key, will redirect to the login page without pre-filling the key.') - ); - customLoginAuthKey.placeholder = ''; - customLoginAuthKey.rmempty = true; - - const logoutBtn = s.taboption('account', form.Button, '_logout', _('Logout'), - _('Click to Log out account on this device.') - +'
'+_('Disconnect from Tailscale and expire current node key.')); - logoutBtn.inputstyle = 'apply'; - logoutBtn.id = 'tailscale_logout_btn'; - - loginBtn.onclick = function() { - const customServerInput = document.getElementById('widget.cbid.tailscale.settings.custom_login_url'); - const customServer = customServerInput ? customServerInput.value : ''; - const customserverAuthInput = document.getElementById('widget.cbid.tailscale.settings.custom_login_AuthKey'); - const customServerAuth = customserverAuthInput ? customserverAuthInput.value : ''; - const loginWindow = window.open('', '_blank'); - if (!loginWindow) { - ui.addTimeLimitedNotification(null, [ E('p', _('Could not open a new tab. Please check if your browser or an extension blocked the pop-up.')) ], 10000, 'error'); - return; - } - // Display a prompt message in the new window - const doc = loginWindow.document; - doc.body.innerHTML = - '

' + _('Tailscale Login') + '

' + - '

' + _('Requesting Tailscale login URL... Please wait.') + '

' + - '

' + _('This can take up to 30 seconds.') + '

'; - - ui.showModal(_('Requesting Login URL...'), E('em', {}, _('Please wait.'))); - const payload = { - loginserver: customServer || '', - loginserver_authkey: customServerAuth || '' - }; - // Show a "loading" modal and execute the asynchronous RPC call - ui.showModal(_('Requesting Login URL...'), E('em', {}, _('Please wait.'))); - return callDoLogin(payload).then(function(res) { - ui.hideModal(); - if (res && res.url) { - // After successfully obtaining the URL, redirect the previously opened tab - loginWindow.location.href = res.url; - } else { - // If it fails, inform the user and they can close the new tab - doc.body.innerHTML = - '

' + _('Error') + '

' + - '

' + _('Failed to get login URL. You may close this tab.') + '

'; - ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: Invalid response from server.')) ], 7000, 'error'); - } - }).catch(function(err) { - ui.hideModal(); - ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: %s').format(err.message || _('Unknown error'))) ], 7000, 'error'); - }); - }; - - logoutBtn.onclick = function() { - const confirmationContent = E([ - E('p', {}, _('Are you sure you want to log out?') - +'
'+_('This will disconnect this device from your Tailnet and require you to re-authenticate.')), - - E('div', { 'style': 'text-align: right; margin-top: 1em;' }, [ - E('button', { - 'class': 'cbi-button', - 'click': ui.hideModal - }, _('Cancel')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-negative', - 'click': function() { - ui.hideModal(); - ui.showModal(_('Logging out...'), E('em', {}, _('Please wait.'))); - - return callDoLogout().then(function(res) { - ui.hideModal(); - ui.addTimeLimitedNotification(null, [ E('p', _('Successfully logged out.')) ], 5000, 'info'); - }).catch(function(err) { - ui.hideModal(); - ui.addTimeLimitedNotification(null, [ E('p', _('Logout failed: %s').format(err.message || _('Unknown error'))) ], 7000, 'error'); - }); - } - }, _('Logout')) - ]) - ]); - ui.showModal(_('Confirm Logout'), confirmationContent); - }; - - // Create the "Daemon Settings" tab and apply daemonConf - //s.tab('daemon', _('Daemon Settings')); - //defTabOpts(s, 'daemon', daemonConf, { optional: false }); - - return map.render(); - }, - - // The handleSaveApply function is executed after clicking "Save & Apply" - handleSaveApply(ev) { - 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'); - }); - }, - - handleSave: null, - handleReset: null -}); diff --git a/luci-app-tailscale-community/po/templates/community.pot b/luci-app-tailscale-community/po/templates/community.pot deleted file mode 100644 index 12882f266..000000000 --- a/luci-app-tailscale-community/po/templates/community.pot +++ /dev/null @@ -1,503 +0,0 @@ -msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:34 -msgid "(Experimental) Reduce Memory Usage" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:432 -msgid "1. Select \"Accept Routes\" (to access remote devices)." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:433 -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 -msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 -msgid "Accept Routes" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:441 -msgid "Account Settings" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 -msgid "Advertise Exit Node" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400 -msgid "Advertise Routes" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400 -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 -msgid "Allow LAN Access" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 -msgid "Allow accepting routes announced by other nodes." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 -msgid "Allow connecting to this device through the SSH function of Tailscale." -msgstr "" - -#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3 -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 -msgid "Are you sure you want to log out?" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:408 -msgid "Auto Configure Firewall" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 -msgid "Cancel" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:466 -msgid "Click to Log out account on this device." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 -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 -msgid "Collecting data ..." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 -msgid "Confirm Logout" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:253 -msgid "Connection Info" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 -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 -msgid "Custom Login Server" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:458 -msgid "Custom Login Server Auth Key" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 -msgid "Declare this device as an Exit Node." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 -msgid "Disable MagicDNS" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 -msgid "Disable SNAT" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 -msgid "" -"Disable Source NAT (SNAT) for traffic to advertised routes. Most users " -"should leave this unchecked." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "Disabled" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:467 -msgid "Disconnect from Tailscale and expire current node key." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 -msgid "Enable Tailscale SSH" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 -msgid "Enable Web Interface" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "Enabled" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:34 -msgid "" -"Enabling this option can reduce memory usage, but it may sacrifice some " -"performance (set GOGC=10)." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 -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 "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 -msgid "Error fetching DERP region map: %s" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:120 -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 -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 -msgid "Exit Node" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 -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 -msgid "Failed to configure firewall: %s" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 -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 -msgid "Failed to get login URL: %s" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:505 -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 -msgid "Firewall Mode" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:417 -msgid "Firewall configuration applied." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:372 -msgid "General Settings" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:250 -msgid "Hostname" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427 -msgid "How to enable Site-to-Site?" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 -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 -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." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 -msgid "Invalid Date" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410 -msgid "" -"It automatically creates the tailscale interface, sets up firewall zones for " -"LAN <-> Tailscale forwarding," -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93 -msgid "Just now" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:205 -msgid "LOGGED OUT" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256 -msgid "Last Seen" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 -msgid "Leave blank for default Tailscale control plane." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528 -msgid "Logging out..." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444 -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 -msgid "Logout" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535 -msgid "Logout failed: %s" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:87 -msgid "N/A" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:216 -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 -msgid "No peer devices found." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:377 -msgid "None" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:88 -msgid "Now" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:252 -msgid "OS" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 -msgid "Offline" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 -msgid "Online" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451 -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 -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: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 -msgid "Please wait." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:224 -msgid "RUNNING" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:254 -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 -msgid "Requesting Login URL..." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:485 -msgid "Requesting Tailscale login URL... Please wait." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376 -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 -msgid "" -"Select the firewall backend for Tailscale to use. Requires service restart " -"to take effect." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:195 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:203 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:215 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:224 -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 -msgid "Shields Up" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:249 -msgid "Status" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532 -msgid "Successfully logged out." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:196 -msgid "TAILSCALE NOT FOUND" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "TUN Mode" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:255 -msgid "TX" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 -msgid "Tailnet Name" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342 -#: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3 -msgid "Tailscale" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:172 -msgid "Tailscale Health Check: %s" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:251 -msgid "Tailscale IP" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 -msgid "Tailscale IPv4" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 -msgid "Tailscale IPv6" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:484 -msgid "Tailscale Login" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342 -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." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:486 -msgid "This can take up to 30 seconds." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:516 -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: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 -msgid "Unknown error" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 -msgid "Use system DNS instead of MagicDNS." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:225 -msgid "Version" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 -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 -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 -msgid "" -"[Important] Log in to the Tailscale admin console and manually enable " -"\"Subnet Routes\" for this device." -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96 -msgid "ago" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:411 -msgid "" -"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " -"connections." -msgstr "" diff --git a/luci-app-tailscale-community/po/zh-cn b/luci-app-tailscale-community/po/zh-cn deleted file mode 120000 index 8d69574dd..000000000 --- a/luci-app-tailscale-community/po/zh-cn +++ /dev/null @@ -1 +0,0 @@ -zh_Hans \ No newline at end of file diff --git a/luci-app-tailscale-community/po/zh_Hans/community.po b/luci-app-tailscale-community/po/zh_Hans/community.po deleted file mode 100644 index 6e108e6d4..000000000 --- a/luci-app-tailscale-community/po/zh_Hans/community.po +++ /dev/null @@ -1,510 +0,0 @@ -msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8\n" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:34 -msgid "(Experimental) Reduce Memory Usage" -msgstr "(实验性) 减少内存使用" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:432 -msgid "1. Select \"Accept Routes\" (to access remote devices)." -msgstr "1. 勾选 接受路由 (接受远程设备访问)。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:433 -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 -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 -msgid "Accept Routes" -msgstr "接受路由" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:441 -msgid "Account Settings" -msgstr "账户设置" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 -msgid "Advertise Exit Node" -msgstr "通告出口节点" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400 -msgid "Advertise Routes" -msgstr "通告路由" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:400 -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 -msgid "Allow LAN Access" -msgstr "允许局域网访问" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 -msgid "Allow accepting routes announced by other nodes." -msgstr "允许接受由其他节点通告的路由。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 -msgid "Allow connecting to this device through the SSH function of Tailscale." -msgstr "允许通过 Tailscale 的 SSH 功能连接到此设备。" - -#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3 -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 -msgid "Are you sure you want to log out?" -msgstr "您确定要登出吗?" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:408 -msgid "Auto Configure Firewall" -msgstr "自动配置防火墙" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 -msgid "Cancel" -msgstr "取消" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:466 -msgid "Click to Log out account on this device." -msgstr "点击以登出此设备上的账户。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 -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 -msgid "Collecting data ..." -msgstr "正在收集数据..." - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 -msgid "Confirm Logout" -msgstr "确认登出" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:253 -msgid "Connection Info" -msgstr "连接信息" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 -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 -msgid "Custom Login Server" -msgstr "自定义登录服务器" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:458 -msgid "Custom Login Server Auth Key" -msgstr "自定义登录服务器认证密钥" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 -msgid "Declare this device as an Exit Node." -msgstr "将此设备声明为出口节点。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 -msgid "Disable MagicDNS" -msgstr "禁用 MagicDNS" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 -msgid "Disable SNAT" -msgstr "禁用 SNAT" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 -msgid "" -"Disable Source NAT (SNAT) for traffic to advertised routes. Most users " -"should leave this unchecked." -msgstr "为通告路由的流量禁用源地址转换 (SNAT)。大多数用户应保持此项不勾选。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "Disabled" -msgstr "已禁用" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:467 -msgid "Disconnect from Tailscale and expire current node key." -msgstr "从 Tailscale 断开连接并使当前节点密钥过期。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 -msgid "Enable Tailscale SSH" -msgstr "启用 Tailscale SSH" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 -msgid "Enable Web Interface" -msgstr "启用 Web 界面" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "Enabled" -msgstr "已启用" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:34 -msgid "" -"Enabling this option can reduce memory usage, but it may sacrifice some " -"performance (set GOGC=10)." -msgstr "启用此选项可以减少内存使用,但可能会牺牲一些性能 (设置 GOGC=10)。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 -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" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 -msgid "Error fetching DERP region map: %s" -msgstr "获取 DERP 区域地图时出错: %s" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:120 -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 -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 -msgid "Exit Node" -msgstr "出口节点" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 -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 -msgid "Failed to configure firewall: %s" -msgstr "获取防火墙设置失败: %s" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 -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 -msgid "Failed to get login URL: %s" -msgstr "获取登录 URL 失败: %s" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:505 -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 -msgid "Firewall Mode" -msgstr "防火墙模式" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:417 -msgid "Firewall configuration applied." -msgstr "已应用防火墙配置" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:372 -msgid "General Settings" -msgstr "常规设置" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:250 -msgid "Hostname" -msgstr "主机名" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:427 -msgid "How to enable Site-to-Site?" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 -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 -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." -msgstr "" -"如果您使用自定义登录服务器但未提供认证密钥,将重定向到登录页面而不会预先填充" -"密钥。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 -msgid "Invalid Date" -msgstr "无效日期" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:410 -msgid "" -"It automatically creates the tailscale interface, sets up firewall zones for " -"LAN <-> Tailscale forwarding," -msgstr "" -"子网路由和出口节点的基本配置。会自动tailscale接口,设置防火墙区域LAN <-> " -"Tailscale转发" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93 -msgid "Just now" -msgstr "刚才" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:205 -msgid "LOGGED OUT" -msgstr "已登出" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:256 -msgid "Last Seen" -msgstr "上次在线" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 -msgid "Leave blank for default Tailscale control plane." -msgstr "留空以使用默认的 Tailscale 控制平面。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:528 -msgid "Logging out..." -msgstr "正在登出..." - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:444 -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 -msgid "Logout" -msgstr "登出" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:535 -msgid "Logout failed: %s" -msgstr "登出失败: %s" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:87 -msgid "N/A" -msgstr "N/A" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:216 -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 -msgid "No peer devices found." -msgstr "未找到对等设备。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:377 -msgid "None" -msgstr "" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:88 -msgid "Now" -msgstr "现在" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:252 -msgid "OS" -msgstr "操作系统" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 -msgid "Offline" -msgstr "离线" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 -msgid "Online" -msgstr "在线" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:451 -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 -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: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 -msgid "Please wait." -msgstr "请稍候。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:224 -msgid "RUNNING" -msgstr "正在运行" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:254 -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 -msgid "Requesting Login URL..." -msgstr "正在请求登录 URL..." - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:485 -msgid "Requesting Tailscale login URL... Please wait." -msgstr "正在请求 Tailscale 登录 URL... 请稍候。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:376 -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 -msgid "" -"Select the firewall backend for Tailscale to use. Requires service restart " -"to take effect." -msgstr "选择 Tailscale 使用的防火墙后端。需要重启服务才能生效。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:195 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:203 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:215 -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:224 -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 -msgid "Shields Up" -msgstr "开启防护 (Shields Up)" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:249 -msgid "Status" -msgstr "状态" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:532 -msgid "Successfully logged out." -msgstr "登出成功。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:196 -msgid "TAILSCALE NOT FOUND" -msgstr "未找到 TAILSCALE" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:226 -msgid "TUN Mode" -msgstr "TUN 模式" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:255 -msgid "TX" -msgstr "发送" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 -msgid "Tailnet Name" -msgstr "Tailnet 名称" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342 -#: applications/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json:3 -msgid "Tailscale" -msgstr "Tailscale" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:172 -msgid "Tailscale Health Check: %s" -msgstr "Tailscale 健康检查: %s" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:251 -msgid "Tailscale IP" -msgstr "Tailscale IP" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 -msgid "Tailscale IPv4" -msgstr "Tailscale IPv4" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 -msgid "Tailscale IPv6" -msgstr "Tailscale IPv6" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:484 -msgid "Tailscale Login" -msgstr "Tailscale 登录" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:342 -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 "" -"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:486 -msgid "This can take up to 30 seconds." -msgstr "此过程最多可能需要 30 秒。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:516 -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: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 -msgid "Unknown error" -msgstr "未知错误" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 -msgid "Use system DNS instead of MagicDNS." -msgstr "使用系统 DNS 而不是 MagicDNS。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:225 -msgid "Version" -msgstr "版本" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 -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 -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 -msgid "" -"[Important] Log in to the Tailscale admin console and manually enable " -"\"Subnet Routes\" for this device." -msgstr "【重要】 登录 Tailscale 控制平面,在设备设置中手动授权子网路由。" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96 -msgid "ago" -msgstr "前" - -#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:411 -msgid "" -"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " -"connections." -msgstr "并启用地址伪装和 MSS钳制确保连接稳定。" diff --git a/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json b/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json deleted file mode 100644 index 70c90695c..000000000 --- a/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "admin/services/tailscale": { - "title": "Tailscale", - "order": 90, - "action": { - "type": "view", - "path": "tailscale" - } - } -} diff --git a/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json b/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json deleted file mode 100644 index f80185ff1..000000000 --- a/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "luci-app-tailscale-community": { - "description": "Allow user access to tailscale", - "read": { - "ubus": { - "tailscale": [ - "get_status", - "get_settings", - "get_subroutes" - ] - }, - "uci": [ "tailscale" ] - }, - "write": { - "ubus": { - "tailscale": [ - "do_login", - "do_logout", - "setup_firewall", - "set_settings" - ] - }, - "uci": [ "tailscale" ] - } - } -} diff --git a/luci-app-tailscale-community/root/usr/share/rpcd/ucode/tailscale.uc b/luci-app-tailscale-community/root/usr/share/rpcd/ucode/tailscale.uc deleted file mode 100644 index fe734a47c..000000000 --- a/luci-app-tailscale-community/root/usr/share/rpcd/ucode/tailscale.uc +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env ucode - -'use strict'; - -import { access, popen, readfile, writefile, unlink } from 'fs'; -import { cursor } from 'uci'; - -const uci = cursor(); - -function exec(command) { - let stdout_content = ''; - let p = popen(command, 'r'); - sleep(100); - if (p == null) { - return { code: -1, stdout: '', stderr: `Failed to execute: ${command}` }; - } - for (let line = p.read('line'); length(line); line = p.read('line')) { - stdout_content = stdout_content+line; - } - stdout_content = rtrim(stdout_content); - stdout_content = split(stdout_content, '\n'); - - let exit_code = p.close(); - let stderr_content = ''; - if (exit_code != 0) { - stderr_content = stdout_content; - } - return { code: exit_code, stdout: stdout_content, stderr: stderr_content }; -} - -function shell_quote(s) { - if (s == null || s == '') return "''"; - return "'" + replace(s, "'", "'\\''") + "'"; -} - -const methods = {}; - -methods.get_status = { - call: function() { - let data = { - status: '', - version: '', - TUNMode: '', - health: '', - ipv4: "Not running", - ipv6: null, - domain_name: '', - peers: [] - }; - if (access('/usr/sbin/tailscale')==true || access('/usr/bin/tailscale')==true){ }else{ - data.status = 'not_installed'; - return data; - } - - let status_json_output = exec('tailscale status --json'); - let peer_map = {}; - if (status_json_output.code == 0 && length(status_json_output.stdout) > 0) { - try { - let status_data = json(join('',status_json_output.stdout)); - data.version = status_data?.Version || 'Unknown'; - data.health = status_data?.Health || ''; - data.TUNMode = status_data?.TUN || 'true'; - if (status_data?.BackendState == 'Running') { data.status = 'running'; } - if (status_data?.BackendState == 'NeedsLogin') { data.status = 'logout'; } - - data.ipv4 = status_data?.Self?.TailscaleIPs?.[0] || 'No IP assigned'; - data.ipv6 = status_data?.Self?.TailscaleIPs?.[1] || null; - data.domain_name = status_data?.CurrentTailnet?.Name || ''; - - // peers list - for (let p in status_data?.Peer) { - p = status_data.Peer[p]; - peer_map[p.ID] = { - ip: join('
', p?.TailscaleIPs) || '', - hostname: split(p?.DNSName || '','.')[0] || '', - ostype: p?.OS, - online: p?.Online, - linkadress: (!p?.CurAddr) ? p?.Relay : p?.CurAddr, - lastseen: p?.LastSeen, - exit_node: !!p?.ExitNode, - exit_node_option: !!p?.ExitNodeOption, - tx: p?.TxBytes || '', - rx: p?.RxBytes || '' - }; - } - } catch (e) { /* ignore */ } - } - - data.peers = peer_map; - return data; - } -}; - -methods.get_settings = { - call: function() { - let settings = {}; - uci.load('tailscale'); - let state_file_path = uci.get('tailscale', 'settings', 'state_file') || "/etc/tailscale/tailscaled.state"; - if (access(state_file_path)) { - try { - let state_content = readfile(state_file_path); - if (state_content != null) { - let state_data = json(state_content); - let profiles_b64 = state_data?._profiles; - if (!profiles_b64) return settings; - - let profiles_data = json(b64dec(profiles_b64)); - let profiles_key = null; - for (let key in profiles_data) { - profiles_key = key; - break; - } - profiles_key = 'profile-'+profiles_key; - - let status_data = json(b64dec(state_data?.[profiles_key])); - if (status_data != null) { - settings.accept_routes = status_data?.RouteAll || false; - settings.advertise_exit_node = status_data?.AdvertiseExitNode || false; - settings.advertise_routes = status_data?.AdvertiseRoutes || []; - settings.exit_node = status_data?.ExitNodeID || ""; - settings.exit_node_allow_lan_access = status_data?.ExitNodeAllowLANAccess || false; - settings.shields_up = status_data?.ShieldsUp || false; - settings.ssh = status_data?.RunSSH || false; - settings.runwebclient = status_data?.RunWebClient || false; - settings.nosnat = status_data?.NoSNAT || false; - settings.disable_magic_dns = !status_data?.CorpDNS || false; - settings.fw_mode = split(uci.get('tailscale', 'settings', 'fw_mode'),' ')[0] || 'nftables'; - } - } - } catch (e) { /* ignore */ } - } - return 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: {} }, - call: function(request) { - const form_data = request.args.form_data; - let loginargs = []; - if (form_data == null || length(form_data) == 0) { - return { error: 'Missing or invalid form_data parameter. Please provide login data.' }; - } - - let status=methods.get_status.call(); - if (status.status != 'logout') { - return { error: 'Tailscale is already logged in and running.' }; - } - - // --- 1. Prepare and Run Login Command (Once) --- - const loginserver = trim(form_data.loginserver) || ''; - const loginserver_authkey = trim(form_data.loginserver_authkey) || ''; - - if (loginserver!='') { - push(loginargs,'--login-server '+shell_quote(loginserver)); - if (loginserver_authkey!='') { - push(loginargs,'--auth-key '+shell_quote(loginserver_authkey)); - } - } - - // Run the command in the background using /bin/sh -c to handle the '&' correctly - let login_cmd = 'tailscale login '+join(' ', loginargs); - popen('/bin/sh -c "' + login_cmd + ' &"', 'r'); - - // --- 2. Loop to Check Status for URL --- - let max_attempts = 15; - let interval = 2000; - - for (let i = 0; i < max_attempts; i++) { - let tresult = exec('tailscale status'); - for (let line in tresult.stdout) { - let trline = trim(line); - if (index(trline, 'http') != -1) { - let parts = split(trline, ' '); - for (let part in parts) { - if (index(part, 'http') != -1) { - return { url: part }; - } - } - } - } - sleep(interval); - } - return { error: 'Could not retrieve login URL from tailscale command after 30 seconds.' }; - } -}; - -methods.do_logout = { - call: function() { - let status=methods.get_status.call(); - if (status.status != 'running') { - return { error: 'Tailscale is not running. Cannot perform logout.' }; - } - - let logout_result = exec('tailscale logout'); - if (logout_result.code != 0) { - return { error: 'Failed to logout: ' + logout_result.stderr }; - } - return { success: true }; - } -}; - -methods.get_subroutes = { - call: function() { - try { - let cmd = 'ip -j route'; - let result = exec(cmd); - let subnets = []; - - if (result.code == 0 && length(result.stdout) > 0) { - let routes_json = json(join('',result.stdout)); - - for (let route in routes_json) { - // We need to filter out local subnets - // 1. 'dst' (target address) is not' default' (default gateway) - // 2. 'scope' is' link' (indicating directly connected network) - // 3. It is an IPv4 address (simple judgment: including'.') - if (route?.dst && route.dst != 'default' && route?.scope == 'link' && index(route.dst,'.') != -1) { - push(subnets,route.dst); - } - } - } - return { routes: subnets }; - } - catch(e) { - return { routes: [] }; - } - } -}; - -methods.setup_firewall = { - call: function() { - try { - uci.load('network'); - uci.load('firewall'); - - let changed_network = false; - let changed_firewall = false; - - // 1. config Network Interface - let net_ts = uci.get('network', 'tailscale'); - if (net_ts == null) { - uci.set('network', 'tailscale', 'interface'); - uci.set('network', 'tailscale', 'proto', 'none'); - uci.set('network', 'tailscale', 'device', 'tailscale0'); - changed_network = true; - } else { - let current_dev = uci.get('network', 'tailscale', 'device'); - if (current_dev != 'tailscale0') { - uci.set('network', 'tailscale', 'device', 'tailscale0'); - changed_network = true; - } - } - - // 2. config Firewall Zone - let fw_all = uci.get_all('firewall'); - let ts_zone_section = null; - let fwd_lan_to_ts = false; - let fwd_ts_to_lan = false; - - for (let sec_key in fw_all) { - let s = fw_all[sec_key]; - if (s['.type'] == 'zone' && s['name'] == 'tailscale') { - ts_zone_section = sec_key; - } - if (s['.type'] == 'forwarding') { - if (s.src == 'lan' && s.dest == 'tailscale') fwd_lan_to_ts = true; - if (s.src == 'tailscale' && s.dest == 'lan') fwd_ts_to_lan = true; - } - } - - if (ts_zone_section == null) { - let zid = uci.add('firewall', 'zone'); - uci.set('firewall', zid, 'name', 'tailscale'); - uci.set('firewall', zid, 'input', 'ACCEPT'); - uci.set('firewall', zid, 'output', 'ACCEPT'); - uci.set('firewall', zid, 'forward', 'ACCEPT'); - uci.set('firewall', zid, 'masq', '1'); - uci.set('firewall', zid, 'mtu_fix', '1'); - uci.set('firewall', zid, 'network', ['tailscale']); - changed_firewall = true; - } else { - let nets = uci.get('firewall', ts_zone_section, 'network'); - let net_list = []; - let has_ts_net = false; - - if (type(nets) == 'array') { - net_list = nets; - } else if (type(nets) == 'string') { - net_list = [nets]; - } - - // check if 'tailscale' is already in the list - for (let n in net_list) { - if (net_list[n] == 'tailscale') { - has_ts_net = true; - break; - } - } - - if (!has_ts_net) { - push(net_list, 'tailscale'); - uci.set('firewall', ts_zone_section, 'network', net_list); - changed_firewall = true; - } - } - - // 3. config Forwarding - if (!fwd_lan_to_ts) { - let fid = uci.add('firewall', 'forwarding'); - uci.set('firewall', fid, 'src', 'lan'); - uci.set('firewall', fid, 'dest', 'tailscale'); - changed_firewall = true; - } - - if (!fwd_ts_to_lan) { - let fid = uci.add('firewall', 'forwarding'); - uci.set('firewall', fid, 'src', 'tailscale'); - uci.set('firewall', fid, 'dest', 'lan'); - changed_firewall = true; - } - - // 4. save - if (changed_network) { - uci.save('network'); - uci.commit('network'); - exec('/etc/init.d/network reload'); - } - - if (changed_firewall) { - uci.save('firewall'); - uci.commit('firewall'); - exec('/etc/init.d/firewall reload'); - } - - return { - success: true, - changed_network: changed_network, - changed_firewall: changed_firewall, - message: (changed_network || changed_firewall) ? 'Tailscale firewall/interface configuration applied.' : 'Tailscale firewall/interface already configured.' - }; - - } catch (e) { - return { error: 'Exception in setup_firewall: ' + e + '\nStack: ' + (e.stacktrace || '') }; - } - } -}; - -return { 'tailscale': methods };