diff --git a/luci-app-tailscale-community/Makefile b/luci-app-tailscale-community/Makefile new file mode 100644 index 000000000..8700de0c0 --- /dev/null +++ b/luci-app-tailscale-community/Makefile @@ -0,0 +1,12 @@ +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-full +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 new file mode 100644 index 000000000..5708b0c94 --- /dev/null +++ b/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js @@ -0,0 +1,695 @@ +'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 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' }); +const callGetLogs = rpc.declare({ object: 'tailscale', method: 'get_logs' }); +let map; + +const tailscaleSettingsConf = [ + [form.Flag, 'service_enabled', _('Enable Tailscale Service'), _('Enable or disable the Tailscale service. When disabled, the service will be stopped and the process will be killed.'), { rmempty: false }], + [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.ListValue, 'dns_mode', _('DNS Mode'), _('Controls how Tailscale DNS is handled.')+'
'+_('Disabled: system DNS only.')+'
'+_('MagicDNS: Tailscale overrides resolv.conf.')+'
'+_('OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)'), { values: [['disabled', _('Disabled')], ['magicdns', 'MagicDNS'], ['openwrt_forward', _('OpenWrt Forward')]], rmempty: false }], + [form.Flag, 'disable_fw_config', _('Disable Firewall Configuration'), _('Disable Tailscale netfilter auto-configuration (--netfilter-mode=off).'), { 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 + +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(), { status: 'stopped', peers: {} }).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))) + ]); + + return statusTable; +} + +function renderLogs(logs_data) { + if (!logs_data || !logs_data.logs || logs_data.logs.length === 0) { + return E('em', {}, _('No tailscale-related logs found.')); + } + + const lines = logs_data.logs.map(function(line) { + return E('div', { 'style': 'white-space: pre; font-family: monospace; font-size: 13px; line-height: 1.5;' }, line); + }); + + return E('div', { + 'style': 'max-height: 500px; overflow-y: auto; background: #f5f5f5; border: 1px solid #ccc; padding: 8px; border-radius: 3px;' + }, lines); +} + +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) { + return E('p', {}, _('No peer devices found.')); + } + + const peerTableHeaders = [ + { text: _('Status'), style: 'width: 80px;' }, + { text: _('Hostname') }, + { text: _('Tailscale IP') }, + { text: _('OS') }, + { text: _('Connection Info') }, + { text: _('RX') }, + { text: _('TX') }, + { text: _('Last Seen') } + ]; + + const existingFilter = document.getElementById('tailscale_devices_filter'); + const currentQuery = existingFilter ? existingFilter.value : ''; + + const searchInput = E('input', { + 'id': 'tailscale_devices_filter', + 'class': 'cbi-input-text', + 'type': 'text', + 'placeholder': _('Filter by hostname, IP, OS...'), + 'style': 'margin-bottom: 8px; width: 100%; max-width: 320px;' + }); + if (currentQuery) { + searchInput.value = currentQuery; + } + + const rows = Object.entries(peers).map(([peerid, peer]) => { + const td_style = 'padding-right: 20px;'; + const hostname = peer.hostname || ''; + const ip = peer.ip || ''; + const ostype = peer.ostype || ''; + const searchText = `${hostname} ${ip} ${ostype}`.toLowerCase(); + + const tr = E('tr', { + 'class': 'cbi-rowstyle-1', + 'data-search-text': searchText + }, [ + 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', {}, hostname + (peer.exit_node_option ? ' (ExNode)' : ''))), + E('td', { 'class': 'cbi-value-field', 'style': td_style }, ip || 'N/A'), + E('td', { 'class': 'cbi-value-field', 'style': td_style }, 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)) + ]); + + if (currentQuery && !searchText.includes(currentQuery.toLowerCase().trim())) { + tr.style.display = 'none'; + } + + return tr; + }); + + searchInput.addEventListener('input', function(ev) { + const q = (ev.target.value || '').toLowerCase().trim(); + for (const tr of rows) { + const text = tr.getAttribute('data-search-text') || ''; + tr.style.display = (!q || text.includes(q)) ? '' : 'none'; + } + }); + + if (existingFilter && document.activeElement === existingFilter) { + requestAnimationFrame(() => { + searchInput.focus(); + const valLen = searchInput.value.length; + searchInput.setSelectionRange(valLen, valLen); + }); + } + + const existingContainer = document.getElementById('tailscale_devices_table_container'); + const scrollPos = existingContainer ? existingContainer.scrollTop : 0; + + const table = E('table', { 'class': 'cbi-table' }, [ + E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => { + let th_style = 'padding-right: 20px; text-align: left; position: sticky; top: 0; background: inherit; z-index: 1;'; + if (header.style) { + th_style += header.style; + } + return E('th', { 'class': 'cbi-table-cell', 'style': th_style }, header.text); + })), + ...rows + ]); + + const tableContainer = E('div', { + 'id': 'tailscale_devices_table_container', + 'style': 'max-height: 480px; overflow-y: auto; overflow-x: auto; border: 1px solid #ddd; border-radius: 3px;' + }, [table]); + + if (scrollPos > 0) { + requestAnimationFrame(() => { + tableContainer.scrollTop = scrollPos; + }); + } + + return E('div', { 'style': 'width: 100%;' }, [ + searchInput, + tableContainer + ]); +} + +return view.extend({ + load() { + return Promise.all([ + L.resolveDefault(callGetStatus(), { status: 'stopped', 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', 'service_enabled', '1'); + 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', 'dns_mode', 'disabled'); + uci.set('tailscale', 'settings', 'disable_fw_config', '0'); + + uci.set('tailscale', 'settings', 'daemon_reduce_memory', '0'); + uci.set('tailscale', 'settings', 'daemon_mtu', ''); + return uci.save(); + } + }).then(function() { + // Migrate from old disable_magic_dns to dns_mode if needed + if (uci.get('tailscale', 'settings', 'dns_mode') === null) { + var oldMagicDns = uci.get('tailscale', 'settings', 'disable_magic_dns'); + uci.set('tailscale', 'settings', 'dns_mode', oldMagicDns === '1' ? 'disabled' : 'magicdns'); + uci.unset('tailscale', 'settings', 'disable_magic_dns'); + 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); + } + + 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'); } + }); + }, 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', null); + s.dynamic = true; + + // Create the "General Settings" tab and apply tailscaleSettingsConf + s.tab('general', _('General Settings')); + + 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) { + 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() { + 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'); + }).then(function() { + return new Promise(function(resolve) { + window.setTimeout(resolve, 3000); + }); + }); + }; + + 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); + }; + + 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)); + }; + + s.tab('logs', _('Logs')); + const logsSection = s.taboption('logs', form.DummyValue, '_logs'); + logsSection.render = function () { + const container = E('div', { 'id': 'tailscale_logs_display', 'class': 'cbi-value' }, + _('No tailscale-related logs found.') + ); + return container; + }; + + const refreshLogsBtn = s.taboption('logs', form.Button, '_refresh_logs', _('Refresh')); + refreshLogsBtn.inputstyle = 'action'; + refreshLogsBtn.onclick = function() { + const display = document.getElementById('tailscale_logs_display'); + if (display) { + display.replaceChildren(E('em', {}, _('Collecting data ...'))); + } + return callGetLogs().then(function(res) { + if (display) { + display.replaceChildren(renderLogs(res)); + } + }).catch(function(err) { + if (display) { + display.replaceChildren(E('em', {}, _('Failed to load logs: %s').format(err.message || _('Unknown error')))); + } + }); + }; + + // 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 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 () { + return ui.changes.apply(mode == '0'); + }); + }, + + handleSave: null, + handleReset: null +}); diff --git a/luci-app-tailscale-community/po/es/community.po b/luci-app-tailscale-community/po/es/community.po new file mode 100644 index 000000000..e141902a8 --- /dev/null +++ b/luci-app-tailscale-community/po/es/community.po @@ -0,0 +1,565 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.8\n" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "(Experimental) Reduce Memory Usage" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469 +msgid "1. Select \"Accept Routes\" (to access remote devices)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470 +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:471 +msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Accept Routes" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 +msgid "Account Settings" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Advertise Exit Node" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "Advertise Routes" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +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:23 +msgid "Allow LAN Access" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Allow accepting routes announced by other nodes." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +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:552 +msgid "Are you sure you want to log out?" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 +msgid "Auto Configure Firewall" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559 +msgid "Cancel" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 +msgid "Click to Log out account on this device." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482 +msgid "Click to get a login URL for this device." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601 +msgid "Collecting data ..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578 +msgid "Confirm Logout" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281 +msgid "Connection Info" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Controls how Tailscale DNS is handled." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515 +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:487 +msgid "Custom Login Server" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495 +msgid "Custom Login Server Auth Key" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "DNS Mode" +msgstr "Modo DNS" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Declare this device as an Exit Node." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581 +msgid "Devices List" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Firewall Configuration" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "Disable SNAT" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +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:29 +msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Disabled" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Disabled: system DNS only." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 +msgid "Disconnect from Tailscale and expire current node key." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "Enable Peer Relay" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Enable Tailscale SSH" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "Enable Tailscale Service" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "Enable Web Interface" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "" +"Enable or disable the Tailscale service. When disabled, the service will be " +"stopped and the process will be killed." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +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:229 +msgid "Enabled" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +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:540 +msgid "Error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 +msgid "Error caching DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153 +msgid "Error fetching DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +msgid "Error reading cached DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 +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:303 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "Exit Node" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +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:455 +msgid "Failed to configure firewall: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 +msgid "Failed to get login URL. You may close this tab." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +msgid "Failed to get login URL: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542 +msgid "Failed to get login URL: Invalid response from server." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Failed to load logs: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "Firewall Mode" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 +msgid "Firewall configuration applied." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401 +msgid "General Settings" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 +msgid "Hostname" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464 +msgid "How to enable Site-to-Site?" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483 +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:497 +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:93 +msgid "Invalid Date" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447 +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:96 +msgid "Just now" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208 +msgid "LOGGED OUT" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284 +msgid "Last Seen" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489 +msgid "Leave blank for default Tailscale control plane." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Logging out..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481 +msgid "Login" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575 +msgid "Logout" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +msgid "Logout failed: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587 +msgid "Logs" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "MagicDNS: Tailscale overrides resolv.conf." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 +msgid "N/A" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219 +msgid "NOT RUNNING" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273 +msgid "No peer devices found." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591 +msgid "No tailscale-related logs found." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414 +msgid "None" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91 +msgid "Now" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280 +msgid "OS" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Offline" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Online" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward" +msgstr "Reenvío OpenWrt" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488 +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:496 +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:405 +msgid "Peer Relay Port" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210 +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:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Please wait." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "RUNNING" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282 +msgid "RX" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596 +msgid "Refresh" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +msgid "Requesting Login URL..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 +msgid "Requesting Tailscale login URL... Please wait." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +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:20 +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:198 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "Service Status" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "Shields Up" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277 +msgid "Status" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569 +msgid "Successfully logged out." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199 +msgid "TAILSCALE NOT FOUND" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "TUN Mode" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283 +msgid "TX" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232 +msgid "Tailnet Name" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +#: 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:175 +msgid "Tailscale Health Check: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279 +msgid "Tailscale IP" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230 +msgid "Tailscale IPv4" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231 +msgid "Tailscale IPv6" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521 +msgid "Tailscale Login" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +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:264 +msgid "Tailscale status error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523 +msgid "This can take up to 30 seconds." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553 +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:406 +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:123 +#: 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:153 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Unknown error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 +msgid "Version" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "" +"When enabled, blocks all inbound connections from the Tailscale network." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 +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:472 +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:99 +msgid "ago" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448 +msgid "" +"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " +"connections." +msgstr "" diff --git a/luci-app-tailscale-community/po/pl/community.po b/luci-app-tailscale-community/po/pl/community.po new file mode 100644 index 000000000..f2893a860 --- /dev/null +++ b/luci-app-tailscale-community/po/pl/community.po @@ -0,0 +1,606 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "(Experimental) Reduce Memory Usage" +msgstr "(Eksperymentalne) Zmniejsz użycie pamięci" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469 +msgid "1. Select \"Accept Routes\" (to access remote devices)." +msgstr "1. Wybierz \"Akceptuj trasy\" (aby uzyskać dostęp do zdalnych urządzeń)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470 +msgid "" +"2. In \"Advertise Routes\", select your local subnet (to allow remote " +"devices to access this LAN)." +msgstr "" +"2. W \"Ogłaszaj trasy\" wybierz swoją lokalną podsieć (aby umożliwić zdalnym " +"urządzaniom dostęp do tej sieci LAN)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:471 +msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." +msgstr "3. Kliknij \"Automatycznie konfiguruj zaporę\" (aby zezwolić na przekazywanie ruchu)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Accept Routes" +msgstr "Akceptuj trasy" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 +msgid "Account Settings" +msgstr "Ustawienia konta" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Advertise Exit Node" +msgstr "Ogłaszaj węzeł wyjścia" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "Advertise Routes" +msgstr "Ogłaszaj trasy" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "" +"Advertise subnet routes behind this device. Select from the detected subnets " +"below or enter custom routes (comma-separated)." +msgstr "" +"Ogłaszaj trasy podsieci za tym urządzeniem. Wybierz z wykrytych podsieci " +"poniżej lub wprowadź niestandardowe trasy (oddzielone przecinkami)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 +msgid "Allow LAN Access" +msgstr "Zezwól na dostęp do LAN" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Allow accepting routes announced by other nodes." +msgstr "Zezwól na akceptowanie tras ogłaszanych przez inne węzły." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Allow connecting to this device through the SSH function of Tailscale." +msgstr "Zezwól na łączenie się z tym urządzeniem przez funkcję SSH Tailscale." + +#: applications/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json:3 +msgid "Allow user access to tailscale" +msgstr "Zezwól użytkownikowi na dostęp do Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:552 +msgid "Are you sure you want to log out?" +msgstr "Czy na pewno chcesz się wylogować?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 +msgid "Auto Configure Firewall" +msgstr "Automatycznie konfiguruj zaporę" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559 +msgid "Cancel" +msgstr "Anuluj" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 +msgid "Click to Log out account on this device." +msgstr "Kliknij, aby wylogować konto z tego urządzenia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482 +msgid "Click to get a login URL for this device." +msgstr "Kliknij, aby uzyskać URL logowania dla tego urządzenia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601 +msgid "Collecting data ..." +msgstr "Zbieranie danych ..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578 +msgid "Confirm Logout" +msgstr "Potwierdź wylogowanie" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281 +msgid "Connection Info" +msgstr "Informacje o połączeniu" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Controls how Tailscale DNS is handled." +msgstr "Kontroluje sposób obsługi DNS Tailscale." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515 +msgid "" +"Could not open a new tab. Please check if your browser or an extension " +"blocked the pop-up." +msgstr "" +"Nie można otworzyć nowej karty. Sprawdź, czy przeglądarka lub rozszerzenie " +"zablokowało wyskakujące okno." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:487 +msgid "Custom Login Server" +msgstr "Niestandardowy serwer logowania" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495 +msgid "Custom Login Server Auth Key" +msgstr "Klucz autoryzacji niestandardowego serwera logowania" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "DNS Mode" +msgstr "Tryb DNS" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Declare this device as an Exit Node." +msgstr "Deklaruje to urządzenie jako węzeł wyjścia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581 +msgid "Devices List" +msgstr "Lista urządzeń" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Firewall Configuration" +msgstr "Wyłącz konfigurację zapory" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "Disable SNAT" +msgstr "Wyłącz SNAT" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "" +"Disable Source NAT (SNAT) for traffic to advertised routes. Most users " +"should leave this unchecked." +msgstr "" +"Wyłącz NAT źródłowy (SNAT) dla ruchu do ogłaszanych tras. Większość użytkowników " +"powinna zostawić to pole niezaznaczone." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)." +msgstr "Wyłącz automatyczną konfigurację netfilter Tailscale (--netfilter-mode=off)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Disabled" +msgstr "Wyłączone" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Disabled: system DNS only." +msgstr "Wyłączone: tylko DNS systemowy." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 +msgid "Disconnect from Tailscale and expire current node key." +msgstr "Rozłącz z Tailscale i wygaś bieżący klucz węzła." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "Enable Peer Relay" +msgstr "Włącz przekazywanie między węzłami" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Enable Tailscale SSH" +msgstr "Włącz SSH Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "Enable Tailscale Service" +msgstr "Włącz usługę Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "Enable Web Interface" +msgstr "Włącz interfejs webowy" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "" +"Enable or disable the Tailscale service. When disabled, the service will be " +"stopped and the process will be killed." +msgstr "" +"Włącz lub wyłącz usługę Tailscale. Po wyłączeniu usługa zostanie " +"zatrzymana, a proces zostanie zakończony." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "" +"Enable this device as a Peer Relay server. Requires a public IP and an UDP " +"port open on the router." +msgstr "" +"Włącz to urządzenie jako serwer przekazywania między węzłami. Wymaga publicznego " +"IP i otwartego portu UDP na routerze." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Enabled" +msgstr "Włączone" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "" +"Enabling this option can reduce memory usage, but it may sacrifice some " +"performance (set GOGC=10)." +msgstr "" +"Włączenie tej opcji może zmniejszyć użycie pamięci, ale może to wpłynąć " +"na wydajność (ustawia GOGC=10)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:540 +msgid "Error" +msgstr "Błąd" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 +msgid "Error caching DERP region map: %s" +msgstr "Błąd buforowania mapy regionu DERP: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153 +msgid "Error fetching DERP region map: %s" +msgstr "Błąd pobierania mapy regionu DERP: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +msgid "Error reading cached DERP region map: %s" +msgstr "Błąd odczytywania buforowanej mapy regionu DERP: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 +msgid "" +"Essential configuration for Subnet Routing (Site-to-Site) and Exit Node " +"features." +msgstr "" +"Podstawowa konfiguracja dla funkcji routingu podsieci (Site-to-Site) " +"i węzła wyjścia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "Exit Node" +msgstr "Węzeł wyjścia" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "" +"Expose a web interface on port 5252 for managing this node over Tailscale." +msgstr "" +"Udostępnij interfejs webowy na porcie 5252 do zarządzania tym węzłem przez Tailscale." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:455 +msgid "Failed to configure firewall: %s" +msgstr "Nie udało się skonfigurować zapory: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 +msgid "Failed to get login URL. You may close this tab." +msgstr "Nie udało się uzyskać URL logowania. Możesz zamknąć tę kartę." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +msgid "Failed to get login URL: %s" +msgstr "Nie udało się uzyskać URL logowania: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542 +msgid "Failed to get login URL: Invalid response from server." +msgstr "Nie udało się uzyskać URL logowania: Nieprawidłowa odpowiedź z serwera." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Failed to load logs: %s" +msgstr "Nie udało się załadować logów: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "Firewall Mode" +msgstr "Tryb zapory" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 +msgid "Firewall configuration applied." +msgstr "Zastosowano konfigurację zapory." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401 +msgid "General Settings" +msgstr "Ustawienia ogólne" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 +msgid "Hostname" +msgstr "Nazwa hosta" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464 +msgid "How to enable Site-to-Site?" +msgstr "Jak włączyć Site-to-Site?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483 +msgid "" +"If the timeout is displayed, you can refresh the page and click Login again." +msgstr "" +"Jeśli wyświetlany jest komunikat o przekroczeniu limitu czasu, możesz odświeżyć stronę i kliknąć Zaloguj ponownie." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:497 +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 "" +"Jeśli korzystasz z niestandardowego serwera logowania, ale nie podajesz klucza " +"autoryzacji, nastąpi przekierowanie do strony logowania bez wypełniania klucza." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:93 +msgid "Invalid Date" +msgstr "Nieprawidłowa data" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447 +msgid "" +"It automatically creates the tailscale interface, sets up firewall zones for " +"LAN <-> Tailscale forwarding," +msgstr "" +"Automatycznie tworzy interfejs tailscale, ustawia strefy zapory dla " +"przekazywania LAN <-> Tailscale," + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:96 +msgid "Just now" +msgstr "Przed chwilą" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208 +msgid "LOGGED OUT" +msgstr "WYLOGOWANO" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284 +msgid "Last Seen" +msgstr "Ostatnia aktywność" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489 +msgid "Leave blank for default Tailscale control plane." +msgstr "Pozostaw puste dla domyślnego panelu kontrolnego Tailscale." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Logging out..." +msgstr "Wylogowywanie..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481 +msgid "Login" +msgstr "Zaloguj" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575 +msgid "Logout" +msgstr "Wyloguj" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +msgid "Logout failed: %s" +msgstr "Wylogowanie nie powiodło się: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587 +msgid "Logs" +msgstr "Logi" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "MagicDNS: Tailscale overrides resolv.conf." +msgstr "MagicDNS: Tailscale nadpisuje resolv.conf." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 +msgid "N/A" +msgstr "Niedostępne" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219 +msgid "NOT RUNNING" +msgstr "NIE DZIAŁA" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273 +msgid "No peer devices found." +msgstr "Nie znaleziono urządzeń partnerskich." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591 +msgid "No tailscale-related logs found." +msgstr "Nie znaleziono logów związanych z Tailscale." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414 +msgid "None" +msgstr "Brak" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91 +msgid "Now" +msgstr "Teraz" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280 +msgid "OS" +msgstr "System operacyjny" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Offline" +msgstr "Offline" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Online" +msgstr "Online" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward" +msgstr "Przekazywanie OpenWrt" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)" +msgstr "Przekazywanie OpenWrt: MagicDNS przez przekazywanie dnsmasq. (Obsługuje tylko ts.net)" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488 +msgid "" +"Optional: Specify a custom control server URL (e.g., a Headscale instance, " +"%s)." +msgstr "" +"Opcjonalne: Podaj niestandardowy URL serwera kontrolnego (np. instancja " +"Headscale, %s)." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496 +msgid "" +"Optional: Specify an authentication key for the custom control server. Leave " +"blank if not required." +msgstr "" +"Opcjonalne: Podaj klucz autoryzacji dla niestandardowego serwera kontrolnego. " +"Pozostaw puste, jeśli nie jest wymagany." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:405 +msgid "Peer Relay Port" +msgstr "Port przekazywania między węzłami" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210 +msgid "Please use the login button in the settings below to authenticate." +msgstr "Użyj przycisku logowania w ustawieniach poniżej, aby się uwierzytelnić." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Please wait." +msgstr "Proszę czekać." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "RUNNING" +msgstr "DZIAŁA" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282 +msgid "RX" +msgstr "Odbieranie" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596 +msgid "Refresh" +msgstr "Odśwież" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +msgid "Requesting Login URL..." +msgstr "Pobieranie URL logowania..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 +msgid "Requesting Tailscale login URL... Please wait." +msgstr "Pobieranie URL logowania Tailscale... Proszę czekać." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "" +"Select an exit node from the list. If enabled, Allow LAN Access is enabled " +"implicitly." +msgstr "" +"Wybierz węzeł wyjścia z listy. Jeśli włączony, dostęp do LAN jest " +"automatycznie włączony." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "" +"Select the firewall backend for Tailscale to use. Requires service restart " +"to take effect." +msgstr "" +"Wybierz mechanizm zapory dla Tailscale. Wymaga restartu usługi, aby " +"zmiany zaczęły obowiązywać." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:198 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "Service Status" +msgstr "Status usługi" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "Shields Up" +msgstr "Tarcza włączona" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277 +msgid "Status" +msgstr "Status" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569 +msgid "Successfully logged out." +msgstr "Pomyślnie wylogowano." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199 +msgid "TAILSCALE NOT FOUND" +msgstr "NIE ZNALEZIONO TAILSCALE" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "TUN Mode" +msgstr "Tryb TUN" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283 +msgid "TX" +msgstr "Wysyłanie" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232 +msgid "Tailnet Name" +msgstr "Nazwa Tailnet" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +#: 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:175 +msgid "Tailscale Health Check: %s" +msgstr "Kontrola stanu Tailscale: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279 +msgid "Tailscale IP" +msgstr "IP Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230 +msgid "Tailscale IPv4" +msgstr "IPv4 Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231 +msgid "Tailscale IPv6" +msgstr "IPv6 Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521 +msgid "Tailscale Login" +msgstr "Logowanie Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +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 to rozwiązanie VPN mesh, które ułatwia bezpieczne łączenie urządzeń. " +"Ta strona konfiguracyjna pozwala zarządzać ustawieniami Tailscale na " +"urządzeniu OpenWrt." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:264 +msgid "Tailscale status error" +msgstr "Błąd statusu Tailscale" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523 +msgid "This can take up to 30 seconds." +msgstr "Może to potrwać do 30 sekund." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553 +msgid "" +"This will disconnect this device from your Tailnet and require you to re-" +"authenticate." +msgstr "" +"Spowoduje to rozłączenie tego urządzenia z Twojego Tailnet i wymaga " +"ponownego uwierzytelnienia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:406 +msgid "" +"UDP port for the Peer Relay service. Open this port on your router firewall/" +"NAT." +msgstr "" +"Port UDP dla usługi przekazywania między węzłami. Otwórz ten port w zaporze/" +"NAT na routerze." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +#: 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:153 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Unknown error" +msgstr "Nieznany błąd" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 +msgid "Version" +msgstr "Wersja" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "" +"When enabled, blocks all inbound connections from the Tailscale network." +msgstr "" +"Po włączeniu blokuje wszystkie połączenia przychodzące z sieci Tailscale." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 +msgid "When using the exit node, access to the local LAN is allowed." +msgstr "Podczas korzystania z węzła wyjścia dozwolony jest dostęp do lokalnego LAN." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:472 +msgid "" +"[Important] Log in to the Tailscale admin console and manually enable " +"\"Subnet Routes\" for this device." +msgstr "" +"[Ważne] Zaloguj się do panelu administracyjnego Tailscale i ręcznie włącz " +"\"Trasy podsieci\" dla tego urządzenia." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:99 +msgid "ago" +msgstr "temu" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448 +msgid "" +"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " +"connections." +msgstr "" +"i włącza Maskaradę oraz MSS Clamping (korekta MTU) w celu zapewnienia " +"stabilnych połączeń." diff --git a/luci-app-tailscale-community/po/templates/community.pot b/luci-app-tailscale-community/po/templates/community.pot new file mode 100644 index 000000000..4a96f687b --- /dev/null +++ b/luci-app-tailscale-community/po/templates/community.pot @@ -0,0 +1,555 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "(Experimental) Reduce Memory Usage" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469 +msgid "1. Select \"Accept Routes\" (to access remote devices)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470 +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:471 +msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Accept Routes" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 +msgid "Account Settings" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Advertise Exit Node" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "Advertise Routes" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +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:23 +msgid "Allow LAN Access" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Allow accepting routes announced by other nodes." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +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:552 +msgid "Are you sure you want to log out?" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 +msgid "Auto Configure Firewall" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559 +msgid "Cancel" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 +msgid "Click to Log out account on this device." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482 +msgid "Click to get a login URL for this device." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601 +msgid "Collecting data ..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578 +msgid "Confirm Logout" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281 +msgid "Connection Info" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Controls how Tailscale DNS is handled." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515 +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:487 +msgid "Custom Login Server" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495 +msgid "Custom Login Server Auth Key" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "DNS Mode" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Declare this device as an Exit Node." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581 +msgid "Devices List" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Firewall Configuration" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "Disable SNAT" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +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:29 +msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Disabled" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Disabled: system DNS only." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 +msgid "Disconnect from Tailscale and expire current node key." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "Enable Peer Relay" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Enable Tailscale SSH" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "Enable Tailscale Service" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "Enable Web Interface" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "" +"Enable or disable the Tailscale service. When disabled, the service will be " +"stopped and the process will be killed." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +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:229 +msgid "Enabled" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +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:540 +msgid "Error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 +msgid "Error caching DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153 +msgid "Error fetching DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +msgid "Error reading cached DERP region map: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 +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:303 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "Exit Node" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +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:455 +msgid "Failed to configure firewall: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 +msgid "Failed to get login URL. You may close this tab." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +msgid "Failed to get login URL: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542 +msgid "Failed to get login URL: Invalid response from server." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Failed to load logs: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "Firewall Mode" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 +msgid "Firewall configuration applied." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401 +msgid "General Settings" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 +msgid "Hostname" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464 +msgid "How to enable Site-to-Site?" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483 +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:497 +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:93 +msgid "Invalid Date" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447 +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:96 +msgid "Just now" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208 +msgid "LOGGED OUT" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284 +msgid "Last Seen" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489 +msgid "Leave blank for default Tailscale control plane." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Logging out..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481 +msgid "Login" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575 +msgid "Logout" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +msgid "Logout failed: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587 +msgid "Logs" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "MagicDNS: Tailscale overrides resolv.conf." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 +msgid "N/A" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219 +msgid "NOT RUNNING" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273 +msgid "No peer devices found." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591 +msgid "No tailscale-related logs found." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414 +msgid "None" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91 +msgid "Now" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280 +msgid "OS" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Offline" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Online" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488 +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:496 +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:405 +msgid "Peer Relay Port" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210 +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:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Please wait." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "RUNNING" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282 +msgid "RX" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596 +msgid "Refresh" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +msgid "Requesting Login URL..." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 +msgid "Requesting Tailscale login URL... Please wait." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +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:20 +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:198 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "Service Status" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "Shields Up" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277 +msgid "Status" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569 +msgid "Successfully logged out." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199 +msgid "TAILSCALE NOT FOUND" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "TUN Mode" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283 +msgid "TX" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232 +msgid "Tailnet Name" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +#: 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:175 +msgid "Tailscale Health Check: %s" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279 +msgid "Tailscale IP" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230 +msgid "Tailscale IPv4" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231 +msgid "Tailscale IPv6" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521 +msgid "Tailscale Login" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +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:264 +msgid "Tailscale status error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523 +msgid "This can take up to 30 seconds." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553 +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:406 +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:123 +#: 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:153 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Unknown error" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 +msgid "Version" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "" +"When enabled, blocks all inbound connections from the Tailscale network." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:23 +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:472 +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:99 +msgid "ago" +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448 +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 new file mode 120000 index 000000000..8d69574dd --- /dev/null +++ b/luci-app-tailscale-community/po/zh-cn @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..abf585a3e --- /dev/null +++ b/luci-app-tailscale-community/po/zh_Hans/community.po @@ -0,0 +1,600 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "(Experimental) Reduce Memory Usage" +msgstr "(实验性) 减少内存使用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469 +msgid "1. Select \"Accept Routes\" (to access remote devices)." +msgstr "1. 勾选 接受路由 (接受远程设备访问)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470 +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:471 +msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." +msgstr "3. 点击 自动配置防火墙 (打通防火墙转发)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Accept Routes" +msgstr "接受路由" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 +msgid "Account Settings" +msgstr "账户设置" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Advertise Exit Node" +msgstr "通告出口节点" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "Advertise Routes" +msgstr "通告路由" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +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:23 +msgid "Allow LAN Access" +msgstr "允许局域网访问" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Allow accepting routes announced by other nodes." +msgstr "允许接受由其他节点通告的路由。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +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:552 +msgid "Are you sure you want to log out?" +msgstr "您确定要登出吗?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 +msgid "Auto Configure Firewall" +msgstr "自动配置防火墙" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559 +msgid "Cancel" +msgstr "取消" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 +msgid "Click to Log out account on this device." +msgstr "点击以登出此设备上的账户。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482 +msgid "Click to get a login URL for this device." +msgstr "点击获取此设备的登录 URL。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601 +msgid "Collecting data ..." +msgstr "正在收集数据..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578 +msgid "Confirm Logout" +msgstr "确认登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281 +msgid "Connection Info" +msgstr "连接信息" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Controls how Tailscale DNS is handled." +msgstr "控制 Tailscale DNS 的处理方式。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515 +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:487 +msgid "Custom Login Server" +msgstr "自定义登录服务器" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495 +msgid "Custom Login Server Auth Key" +msgstr "自定义登录服务器认证密钥" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "DNS Mode" +msgstr "DNS 模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Declare this device as an Exit Node." +msgstr "将此设备声明为出口节点。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581 +msgid "Devices List" +msgstr "设备列表" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Firewall Configuration" +msgstr "禁用防火墙配置" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "Disable SNAT" +msgstr "禁用 SNAT" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +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:29 +msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)." +msgstr "" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Disabled" +msgstr "已禁用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Disabled: system DNS only." +msgstr "已禁用:使用系统指定DNS模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 +msgid "Disconnect from Tailscale and expire current node key." +msgstr "从 Tailscale 断开连接并使当前节点密钥过期。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "Enable Peer Relay" +msgstr "启用节点中继" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Enable Tailscale SSH" +msgstr "启用 Tailscale SSH" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "Enable Tailscale Service" +msgstr "Tailscale服务开关" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "Enable Web Interface" +msgstr "启用 Web 界面" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "" +"Enable or disable the Tailscale service. When disabled, the service will be " +"stopped and the process will be killed." +msgstr "启用或禁用Tailscale服务。禁用后,服务将被停止,当前进程将被终止。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "" +"Enable this device as a Peer Relay server. Requires a public IP and an UDP " +"port open on the router." +msgstr "" +"将此设备设定为节点中继服务器(DERP)。需要此路由器具备公网IP以及开放一个UDP端" +"口" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Enabled" +msgstr "已启用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +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:540 +msgid "Error" +msgstr "错误" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 +msgid "Error caching DERP region map: %s" +msgstr "缓存 DERP 区域地图时出错: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153 +msgid "Error fetching DERP region map: %s" +msgstr "获取 DERP 区域地图时出错: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +msgid "Error reading cached DERP region map: %s" +msgstr "读取缓存的 DERP 区域地图时出错: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 +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:303 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "Exit Node" +msgstr "出口节点" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +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:455 +msgid "Failed to configure firewall: %s" +msgstr "获取防火墙设置失败: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 +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:546 +msgid "Failed to get login URL: %s" +msgstr "获取登录 URL 失败: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542 +msgid "Failed to get login URL: Invalid response from server." +msgstr "获取登录 URL 失败: 服务器响应无效。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Failed to load logs: %s" +msgstr "加载日志失败" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "Filter by hostname, IP, OS..." +msgstr "按主机名、IP、系统过滤..." + +msgid "Firewall Mode" +msgstr "防火墙模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 +msgid "Firewall configuration applied." +msgstr "已应用防火墙配置" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401 +msgid "General Settings" +msgstr "常规设置" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 +msgid "Hostname" +msgstr "主机名" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464 +msgid "How to enable Site-to-Site?" +msgstr "如何实现站点到站点(即多局域网互通)?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483 +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:497 +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:93 +msgid "Invalid Date" +msgstr "无效日期" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447 +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:96 +msgid "Just now" +msgstr "刚才" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208 +msgid "LOGGED OUT" +msgstr "已登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284 +msgid "Last Seen" +msgstr "上次在线" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489 +msgid "Leave blank for default Tailscale control plane." +msgstr "留空以使用默认的 Tailscale 控制平面。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Logging out..." +msgstr "正在登出..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481 +msgid "Login" +msgstr "登录" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575 +msgid "Logout" +msgstr "登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +msgid "Logout failed: %s" +msgstr "登出失败: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587 +msgid "Logs" +msgstr "日志" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "MagicDNS: Tailscale overrides resolv.conf." +msgstr "MagicDNS:Tailscale将根据设置覆写系统resolv.conf配置" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 +msgid "N/A" +msgstr "不可用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219 +msgid "NOT RUNNING" +msgstr "未运行" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273 +msgid "No peer devices found." +msgstr "未找到节点设备。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591 +msgid "No tailscale-related logs found." +msgstr "没有找到Tailscale相关日志" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414 +msgid "None" +msgstr "无" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91 +msgid "Now" +msgstr "现在" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280 +msgid "OS" +msgstr "操作系统" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Offline" +msgstr "离线" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Online" +msgstr "在线" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward" +msgstr "OpenWrt 转发" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)" +msgstr "OpenWrt 转发:通过dnsmasq转发MagicDNS(仅支持 ts.net域名)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488 +msgid "" +"Optional: Specify a custom control server URL (e.g., a Headscale instance, " +"%s)." +msgstr "可选:指定一个自定义控制服务器 URL (例如,一个 Headscale 实例, %s)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496 +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:405 +msgid "Peer Relay Port" +msgstr "DERP端口" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210 +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:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Please wait." +msgstr "请稍候。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "RUNNING" +msgstr "正在运行" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282 +msgid "RX" +msgstr "接收" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596 +msgid "Refresh" +msgstr "刷新" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +msgid "Requesting Login URL..." +msgstr "正在请求登录 URL..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 +msgid "Requesting Tailscale login URL... Please wait." +msgstr "正在请求 Tailscale 登录 URL... 请稍候。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +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:20 +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:198 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "Service Status" +msgstr "服务状态" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "Shields Up" +msgstr "开启防护 (Shields Up)" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277 +msgid "Status" +msgstr "状态" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569 +msgid "Successfully logged out." +msgstr "登出成功。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199 +msgid "TAILSCALE NOT FOUND" +msgstr "未找到 TAILSCALE 主程序" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "TUN Mode" +msgstr "TUN 模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283 +msgid "TX" +msgstr "发送" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232 +msgid "Tailnet Name" +msgstr "Tailnet 名称" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +#: 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:175 +msgid "Tailscale Health Check: %s" +msgstr "Tailscale 健康检查: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279 +msgid "Tailscale IP" +msgstr "Tailscale IP" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230 +msgid "Tailscale IPv4" +msgstr "Tailscale IPv4" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231 +msgid "Tailscale IPv6" +msgstr "Tailscale IPv6" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521 +msgid "Tailscale Login" +msgstr "Tailscale 登录" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +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:264 +msgid "Tailscale status error" +msgstr "Tailscale 状态异常" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523 +msgid "This can take up to 30 seconds." +msgstr "此过程最多可能需要 30 秒。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553 +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:406 +msgid "" +"UDP port for the Peer Relay service. Open this port on your router firewall/" +"NAT." +msgstr "DERP的UDP端口号。需要在防火墙/NAT规则中开放该端口。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +#: 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:153 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Unknown error" +msgstr "未知错误" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 +msgid "Version" +msgstr "版本" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +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:23 +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:472 +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:99 +msgid "ago" +msgstr "前" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448 +msgid "" +"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " +"connections." +msgstr "并启用伪装和MSS钳制(MTU修复)功能,以确保连接稳定。" + +#~ msgid "" +#~ "Completely disable all Tailscale firewall auto-configuration, including " +#~ "zone creation, forwarding rules, and netfilter settings." +#~ msgstr "" +#~ "完全禁用所有 Tailscale 防火墙自动配置,包括区域创建、转发规则和 netfilter " +#~ "设置。" + +#~ msgid "" +#~ "Optional: Specify a custom control server URL (e.g., a Headscale " +#~ "instance, https://example.com)." +#~ 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/luci-app-tailscale-community/po/zh_Hant/community.po b/luci-app-tailscale-community/po/zh_Hant/community.po new file mode 100644 index 000000000..8a1c943e0 --- /dev/null +++ b/luci-app-tailscale-community/po/zh_Hant/community.po @@ -0,0 +1,598 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +msgid "(Experimental) Reduce Memory Usage" +msgstr "(實驗性) 減少記憶體使用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:469 +msgid "1. Select \"Accept Routes\" (to access remote devices)." +msgstr "1. 勾選 接受路由 (以存取遠端設備)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:470 +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:471 +msgid "3. Click \"Auto Configure Firewall\" (to allow traffic forwarding)." +msgstr "3. 點選 自動配置防火牆 (允許流量轉送)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Accept Routes" +msgstr "接受路由" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:478 +msgid "Account Settings" +msgstr "帳號設定" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Advertise Exit Node" +msgstr "宣告出口節點" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +msgid "Advertise Routes" +msgstr "宣告路由" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:437 +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:23 +msgid "Allow LAN Access" +msgstr "允許區網存取" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:21 +msgid "Allow accepting routes announced by other nodes." +msgstr "允許接受由其他節點宣告的路由。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +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:552 +msgid "Are you sure you want to log out?" +msgstr "您確定要登出嗎?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:445 +msgid "Auto Configure Firewall" +msgstr "自動設定防火牆" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:559 +msgid "Cancel" +msgstr "取消" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:503 +msgid "Click to Log out account on this device." +msgstr "點選以登出此裝置上的帳號。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:482 +msgid "Click to get a login URL for this device." +msgstr "點選獲取此裝置的登入 URL。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:169 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:260 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:392 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:601 +msgid "Collecting data ..." +msgstr "正在收集資料..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:578 +msgid "Confirm Logout" +msgstr "確認登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:281 +msgid "Connection Info" +msgstr "連線資訊" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Controls how Tailscale DNS is handled." +msgstr "控制 Tailscale DNS 的處理方式。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:515 +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:487 +msgid "Custom Login Server" +msgstr "自訂登入伺服器" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:495 +msgid "Custom Login Server Auth Key" +msgstr "自訂登入伺服器認證金鑰" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "DNS Mode" +msgstr "DNS 模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:22 +msgid "Declare this device as an Exit Node." +msgstr "將此裝置宣告為出口節點。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:581 +msgid "Devices List" +msgstr "裝置清單" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:29 +msgid "Disable Firewall Configuration" +msgstr "停用防火牆設定" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +msgid "Disable SNAT" +msgstr "停用 SNAT" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:25 +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:29 +msgid "Disable Tailscale netfilter auto-configuration (--netfilter-mode=off)." +msgstr "停用 Tailscale netfilter 自動設定 (--netfilter-mode=off)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Disabled" +msgstr "已停用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "Disabled: system DNS only." +msgstr "已停用: 只有系統 DNS。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:504 +msgid "Disconnect from Tailscale and expire current node key." +msgstr "中斷 Tailscale 連線並使目前節點金鑰過期。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "Enable Peer Relay" +msgstr "啟用對等中繼" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:27 +msgid "Enable Tailscale SSH" +msgstr "啟用 Tailscale SSH" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "Enable Tailscale Service" +msgstr "啟用 Tailscale 服務" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +msgid "Enable Web Interface" +msgstr "啟用 Web 介面" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:19 +msgid "" +"Enable or disable the Tailscale service. When disabled, the service will be " +"stopped and the process will be killed." +msgstr "啟用或停用 Tailscale 服務。停用後,該服務將停止,處理程序將被終止。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:30 +msgid "" +"Enable this device as a Peer Relay server. Requires a public IP and an UDP " +"port open on the router." +msgstr "將此設備啟用為對等中繼伺服器。需要公開 IP 位址,且路由器上需要開啟一個 UDP 連接埠。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "Enabled" +msgstr "已啟用" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:37 +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:540 +msgid "Error" +msgstr "錯誤" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:150 +msgid "Error caching DERP region map: %s" +msgstr "快取 DERP 區域地圖時出錯: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:153 +msgid "Error fetching DERP region map: %s" +msgstr "獲取 DERP 區域地圖時出錯: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +msgid "Error reading cached DERP region map: %s" +msgstr "讀取快取的 DERP 區域地圖時出錯: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:446 +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:303 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +msgid "Exit Node" +msgstr "出口節點" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:24 +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:455 +msgid "Failed to configure firewall: %s" +msgstr "獲取防火牆設定失敗: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:541 +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:546 +msgid "Failed to get login URL: %s" +msgstr "獲取登入 URL 失敗: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:542 +msgid "Failed to get login URL: Invalid response from server." +msgstr "獲取登入 URL 失敗: 伺服器回應無效。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Failed to load logs: %s" +msgstr "載入日誌失敗:%s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:20 +msgid "Filter by hostname, IP, OS..." +msgstr "按主機名稱、IP、系統過濾..." + +msgid "Firewall Mode" +msgstr "防火牆模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:452 +msgid "Firewall configuration applied." +msgstr "已套用防火牆設定" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:401 +msgid "General Settings" +msgstr "一般設定" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:278 +msgid "Hostname" +msgstr "主機名" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:464 +msgid "How to enable Site-to-Site?" +msgstr "如何啟用站對站通訊?" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:483 +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:497 +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:93 +msgid "Invalid Date" +msgstr "無效日期" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:447 +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:96 +msgid "Just now" +msgstr "剛才" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:208 +msgid "LOGGED OUT" +msgstr "已登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:284 +msgid "Last Seen" +msgstr "上次連線" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:489 +msgid "Leave blank for default Tailscale control plane." +msgstr "留白則使用預設的 Tailscale 控制台。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Logging out..." +msgstr "正在登出..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:481 +msgid "Login" +msgstr "登入" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:502 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:575 +msgid "Logout" +msgstr "登出" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +msgid "Logout failed: %s" +msgstr "登出失敗: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:587 +msgid "Logs" +msgstr "日誌" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "MagicDNS: Tailscale overrides resolv.conf." +msgstr "MagicDNS:Tailscale 會覆寫 resolv.conf。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:90 +msgid "N/A" +msgstr "N/A" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:219 +msgid "NOT RUNNING" +msgstr "未執行" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:273 +msgid "No peer devices found." +msgstr "未找到對等裝置。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:246 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:591 +msgid "No tailscale-related logs found." +msgstr "未找到與 tailscale 相關的日誌。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:414 +msgid "None" +msgstr "無" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:91 +msgid "Now" +msgstr "現在" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:280 +msgid "OS" +msgstr "作業系統" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Offline" +msgstr "離線" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:303 +msgid "Online" +msgstr "上線" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward" +msgstr "OpenWrt 轉發" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:28 +msgid "OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)" +msgstr "OpenWrt 轉送:透過 dnsmasq 轉送 MagicDNS。(僅支援 ts.net)" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:488 +msgid "" +"Optional: Specify a custom control server URL (e.g., a Headscale instance, " +"%s)." +msgstr "可選:指定自訂控制伺服器 URL(例如,Headscale 實例,「%s」)。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:496 +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:405 +msgid "Peer Relay Port" +msgstr "對等中繼連接埠" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:210 +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:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:565 +msgid "Please wait." +msgstr "請稍候。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "RUNNING" +msgstr "正在執行" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:282 +msgid "RX" +msgstr "接收" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:596 +msgid "Refresh" +msgstr "重新整理" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:525 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:531 +msgid "Requesting Login URL..." +msgstr "正在請求登入 URL..." + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:522 +msgid "Requesting Tailscale login URL... Please wait." +msgstr "正在請求 Tailscale 登入 URL... 請稍候。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:413 +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:20 +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:198 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:206 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:218 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:227 +msgid "Service Status" +msgstr "服務狀態" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +msgid "Shields Up" +msgstr "開啟防護 (Shields Up)" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:277 +msgid "Status" +msgstr "狀態" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:569 +msgid "Successfully logged out." +msgstr "登出成功。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:199 +msgid "TAILSCALE NOT FOUND" +msgstr "找不到 TAILSCALE" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:229 +msgid "TUN Mode" +msgstr "TUN 模式" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:283 +msgid "TX" +msgstr "傳送" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:232 +msgid "Tailnet Name" +msgstr "Tailnet 名稱" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +#: 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:175 +msgid "Tailscale Health Check: %s" +msgstr "Tailscale 健康檢查: %s" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:279 +msgid "Tailscale IP" +msgstr "Tailscale IP" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:230 +msgid "Tailscale IPv4" +msgstr "Tailscale IPv4" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:231 +msgid "Tailscale IPv6" +msgstr "Tailscale IPv6" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:521 +msgid "Tailscale Login" +msgstr "Tailscale 登入" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:366 +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:264 +msgid "Tailscale status error" +msgstr "Tailscale 狀態錯誤" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:523 +msgid "This can take up to 30 seconds." +msgstr "此過程最多可能需要 30 秒。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:553 +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:406 +msgid "" +"UDP port for the Peer Relay service. Open this port on your router firewall/" +"NAT." +msgstr "用於對等中繼服務的 UDP 連接埠。請在路由器防火牆上開啟此連接埠以及 NAT。" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:123 +#: 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:153 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:546 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:572 +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:609 +msgid "Unknown error" +msgstr "未知錯誤" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:228 +msgid "Version" +msgstr "版本" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:26 +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:23 +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:472 +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:99 +msgid "ago" +msgstr "前" + +#: applications/luci-app-tailscale-community/htdocs/luci-static/resources/view/tailscale.js:448 +msgid "" +"and enables Masquerading and MSS Clamping (MTU fix) to ensure stable " +"connections." +msgstr "並啟用地址偽裝和 MSS鉗制確保連線穩定。" + +#~ msgid "" +#~ "Completely disable all Tailscale firewall auto-configuration, including " +#~ "zone creation, forwarding rules, and netfilter settings." +#~ msgstr "" +#~ "完全停用所有 Tailscale 防火牆自動設定,包括區域建立、轉送規則和 netfilter " +#~ "設定。" + +#~ msgid "" +#~ "Optional: Specify a custom control server URL (e.g., a Headscale " +#~ "instance, https://example.com)." +#~ 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/luci-app-tailscale-community/root/etc/init.d/tailscale-settings b/luci-app-tailscale-community/root/etc/init.d/tailscale-settings new file mode 100755 index 000000000..262654a0b --- /dev/null +++ b/luci-app-tailscale-community/root/etc/init.d/tailscale-settings @@ -0,0 +1,237 @@ +#!/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 $? +} + +# Add/remove DNS forward entry in dnsmasq for MagicDNS via OpenWrt +setup_tailscale_dns_forward() { + if [ ! -x /usr/sbin/tailscale ] && [ ! -x /usr/bin/tailscale ]; then + return 0 + fi + + local entry="/.ts.net/100.100.100.100" + local count + count=$(uci -q get dhcp.@dnsmasq[0].server 2>/dev/null | tr ' ' '\n' | grep -Fxc "$entry" 2>/dev/null) + + if [ "$count" -gt 1 ]; then + uci -q del_list dhcp.@dnsmasq[0].server="$entry" + uci add_list dhcp.@dnsmasq[0].server="$entry" + uci commit dhcp + /etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null + logger -t "$NAME" "Collapsed duplicate DNS forward entries" + elif [ "$count" -eq 0 ]; then + uci add_list dhcp.@dnsmasq[0].server="$entry" + uci commit dhcp + /etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null + logger -t "$NAME" "Added DNS forward: $entry" + fi +} + +remove_tailscale_dns_forward() { + local entry="/.ts.net/100.100.100.100" + local idx=0 + local removed=0 + while true; do + local val + val=$(uci -q get dhcp.@dnsmasq[0].server."${idx}" 2>/dev/null) + [ -z "$val" ] && break + if [ "$val" = "$entry" ]; then + uci -q del_list dhcp.@dnsmasq[0].server="$entry" + removed=1 + break + fi + idx=$((idx + 1)) + done + + if [ "$removed" = "1" ]; then + uci commit dhcp + /etc/init.d/dnsmasq reload 2>/dev/null || /etc/init.d/dnsmasq restart 2>/dev/null + logger -t "$NAME" "Removed Tailscale DNS forward entry: $entry" + fi +} + +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 shields_up runwebclient nosnat hostname + local enable_relay relay_server_port disable_fw_config + + 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 dns_mode settings dns_mode 'disabled' + 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' + config_get disable_fw_config settings disable_fw_config '0' + + # 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 based on dns_mode + case "$dns_mode" in + magicdns) + set -- "$@" --accept-dns=true + remove_tailscale_dns_forward + ;; + openwrt_forward) + set -- "$@" --accept-dns=false + setup_tailscale_dns_forward + ;; + *) + # disabled (default) + set -- "$@" --accept-dns=false + remove_tailscale_dns_forward + ;; + esac + + # --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 + + # --netfilter-mode (only when disable_fw_config is enabled and tailscale init doesn't already handle it) + if [ "$disable_fw_config" = "1" ]; then + set -- "$@" --netfilter-mode=off + 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 +} + +handle_service_state() { + config_load tailscale + local service_enabled + config_get service_enabled settings service_enabled '1' + + if [ "$service_enabled" = "0" ]; then + logger -t "$NAME" "Service is disabled, stopping tailscale" + /etc/init.d/tailscale stop 2>/dev/null + /etc/init.d/tailscale disable 2>/dev/null + killall tailscaled 2>/dev/null + else + logger -t "$NAME" "Service is enabled, ensuring tailscale is running" + /etc/init.d/tailscale enable 2>/dev/null + /etc/init.d/tailscale start 2>/dev/null + apply_settings + fi +} + +start_service() { + apply_settings +} + +service_triggers() { + procd_add_reload_trigger "tailscale" +} + +reload_service() { + handle_service_state +} 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 new file mode 100644 index 000000000..49fc63001 --- /dev/null +++ b/luci-app-tailscale-community/root/usr/share/luci/menu.d/luci-app-tailscale-community.json @@ -0,0 +1,10 @@ +{ + "admin/vpn/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 new file mode 100644 index 000000000..2b9ea0724 --- /dev/null +++ b/luci-app-tailscale-community/root/usr/share/rpcd/acl.d/luci-app-tailscale-community.json @@ -0,0 +1,26 @@ +{ + "luci-app-tailscale-community": { + "description": "Allow user access to tailscale", + "read": { + "ubus": { + "tailscale": [ + "get_status", + "get_settings", + "get_subroutes", + "get_logs" + ] + }, + "uci": [ "tailscale" ] + }, + "write": { + "ubus": { + "tailscale": [ + "do_login", + "do_logout", + "setup_firewall" + ] + }, + "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 new file mode 100755 index 000000000..4f2e0e80f --- /dev/null +++ b/luci-app-tailscale-community/root/usr/share/rpcd/ucode/tailscale.uc @@ -0,0 +1,377 @@ +#!/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: false, + health: '', + ipv4: "Not running", + ipv6: "", + 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 && length(status_data.Health) > 0) ? status_data.Health[0] : ''; + 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] || ''; + data.domain_name = status_data?.CurrentTailnet?.Name || ''; + + // peers list + if (status_data?.Peer) { + for (let p in status_data.Peer) { + let peer = status_data.Peer[p]; + peer_map[peer.ID] = { + ip: join('
', peer?.TailscaleIPs || []), + hostname: split(peer?.DNSName || '', '.')[0] || peer?.HostName || '', + ostype: peer?.OS || '', + online: !!peer?.Online, + linkadress: (!peer?.CurAddr) ? (peer?.Relay || '') : peer?.CurAddr, + lastseen: peer?.LastSeen || '', + exit_node: !!peer?.ExitNode, + exit_node_option: !!peer?.ExitNodeOption, + tx: peer?.TxBytes || 0, + rx: peer?.RxBytes || 0 + }; + } + } + } 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.dns_mode = uci.get('tailscale', 'settings', 'dns_mode') || 'disabled'; + settings.fw_mode = split(uci.get('tailscale', 'settings', 'fw_mode'),' ')[0] || 'nftables'; + } + } + } catch (e) { /* ignore */ } + } + return settings; + } +}; + + +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 ' + shell_quote(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('tailscale'); + + 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 ts_zone_section = null; + let fwd_lan_to_ts = false; + let fwd_ts_to_lan = false; + + uci.foreach('firewall', 'zone', function(s) { + if (s['name'] == 'tailscale') + ts_zone_section = s['.name']; + }); + uci.foreach('firewall', 'forwarding', function(s) { + 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 (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; + } + + // Exit node requires WAN <- tailscale forwarding + let fwd_ts_to_wan = false; + uci.foreach('firewall', 'forwarding', function(s) { + if (s['src'] == 'tailscale' && s['dest'] == 'wan') fwd_ts_to_wan = true; + }); + + if (!fwd_ts_to_wan) { + let fid = uci.add('firewall', 'forwarding'); + uci.set('firewall', fid, 'src', 'tailscale'); + uci.set('firewall', fid, 'dest', 'wan'); + 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 || '') }; + } + } +}; + +methods.get_logs = { + args: { lines: 200 }, + call: function(request) { + let lines = int(request?.args?.lines) || 200; + let cmd = 'logread -l ' + lines + ' 2>/dev/null | grep -i -E "tailscale" || true'; + let result = exec(cmd); + if (result.code == 0) { + return { logs: result.stdout }; + } + return { logs: [], error: join(' ', result.stderr) }; + } +}; + +return { 'tailscale': methods };