diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js index a5ddfb8..c27548a 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js @@ -5,12 +5,25 @@ 'require network'; 'require fs'; +function formatDiagnosticOutput(output) { + if (!output) return ''; + return output.trim() + .replace(/\x1b\[[0-9;]*m/g, '') // Remove ANSI color codes + .replace(/\r\n/g, '\n') // Normalize line endings + .replace(/\r/g, '\n'); +} + return view.extend({ async render() { document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', ` + `); var m, s, o; @@ -21,6 +34,7 @@ return view.extend({ m.title = _('Podkop') + ' v' + res.stdout.trim(); } }); + s = m.section(form.TypedSection, 'main'); s.anonymous = true; @@ -43,6 +57,144 @@ return view.extend({ o.depends('proxy_config_type', 'url'); o.rows = 5; o.ucisection = 'main'; + o.validate = function (section_id, value) { + if (!value || value.length === 0) { + return true; + } + + try { + // Check if it's a valid URL format + if (!value.startsWith('vless://') && !value.startsWith('ss://')) { + return _('URL must start with vless:// or ss://'); + } + + // For Shadowsocks + if (value.startsWith('ss://')) { + let encrypted_part; + try { + // Split URL properly handling both old and new formats + let mainPart = value.includes('?') ? value.split('?')[0] : value.split('#')[0]; + encrypted_part = mainPart.split('/')[2].split('@')[0]; + + // Try base64 decode first (for old format) + try { + let decoded = atob(encrypted_part); + if (!decoded.includes(':')) { + // Not old format, check if it's 2022 format + if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) { + return _('Invalid Shadowsocks URL format: missing method and password separator ":"'); + } + } + } catch (e) { + // If base64 decode fails, check if it's 2022 format + if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) { + return _('Invalid Shadowsocks URL format: missing method and password separator ":"'); + } + } + } catch (e) { + return _('Invalid Shadowsocks URL format'); + } + + // Check server and port + try { + let serverPart = value.split('@')[1]; + if (!serverPart) { + return _('Invalid Shadowsocks URL: missing server address'); + } + let [server, portAndRest] = serverPart.split(':'); + if (!server) { + return _('Invalid Shadowsocks URL: missing server'); + } + let port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; + if (!port) { + return _('Invalid Shadowsocks URL: missing port'); + } + let portNum = parseInt(port); + if (isNaN(portNum) || portNum < 1 || portNum > 65535) { + return _('Invalid port number. Must be between 1 and 65535'); + } + } catch (e) { + return _('Invalid Shadowsocks URL: missing or invalid server/port format'); + } + } + + // For VLESS + if (value.startsWith('vless://')) { + // Check UUID + let uuid = value.split('/')[2].split('@')[0]; + if (!uuid || uuid.length === 0) { + return _('Invalid VLESS URL: missing UUID'); + } + + // Check server and port + try { + let serverPart = value.split('@')[1]; + if (!serverPart) { + return _('Invalid VLESS URL: missing server address'); + } + let [server, portAndRest] = serverPart.split(':'); + if (!server) { + return _('Invalid VLESS URL: missing server'); + } + // Handle cases where port might be followed by / or ? or # + let port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null; + if (!port && port !== '') { // Allow empty port for specific cases + return _('Invalid VLESS URL: missing port'); + } + if (port !== '') { // Only validate port if it's not empty + let portNum = parseInt(port); + if (isNaN(portNum) || portNum < 1 || portNum > 65535) { + return _('Invalid port number. Must be between 1 and 65535'); + } + } + } catch (e) { + return _('Invalid VLESS URL: missing or invalid server/port format'); + } + + // Parse query parameters + let queryString = value.split('?')[1]; + if (!queryString) { + return _('Invalid VLESS URL: missing query parameters'); + } + + let params = new URLSearchParams(queryString.split('#')[0]); + + // Check type parameter + let type = params.get('type'); + if (!type) { + return _('Invalid VLESS URL: missing type parameter'); + } + + // Check security parameter + let security = params.get('security'); + if (!security) { + return _('Invalid VLESS URL: missing security parameter'); + } + + // If security is "reality", check required reality parameters + if (security === 'reality') { + if (!params.get('pbk')) { + return _('Invalid VLESS URL: missing pbk parameter for reality security'); + } + if (!params.get('fp')) { + return _('Invalid VLESS URL: missing fp parameter for reality security'); + } + } + + // If security is "tls", check required TLS parameters + if (security === 'tls') { + if (!params.get('sni') && type !== 'tcp') { + return _('Invalid VLESS URL: missing sni parameter for tls security'); + } + } + } + + return true; + } catch (e) { + console.error('Validation error:', e); + return _('Invalid URL format: ' + e.message); + } + }; o = s.taboption('basic', form.TextValue, 'outbound_json', _('Outbound Configuration'), _('Enter complete outbound configuration in JSON format')); o.depends('proxy_config_type', 'outbound'); @@ -153,7 +305,7 @@ return view.extend({ if (removedServices.length > 0) { newValues = newValues.filter(v => allowedWithRussiaInside.includes(v)); - const warningMsg = _('Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s have been removed from selection.').format( + const warningMsg = _('Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s already in Russia inside and have been removed from selection.').format( removedServices.join(', ') ); @@ -211,7 +363,7 @@ return view.extend({ o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)')); o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com'; o.depends('custom_domains_list_type', 'text'); - o.rows = 10; + o.rows = 8; o.rmempty = false; o.ucisection = 'main'; o.validate = function (section_id, value) { @@ -469,7 +621,6 @@ return view.extend({ o.ucisection = 'main'; // Additional Settings Tab - o = s.tab('additional', _('Additional Settings')); o = s.taboption('additional', form.Flag, 'yacd', _('Yacd enable'), _('http://openwrt.lan:9090/ui')); @@ -497,422 +648,604 @@ return view.extend({ o.rmempty = false; o.ucisection = 'main'; + // Diagnostics tab o = s.tab('diagnostics', _('Diagnostics')); - function formatDiagnosticOutput(output) { - if (!output) return ''; + // Service Status Section + let createStatusSection = function (podkopStatus, singboxStatus, podkop, luci, singbox, system) { + return E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Service Status')), + E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [ + // Podkop Column + E('div', { 'style': 'flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;' }, [ + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('strong', {}, _('Podkop Status')), + E('br'), + E('span', { 'style': `color: ${podkopStatus.running ? '#4caf50' : '#f44336'}` }, [ + podkopStatus.running ? '✔' : '✘', + ' ', + podkopStatus.status + ]) + ]), + E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ + podkopStatus.running ? + E('button', { + 'class': 'btn cbi-button-remove', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['stop']) + .then(() => location.reload()); + } + }, _('Stop Podkop')) : + E('button', { + 'class': 'btn cbi-button-apply', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['start']) + .then(() => location.reload()); + } + }, _('Start Podkop')), + E('button', { + 'class': 'btn cbi-button-apply', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['restart']) + .then(() => location.reload()); + } + }, _('Restart Podkop')), + E('button', { + 'class': 'btn cbi-button-' + (podkopStatus.enabled ? 'remove' : 'apply'), + 'click': function () { + return fs.exec('/etc/init.d/podkop', [podkopStatus.enabled ? 'disable' : 'enable']) + .then(() => location.reload()); + } + }, podkopStatus.enabled ? _('Disable Podkop') : _('Enable Podkop')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['show_config']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Podkop Configuration'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Show Config')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['check_logs']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Podkop Logs'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('View Logs')) + ]) + ]), + // Sing-box Column + E('div', { 'style': 'flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;' }, [ + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('strong', {}, _('Sing-box Status')), + E('br'), + E('span', { 'style': `color: ${singboxStatus.running ? '#4caf50' : '#f44336'}` }, [ + singboxStatus.running ? '✔' : '✘', + ' ', + `${singboxStatus.status}` + ]) + ]), + E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['show_sing_box_config']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Sing-box Configuration'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Show Config')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['check_sing_box_logs']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Sing-box Logs'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('View Logs')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['check_sing_box_connections']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Active Connections'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Check Connections')) + ]) + ]), + // Diagnostics Column + E('div', { 'style': 'flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;' }, [ + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('strong', {}, _('FakeIP Status')), + E('div', { 'id': 'fakeip-status' }, [ + E('span', {}, _('Checking FakeIP...')) + ]) + ]), + E('div', { 'class': 'btn-group', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['check_nft']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('NFT Rules'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Check NFT Rules')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['check_dnsmasq']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('DNSMasq Configuration'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Check DNSMasq')), + E('button', { + 'class': 'btn', + 'click': function () { + return fs.exec('/etc/init.d/podkop', ['list_update']) + .then(function (res) { + const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); + ui.showModal(_('Lists Update Results'), [ + E('div', { + style: 'max-height: 70vh;' + + 'overflow-y: auto;' + + 'margin: 1em 0;' + + 'padding: 1.5em;' + + 'background: #f8f9fa;' + + 'border: 1px solid #e9ecef;' + + 'border-radius: 4px;' + + 'font-family: monospace;' + + 'white-space: pre-wrap;' + + 'word-wrap: break-word;' + + 'line-height: 1.5;' + + 'font-size: 14px;' + }, [ + E('pre', { style: 'margin: 0;' }, formattedOutput) + ]), + E('div', { + style: 'display: flex; justify-content: space-between; margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': function (ev) { + const textarea = document.createElement('textarea'); + textarea.value = '```txt\n' + formattedOutput + '\n```'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + ev.target.textContent = _('Copied!'); + setTimeout(() => { + ev.target.textContent = _('Copy to Clipboard'); + }, 1000); + } catch (err) { + ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); + } + document.body.removeChild(textarea); + } + }, _('Copy to Clipboard')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]); + }); + } + }, _('Update Lists')) + ]) + ]), + // Version Information Column + E('div', { 'style': 'flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;' }, [ + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('strong', {}, _('Version Information')), + E('br'), + E('div', { 'style': 'margin-top: 10px; font-family: monospace; white-space: pre-wrap;' }, [ + E('strong', {}, 'Podkop: '), podkop.stdout ? podkop.stdout.trim() : _('Unknown'), '\n', + E('strong', {}, 'LuCI App: '), luci.stdout ? luci.stdout.trim() : _('Unknown'), '\n', + E('strong', {}, 'Sing-box: '), singbox.stdout ? singbox.stdout.trim() : _('Unknown'), '\n', + E('strong', {}, 'OpenWrt Version: '), system.stdout ? system.stdout.split('\n')[1].trim() : _('Unknown'), '\n', + E('strong', {}, 'Device Model: '), system.stdout ? system.stdout.split('\n')[4].trim() : _('Unknown') + ]) + ]) + ]), + ]) + ]); + }; - return output - .replace(/\x1B\[[0-9;]*[mK]/g, '') - .replace(/\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\] /g, '') - .replace(/\n{3,}/g, '\n\n') - .replace(/===\s+(.*?)\s+===/g, (_, title) => `\n${title}\n${'─'.repeat(title.length)}`) - .replace(/^Checking\s+(.+)\.{3}/gm, '► Checking $1...') - .replace(/:\s+(available|not found)$/gm, (_, status) => - `: ${status === 'available' ? '✓' : '✗'}`); + o = s.taboption('diagnostics', form.DummyValue, '_status'); + o.rawhtml = true; + o.cfgvalue = function () { + return E('div', { id: 'diagnostics-status' }, _('Loading diagnostics...')); + }; + + function checkFakeIP() { + fetch('http://httpbin.org/ip') + .then(response => response.text()) + .then(text => { + const statusElement = document.getElementById('fakeip-status'); + if (!statusElement) return; + + console.log('FakeIP check response:', text); + + if (text.includes('Cannot GET /ip')) { + console.log('FakeIP status: working (Cannot GET /ip)'); + statusElement.innerHTML = E('span', { 'style': 'color: #4caf50' }, [ + '✔ ', + _('working') + ]).outerHTML; + } else if (text.includes('"origin":')) { + console.log('FakeIP status: not working (got IP response)'); + statusElement.innerHTML = E('span', { 'style': 'color: #f44336' }, [ + '✘ ', + _('not working') + ]).outerHTML; + } else { + console.log('FakeIP status: check error (unexpected response)'); + statusElement.innerHTML = E('span', { 'style': 'color: #ff9800' }, [ + '! ', + _('check error') + ]).outerHTML; + } + }) + .catch(error => { + console.log('FakeIP check error:', error.message); + const statusElement = document.getElementById('fakeip-status'); + if (statusElement) { + statusElement.innerHTML = E('span', { 'style': 'color: #ff9800' }, 'check error').outerHTML; + } + }); } - // Connection Checks Section - o = s.taboption('diagnostics', form.Button, '_check_nft'); - o.title = _('NFT Rules'); - o.description = _('Show current nftables rules and statistics'); - o.inputtitle = _('Check Rules'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['check_nft']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('NFT Rules'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - - - // Logs Section - o = s.taboption('diagnostics', form.Button, '_check_sing_box_logs'); - o.title = _('Sing-Box Logs'); - o.description = _('View recent sing-box logs from system journal'); - o.inputtitle = _('View Sing-Box Logs'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['check_sing_box_logs']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Sing-Box Logs'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - o = s.taboption('diagnostics', form.Button, '_check_logs'); - o.title = _('Podkop Logs'); - o.description = _('View recent podkop logs from system journal'); - o.inputtitle = _('View Podkop Logs'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['check_logs']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Podkop Logs'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - // Configurations Section - o = s.taboption('diagnostics', form.Button, '_check_sing_box_connections'); - o.title = _('Active Connections'); - o.description = _('View active sing-box network connections'); - o.inputtitle = _('Check Connections'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['check_sing_box_connections']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Active Connections'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - o = s.taboption('diagnostics', form.Button, '_check_dnsmasq'); - o.title = _('DNSMasq Configuration'); - o.description = _('View current DNSMasq configuration settings'); - o.inputtitle = _('Check DNSMasq'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['check_dnsmasq']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('DNSMasq Configuration'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - o = s.taboption('diagnostics', form.Button, '_show_sing_box_config'); - o.title = _('Sing-Box Configuration'); - o.description = _('Show current sing-box configuration'); - o.inputtitle = _('Show Sing-Box Config'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['show_sing_box_config']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Sing-Box Configuration'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = '```json\n' + formattedOutput + '\n```'; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - o = s.taboption('diagnostics', form.Button, '_show_config'); - o.title = _('Podkop Configuration'); - o.description = _('Show current podkop configuration with masked sensitive data'); - o.inputtitle = _('Show Config'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['show_config']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Podkop Configuration'), [ - E('div', { - style: - 'max-height: 70vh;' + - 'overflow-y: auto;' + - 'margin: 1em 0;' + - 'padding: 1.5em;' + - 'background: #f8f9fa;' + - 'border: 1px solid #e9ecef;' + - 'border-radius: 4px;' + - 'font-family: monospace;' + - 'white-space: pre-wrap;' + - 'word-wrap: break-word;' + - 'line-height: 1.5;' + - 'font-size: 14px;' - }, [ - E('pre', { style: 'margin: 0;' }, formattedOutput) - ]), - E('div', { - style: 'display: flex; justify-content: space-between; margin-top: 1em;' - }, [ - E('button', { - 'class': 'btn', - 'click': function () { - const textarea = document.createElement('textarea'); - textarea.value = formattedOutput; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); - } - }, _('Copy to Clipboard')), - E('button', { - 'class': 'btn', - 'click': ui.hideModal - }, _('Close')) - ]) - ]); - }); - }; - - o = s.taboption('diagnostics', form.Button, '_list_update'); - o.title = _('Update Lists'); - o.description = _('Update all lists in config'); - o.inputtitle = _('Update Lists'); - o.inputstyle = 'apply'; - o.onclick = function () { - return fs.exec('/etc/init.d/podkop', ['list_update']) - .then(function (res) { - const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - ui.showModal(_('Lists Update Results'), [ - E('div', { style: 'white-space:pre-wrap;padding:5px' }, formattedOutput), - E('div', { class: 'right' }, E('button', { - class: 'btn', - click: ui.hideModal - }, _('Close'))) - ]); - }); - }; + function updateDiagnostics() { + Promise.all([ + fs.exec('/etc/init.d/podkop', ['get_status']), + fs.exec('/etc/init.d/podkop', ['get_sing_box_status']), + fs.exec('/etc/init.d/podkop', ['show_version']), + fs.exec('/etc/init.d/podkop', ['show_luci_version']), + fs.exec('/etc/init.d/podkop', ['show_sing_box_version']), + fs.exec('/etc/init.d/podkop', ['show_system_info']) + ]).then(function ([podkopStatus, singboxStatus, podkop, luci, singbox, system]) { + try { + const parsedPodkopStatus = JSON.parse(podkopStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); + const parsedSingboxStatus = JSON.parse(singboxStatus.stdout || '{"running":0,"enabled":0,"status":"unknown"}'); + var newContent = createStatusSection(parsedPodkopStatus, parsedSingboxStatus, podkop, luci, singbox, system); + var container = document.getElementById('diagnostics-status'); + if (container) { + container.innerHTML = ''; + container.appendChild(newContent); + checkFakeIP(); + } + } catch (e) { + console.error('Error parsing diagnostics status:', e); + var container = document.getElementById('diagnostics-status'); + if (container) { + container.innerHTML = '
'; + } + } + }); + } // Add new section 'extra' var s = m.section(form.TypedSection, 'extra', _('Extra configurations')); @@ -1119,7 +1452,6 @@ return view.extend({ return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y'); } - // Разбираем IP и маску const [ip, cidr] = value.split('/'); const ipParts = ip.split('.'); @@ -1240,6 +1572,15 @@ return view.extend({ // o = s.taboption('basic', form.Flag, 'socks5', _('Mixed enable'), _('Browser port: 2080 (extra +1)')); // o.default = '0'; // o.rmempty = false; - return m.render(); + + let map_promise = m.render(); + + map_promise.then(node => { + node.classList.add('fade-in'); + updateDiagnostics(); + return node; + }); + + return map_promise; } }); \ No newline at end of file diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 1b2aecf..b5afa80 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -25,44 +25,59 @@ msgstr "Тип подключения" msgid "Select between VPN and Proxy connection methods for traffic routing" msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" +msgid "Configuration Type" +msgstr "Тип конфигурации" + +msgid "Select how to configure the proxy" +msgstr "Выберите способ настройки прокси" + +msgid "Connection URL" +msgstr "URL подключения" + +msgid "Outbound Config" +msgstr "Конфигурация Outbound" + msgid "Proxy Configuration URL" msgstr "URL конфигурации прокси" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси" +msgid "Outbound Configuration" +msgstr "Конфигурация исходящего соединения" + +msgid "Enter complete outbound configuration in JSON format" +msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" + msgid "Network Interface" msgstr "Сетевой интерфейс" msgid "Select network interface for VPN connection" msgstr "Выберите сетевой интерфейс для VPN подключения" -msgid "Community Domain Lists" -msgstr "Предустановленные списки доменов" +msgid "Community Lists" +msgstr "Предустановленные списки" -msgid "Domain List" -msgstr "Список доменов" +msgid "Service List" +msgstr "Список сервисов" -msgid "Select a list" -msgstr "Выберите список доменов" +msgid "Select predefined service for routing" +msgstr "Выберите предустановленные сервисы для маршрутизации" -msgid "Community Subnet Lists" -msgstr "Предустановленные сети сервисов" +msgid "User Domain List Type" +msgstr "Тип пользовательского списка доменов" -msgid "Enable routing for popular services like Twitter, Meta, and Discord" -msgstr "Включить маршрутизацию для популярных сервисов, таких как Twitter, Meta и Discord" +msgid "Select how to add your custom domains" +msgstr "Выберите способ добавления пользовательских доменов" -msgid "Service Networks" -msgstr "Сети сервисов" +msgid "Disabled" +msgstr "Отключено" -msgid "Select predefined service networks for routing" -msgstr "Выберите предустановленные сети сервисов для маршрутизации" +msgid "Dynamic List" +msgstr "Динамический список" -msgid "User Domain List" -msgstr "Пользовательский список доменов" - -msgid "Enable and manage your custom list of domains for selective routing" -msgstr "Включить и управлять пользовательским списком доменов для выборочной маршрутизации" +msgid "Text List" +msgstr "Текстовый список" msgid "User Domains" msgstr "Пользовательские домены" @@ -70,6 +85,24 @@ msgstr "Пользовательские домены" msgid "Enter domain names without protocols (example: sub.example.com or example.com)" msgstr "Введите имена доменов без протоколов (пример: sub.example.com или example.com)" +msgid "User Domains List" +msgstr "Список пользовательских доменов" + +msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" +msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)" + +msgid "Local Domain Lists" +msgstr "Локальные списки доменов" + +msgid "Use the list from the router filesystem" +msgstr "Использовать список из файловой системы роутера" + +msgid "Local Domain Lists Path" +msgstr "Путь к локальным спискам доменов" + +msgid "Enter to the list file path" +msgstr "Введите путь к файлу списка" + msgid "Remote Domain Lists" msgstr "Удаленные списки доменов" @@ -82,17 +115,26 @@ msgstr "URL удаленных доменов" msgid "Enter full URLs starting with http:// or https://" msgstr "Введите полные URL, начинающиеся с http:// или https://" -msgid "User Subnet List" -msgstr "Пользовательский список подсетей" +msgid "User Subnet List Type" +msgstr "Тип пользовательского списка подсетей" -msgid "Enable and manage your custom list of IP subnets for selective routing" -msgstr "Включить и управлять пользовательским списком IP-подсетей для выборочной маршрутизации" +msgid "Select how to add your custom subnets" +msgstr "Выберите способ добавления пользовательских подсетей" + +msgid "Text List (comma/space/newline separated)" +msgstr "Текстовый список (разделенный запятыми/пробелами/новыми строками)" msgid "User Subnets" msgstr "Пользовательские подсети" -msgid "Enter subnet in CIDR notation (example: 103.21.244.0/22)" -msgstr "Введите подсеть в нотации CIDR (пример: 103.21.244.0/22)" +msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" +msgstr "Введите подсети в нотации CIDR (пример: 103.21.244.0/22) или отдельные IP-адреса" + +msgid "User Subnets List" +msgstr "Список пользовательских подсетей" + +msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline" +msgstr "Введите подсети в нотации CIDR или отдельные IP-адреса через запятую, пробел или новую строку" msgid "Remote Subnet Lists" msgstr "Удаленные списки подсетей" @@ -103,18 +145,6 @@ msgstr "Загрузка и использование списков подсе msgid "Remote Subnet URLs" msgstr "URL удаленных подсетей" -msgid "Domain Exclusions" -msgstr "Исключения доменов" - -msgid "Exclude specific domains from routing rules" -msgstr "Исключить определенные домены из правил маршрутизации" - -msgid "Excluded Domains" -msgstr "Исключенные домены" - -msgid "Domains to be excluded from routing" -msgstr "Домены, которые будут исключены из маршрутизации" - msgid "IP for full redirection" msgstr "Принудительные прокси IP" @@ -133,6 +163,27 @@ msgstr "Исключения прокси IP" msgid "Specify local IP addresses that will never use the configured route" msgstr "Укажите локальные IP-адреса, которые никогда не будут использовать настроенный маршрут" +msgid "Mixed enable" +msgstr "Включить смешанный режим" + +msgid "Browser port: 2080" +msgstr "Порт браузера: 2080" + +msgid "Yacd enable" +msgstr "Включить Yacd" + +msgid "Exclude NTP" +msgstr "Исключить NTP" + +msgid "For issues with open connections sing-box" +msgstr "Для проблем с открытыми соединениями sing-box" + +msgid "QUIC disable" +msgstr "Отключить QUIC" + +msgid "For issues with the video stream" +msgstr "Для проблем с видеопотоком" + msgid "List Update Frequency" msgstr "Частота обновления списков" @@ -145,6 +196,9 @@ msgstr "Каждый час" msgid "Every 2 hours" msgstr "Каждые 2 часа" +msgid "Every 3 hours" +msgstr "Каждые 3 часа" + msgid "Every 4 hours" msgstr "Каждые 4 часа" @@ -154,51 +208,18 @@ msgstr "Каждые 6 часов" msgid "Every 12 hours" msgstr "Каждые 12 часов" +msgid "Every day" +msgstr "Каждый день" + +msgid "Every 3 days" +msgstr "Каждые 3 дня" + msgid "Once a day at 04:00" msgstr "Раз в день в 04:00" msgid "Once a week on Sunday at 04:00" msgstr "Раз в неделю в воскресенье в 04:00" -msgid "Yacd enable" -msgstr "Включить Yacd" - -msgid "Mixed enable" -msgstr "Включить смешанный режим" - -msgid "Browser port: 2080" -msgstr "Порт браузера: 2080" - -msgid "Exclude NTP" -msgstr "Исключить NTP" - -msgid "For issues with open connections sing-box" -msgstr "Для проблем с открытыми соединениями sing-box" - -msgid "Service Domain List Enable" -msgstr "Включить список доменов сервисов" - -msgid "Enable predefined service domain lists for routing" -msgstr "Включить предустановленные списки доменов для маршрутизации" - -msgid "Service List" -msgstr "Список сервисов" - -msgid "Select predefined services for routing" -msgstr "Выберите предустановленные сервисы для маршрутизации" - -msgid "Domains" -msgstr "Домены" - -msgid "Subnet List" -msgstr "Список подсетей" - -msgid "Configure custom subnets for routing" -msgstr "Настройка пользовательских подсетей для маршрутизации" - -msgid "Subnets" -msgstr "Подсети" - msgid "Invalid domain format. Enter domain without protocol (example: sub.example.com)" msgstr "Неверный формат домена. Введите домен без протокола (пример: sub.example.com)" @@ -208,8 +229,8 @@ msgstr "URL должен использовать протокол http:// ил msgid "Invalid URL format. URL must start with http:// or https://" msgstr "Неверный формат URL. URL должен начинаться с http:// или https://" -msgid "Invalid subnet format. Use format: X.X.X.X/Y (like 192.168.1.0/24)" -msgstr "Неверный формат подсети. Используйте формат: X.X.X.X/Y (например: 192.168.1.0/24)" +msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте формат: X.X.X.X или X.X.X.X/Y" msgid "IP address parts must be between 0 and 255" msgstr "Части IP-адреса должны быть между 0 и 255" @@ -220,71 +241,23 @@ msgstr "CIDR должен быть между 0 и 32" msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)" msgstr "Неверный формат IP. Используйте формат: X.X.X.X (например: 192.168.1.1)" -msgid "User Domain List Type" -msgstr "Тип пользовательского списка доменов" - -msgid "Select how to add your custom domains" -msgstr "Выберите способ добавления пользовательских доменов" - -msgid "Disabled" -msgstr "Отключено" - -msgid "Dynamic List" -msgstr "Динамический список" - -msgid "Text List" -msgstr "Текстовый список" - -msgid "User Domains List" -msgstr "Список пользовательских доменов" - -msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" -msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)" - msgid "Invalid domain format: %s. Enter domain without protocol" msgstr "Неверный формат домена: %s. Введите домен без протокола" -msgid "User Subnet List Type" -msgstr "Тип пользовательского списка подсетей" - -msgid "Select how to add your custom subnets" -msgstr "Выберите способ добавления пользовательских подсетей" - -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (разделенный запятыми/пробелами/новыми строками)" - -msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" -msgstr "Введите подсети в нотации CIDR (пример: 103.21.244.0/22) или отдельные IP-адреса" - -msgid "User Subnets List" -msgstr "Список пользовательских подсетей" - -msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline" -msgstr "Введите подсети в нотации CIDR или отдельные IP-адреса через запятую, пробел или новую строку" - -msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте формат: X.X.X.X или X.X.X.X/Y" +msgid "Invalid format: %s. Use format: X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат: %s. Используйте формат: X.X.X.X или X.X.X.X/Y" msgid "IP parts must be between 0 and 255 in: %s" msgstr "Части IP-адреса должны быть между 0 и 255 в: %s" -msgid "Configuration Type" -msgstr "Тип конфигурации" +msgid "CIDR must be between 0 and 32 in: %s" +msgstr "CIDR должен быть между 0 и 32 в: %s" -msgid "Select how to configure the proxy" -msgstr "Выберите способ настройки прокси" +msgid "Invalid path format. Path must start with \"/\" and contain only valid characters (letters, numbers, \"-\", \"_\", \"/\", \".\")" +msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать только допустимые символы (буквы, цифры, \"-\", \"_\", \"/\", \".\")" -msgid "Connection URL" -msgstr "URL подключения" - -msgid "Outbound Config" -msgstr "Конфигурация Outbound" - -msgid "Outbound Configuration" -msgstr "Конфигурация исходящего соединения" - -msgid "Enter complete outbound configuration in JSON format" -msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" +msgid "Invalid path format" +msgstr "Неверный формат пути" msgid "JSON must contain at least type, server and server_port fields" msgstr "JSON должен содержать как минимум поля type, server и server_port" @@ -292,189 +265,233 @@ msgstr "JSON должен содержать как минимум поля type msgid "Invalid JSON format" msgstr "Неверный формат JSON" -msgid "Diagnostics" -msgstr "Диагностика" - -msgid "Main Check" -msgstr "Основная проверка" - -msgid "Run a comprehensive diagnostic check of all components" -msgstr "Запустить комплексную диагностическую проверку всех компонентов" - -msgid "Run Check" -msgstr "Запустить проверку" - -msgid "Full Diagnostic Results" -msgstr "Полные результаты диагностики" - -msgid "Failed to copy: " -msgstr "Ошибка копирования: " - -msgid "Copy to Clipboard" -msgstr "Скопировать в буфер" - -msgid "Close" -msgstr "Закрыть" - -msgid "No output" -msgstr "Нет данных" - -msgid "System Logs" -msgstr "Системные логи" - -msgid "View recent system logs related to Podkop" -msgstr "Просмотр недавних системных логов, связанных с Podkop" - -msgid "View Logs" -msgstr "Просмотр логов" - -msgid "Failed to copy logs: " -msgstr "Ошибка копирования логов: " - -msgid "Show Config" -msgstr "Показать конфигурацию" - -msgid "Show current podkop configuration with masked sensitive data" -msgstr "Показать текущую конфигурацию podkop с маскированными конфиденциальными данными" - -msgid "Podkop Configuration" -msgstr "Конфигурация Podkop" - -msgid "Update lists" -msgstr "Обновить списки" - -msgid "Update all lists in config" -msgstr "Обновить все списки в конфигурации" - -msgid "List Update" -msgstr "Обновление списков" - -msgid "Lists will be updated in background. You can check the progress in system logs." -msgstr "Списки будут обновлены в фоновом режиме. Вы можете проверить прогресс в системных логах." - -msgid "Extra configurations" -msgstr "Дополнительные конфигурации" - -msgid "Extra configuration" -msgstr "Дополнительная конфигурация" - -msgid "Add Section" -msgstr "Добавить раздел" - -msgid "QUIC disable" -msgstr "Отключить QUIC" - -msgid "For issues with the video stream" -msgstr "Для проблем с видеопотоком" - -msgid "Community Lists" -msgstr "Списки сообщества" - -msgid "Local Domain Lists" -msgstr "Локальные списки доменов" - -msgid "Use the list from the router filesystem" -msgstr "Использовать список из файловой системы роутера" - -msgid "Local Domain Lists Path" -msgstr "Путь к локальным спискам доменов" - -msgid "Enter to the list file path" -msgstr "Введите путь к файлу списка" - -msgid "Proxy Check" -msgstr "Проверка прокси" - -msgid "Check if sing-box proxy works correctly" -msgstr "Проверить корректность работы прокси sing-box" - -msgid "Check Proxy" -msgstr "Проверить прокси" - -msgid "Proxy Check Results" -msgstr "Результаты проверки прокси" - -msgid "NFT Rules" -msgstr "Правила NFT" - -msgid "Show current nftables rules and statistics" -msgstr "Показать текущие правила и статистику nftables" - -msgid "Check Rules" -msgstr "Проверить правила" - -msgid "GitHub Connectivity" -msgstr "Подключение к GitHub" - -msgid "Check GitHub connectivity and lists availability" -msgstr "Проверить подключение к GitHub и доступность списков" - -msgid "Check GitHub" -msgstr "Проверить GitHub" - -msgid "GitHub Connectivity Results" -msgstr "Результаты проверки подключения к GitHub" - -msgid "Sing-Box Logs" -msgstr "Логи Sing-Box" - -msgid "View recent sing-box logs from system journal" -msgstr "Просмотр последних логов sing-box из системного журнала" - -msgid "View Sing-Box Logs" -msgstr "Просмотр логов Sing-Box" - -msgid "Podkop Logs" -msgstr "Логи Podkop" - -msgid "View recent podkop logs from system journal" -msgstr "Просмотр последних логов podkop из системного журнала" - -msgid "View Podkop Logs" -msgstr "Просмотр логов Podkop" - -msgid "Active Connections" -msgstr "Активные соединения" - -msgid "View active sing-box network connections" -msgstr "Просмотр активных сетевых соединений sing-box" - -msgid "Check Connections" -msgstr "Проверить соединения" - -msgid "DNSMasq Configuration" -msgstr "Конфигурация DNSMasq" - -msgid "View current DNSMasq configuration settings" -msgstr "Просмотр текущих настроек конфигурации DNSMasq" - -msgid "Check DNSMasq" -msgstr "Проверить DNSMasq" - -msgid "Sing-Box Configuration" -msgstr "Конфигурация Sing-Box" - -msgid "Show current sing-box configuration" -msgstr "Показать текущую конфигурацию sing-box" - -msgid "Show Sing-Box Config" -msgstr "Показать конфигурацию Sing-Box" - -msgid "Lists Update Results" -msgstr "Результаты обновления списков" - -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX -msgid "Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s have been removed from selection." -msgstr "Внимание: Russia inside может использоваться только с Meta, Twitter, Discord и Telegram. %s были удалены из выбора." - -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX msgid "Regional options cannot be used together" msgstr "Нельзя использовать несколько региональных опций" -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX +msgid "Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s already in Russia inside and have been removed from selection." +msgstr "Внимание: Russia inside может использоваться только с Meta, Twitter, Discord и Telegram. %s были удалены из выбора." + msgid "Russia inside restrictions" -msgstr "Ограничения Russia inside" \ No newline at end of file +msgstr "Ограничения Russia inside" + +msgid "URL must start with vless:// or ss://" +msgstr "URL должен начинаться с vless:// или ss://" + +msgid "Invalid Shadowsocks URL format: missing method and password separator \":\"" +msgstr "Неверный формат URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" + +msgid "Invalid Shadowsocks URL format" +msgstr "Неверный формат URL Shadowsocks" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Должен быть между 1 и 65535" + +msgid "Invalid Shadowsocks URL: missing or invalid server/port format" +msgstr "Неверный URL Shadowsocks: отсутствует или неверный формат сервера/порта" + +msgid "Invalid VLESS URL: missing UUID" +msgstr "Неверный URL VLESS: отсутствует UUID" + +msgid "Invalid VLESS URL: missing server address" +msgstr "Неверный URL VLESS: отсутствует адрес сервера" + +msgid "Invalid VLESS URL: missing server" +msgstr "Неверный URL VLESS: отсутствует сервер" + +msgid "Invalid VLESS URL: missing port" +msgstr "Неверный URL VLESS: отсутствует порт" + +msgid "Invalid VLESS URL: missing or invalid server/port format" +msgstr "Неверный URL VLESS: отсутствует или неверный формат сервера/порта" + +msgid "Invalid VLESS URL: missing query parameters" +msgstr "Неверный URL VLESS: отсутствуют параметры запроса" + +msgid "Invalid VLESS URL: missing type parameter" +msgstr "Неверный URL VLESS: отсутствует параметр type" + +msgid "Invalid VLESS URL: missing security parameter" +msgstr "Неверный URL VLESS: отсутствует параметр security" + +msgid "Invalid VLESS URL: missing pbk parameter for reality security" +msgstr "Неверный URL VLESS: отсутствует параметр pbk для security reality" + +msgid "Invalid VLESS URL: missing fp parameter for reality security" +msgstr "Неверный URL VLESS: отсутствует параметр fp для security reality" + +msgid "Invalid VLESS URL: missing sni parameter for tls security" +msgstr "Неверный URL VLESS: отсутствует параметр sni для security tls" + +msgid "Invalid URL format: %s" +msgstr "Неверный формат URL: %s" + +msgid "Remote Domain Lists URL" +msgstr "URL удаленных списков доменов" + +msgid "Enter URL to download domain list" +msgstr "Введите URL для загрузки списка доменов" + +msgid "Update Interval" +msgstr "Интервал обновления" + +msgid "Select how often to update the lists" +msgstr "Выберите, как часто обновлять списки" + +msgid "Last Update" +msgstr "Последнее обновление" + +msgid "Last update time" +msgstr "Время последнего обновления" + +msgid "Next Update" +msgstr "Следующее обновление" + +msgid "Next scheduled update time" +msgstr "Время следующего запланированного обновления" + +msgid "Version" +msgstr "Версия" + +msgid "Component version" +msgstr "Версия компонента" + +msgid "Installed" +msgstr "Установлено" + +msgid "Not installed" +msgstr "Не установлено" + +msgid "Unknown version" +msgstr "Неизвестная версия" + +msgid "Error parsing version" +msgstr "Ошибка разбора версии" + +msgid "Error parsing status" +msgstr "Ошибка разбора статуса" + +msgid "Service is running" +msgstr "Сервис запущен" + +msgid "Service is stopped" +msgstr "Сервис остановлен" + +msgid "Service is enabled" +msgstr "Сервис включен" + +msgid "Service is disabled" +msgstr "Сервис отключен" + +msgid "Service Status" +msgstr "Статус сервиса" + +msgid "working" +msgstr "работает" + +msgid "not working" +msgstr "не работает" + +msgid "check error" +msgstr "ошибка проверки" + +msgid "Diagnostic check in progress..." +msgstr "Выполняется диагностическая проверка..." + +msgid "Diagnostic check completed" +msgstr "Диагностическая проверка завершена" + +msgid "Diagnostic check failed" +msgstr "Диагностическая проверка не удалась" + +msgid "Update in progress..." +msgstr "Выполняется обновление..." + +msgid "Update completed" +msgstr "Обновление завершено" + +msgid "Update failed" +msgstr "Обновление не удалось" + +msgid "Check in progress..." +msgstr "Выполняется проверка..." + +msgid "Check completed" +msgstr "Проверка завершена" + +msgid "Check failed" +msgstr "Проверка не удалась" + +msgid "Version Information" +msgstr "Информация о версии" + +msgid "Copied!" +msgstr "Скопировано!" + +msgid "Podkop Status" +msgstr "Статус Podkop" + +msgid "Start Podkop" +msgstr "Запустить Podkop" + +msgid "Stop Podkop" +msgstr "Остановить Podkop" + +msgid "Restart Podkop" +msgstr "Перезапустить Podkop" + +msgid "Enable Podkop" +msgstr "Включить Podkop" + +msgid "Disable Podkop" +msgstr "Отключить Podkop" + +msgid "Loading diagnostics..." +msgstr "Загрузка диагностики..." + +msgid "Error loading diagnostics" +msgstr "Ошибка загрузки диагностики" + +msgid "Sing-box Status" +msgstr "Статус Sing-box" + +msgid "Diagnostic Tools" +msgstr "Инструменты диагностики" + +msgid "Unknown" +msgstr "Неизвестно" + +msgid "Device Model: " +msgstr "Модель устройства: " + +msgid "OpenWrt Version: " +msgstr "Версия OpenWrt: " + +msgid "Sing-box: " +msgstr "Sing-box: " + +msgid "LuCI App: " +msgstr "LuCI App: " + +msgid "Podkop: " +msgstr "Podkop: " + +msgid "Check NFT Rules" +msgstr "Проверить правила NFT" + +msgid "Update Lists" +msgstr "Обновить списки" + +msgid "Lists Update Results" +msgstr "Результаты обновления списков" \ No newline at end of file diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 68a04c5..f13ab9a 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -25,18 +25,318 @@ msgstr "" msgid "Select between VPN and Proxy connection methods for traffic routing" msgstr "" +msgid "Configuration Type" +msgstr "" + +msgid "Select how to configure the proxy" +msgstr "" + +msgid "Connection URL" +msgstr "" + +msgid "Outbound Config" +msgstr "" + msgid "Proxy Configuration URL" msgstr "" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgstr "" +msgid "Outbound Configuration" +msgstr "" + +msgid "Enter complete outbound configuration in JSON format" +msgstr "" + msgid "Network Interface" msgstr "" msgid "Select network interface for VPN connection" msgstr "" +msgid "Community Lists" +msgstr "" + +msgid "Service List" +msgstr "" + +msgid "Select predefined service for routing" +msgstr "" + +msgid "User Domain List Type" +msgstr "" + +msgid "Select how to add your custom domains" +msgstr "" + +msgid "Disabled" +msgstr "" + +msgid "Dynamic List" +msgstr "" + +msgid "Text List" +msgstr "" + +msgid "User Domains" +msgstr "" + +msgid "Enter domain names without protocols (example: sub.example.com or example.com)" +msgstr "" + +msgid "User Domains List" +msgstr "" + +msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" +msgstr "" + +msgid "Local Domain Lists" +msgstr "" + +msgid "Use the list from the router filesystem" +msgstr "" + +msgid "Local Domain Lists Path" +msgstr "" + +msgid "Enter to the list file path" +msgstr "" + +msgid "Remote Domain Lists" +msgstr "" + +msgid "Download and use domain lists from remote URLs" +msgstr "" + +msgid "Remote Domain URLs" +msgstr "" + +msgid "Enter full URLs starting with http:// or https://" +msgstr "" + +msgid "User Subnet List Type" +msgstr "" + +msgid "Select how to add your custom subnets" +msgstr "" + +msgid "Text List (comma/space/newline separated)" +msgstr "" + +msgid "User Subnets" +msgstr "" + +msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" +msgstr "" + +msgid "User Subnets List" +msgstr "" + +msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline" +msgstr "" + +msgid "Remote Subnet Lists" +msgstr "" + +msgid "Download and use subnet lists from remote URLs" +msgstr "" + +msgid "Remote Subnet URLs" +msgstr "" + +msgid "IP for full redirection" +msgstr "" + +msgid "Specify local IP addresses whose traffic will always use the configured route" +msgstr "" + +msgid "Local IPs" +msgstr "" + +msgid "Enter valid IPv4 addresses" +msgstr "" + +msgid "IP for exclusion" +msgstr "" + +msgid "Specify local IP addresses that will never use the configured route" +msgstr "" + +msgid "Mixed enable" +msgstr "" + +msgid "Browser port: 2080" +msgstr "" + +msgid "Yacd enable" +msgstr "" + +msgid "Exclude NTP" +msgstr "" + +msgid "For issues with open connections sing-box" +msgstr "" + +msgid "QUIC disable" +msgstr "" + +msgid "For issues with the video stream" +msgstr "" + +msgid "List Update Frequency" +msgstr "" + +msgid "Select how often the lists will be updated" +msgstr "" + +msgid "Every hour" +msgstr "" + +msgid "Every 2 hours" +msgstr "" + +msgid "Every 3 hours" +msgstr "" + +msgid "Every 4 hours" +msgstr "" + +msgid "Every 6 hours" +msgstr "" + +msgid "Every 12 hours" +msgstr "" + +msgid "Every day" +msgstr "" + +msgid "Every 3 days" +msgstr "" + +msgid "Once a day at 04:00" +msgstr "" + +msgid "Once a week on Sunday at 04:00" +msgstr "" + +msgid "Invalid domain format. Enter domain without protocol (example: sub.example.com)" +msgstr "" + +msgid "URL must use http:// or https:// protocol" +msgstr "" + +msgid "Invalid URL format. URL must start with http:// or https://" +msgstr "" + +msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y" +msgstr "" + +msgid "IP address parts must be between 0 and 255" +msgstr "" + +msgid "CIDR must be between 0 and 32" +msgstr "" + +msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)" +msgstr "" + +msgid "Invalid domain format: %s. Enter domain without protocol" +msgstr "" + +msgid "Invalid format: %s. Use format: X.X.X.X or X.X.X.X/Y" +msgstr "" + +msgid "IP parts must be between 0 and 255 in: %s" +msgstr "" + +msgid "CIDR must be between 0 and 32 in: %s" +msgstr "" + +msgid "Invalid path format. Path must start with \"/\" and contain only valid characters (letters, numbers, \"-\", \"_\", \"/\", \".\")" +msgstr "" + +msgid "Invalid path format" +msgstr "" + +msgid "JSON must contain at least type, server and server_port fields" +msgstr "" + +msgid "Invalid JSON format" +msgstr "" + +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "" + +msgid "Regional options cannot be used together" +msgstr "" + +msgid "Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s already in Russia inside and have been removed from selection." +msgstr "" + +msgid "Russia inside restrictions" +msgstr "" + +msgid "URL must start with vless:// or ss://" +msgstr "" + +msgid "Invalid Shadowsocks URL format: missing method and password separator \":\"" +msgstr "" + +msgid "Invalid Shadowsocks URL format" +msgstr "" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +msgid "Invalid Shadowsocks URL: missing or invalid server/port format" +msgstr "" + +msgid "Invalid VLESS URL: missing UUID" +msgstr "" + +msgid "Invalid VLESS URL: missing server address" +msgstr "" + +msgid "Invalid VLESS URL: missing server" +msgstr "" + +msgid "Invalid VLESS URL: missing port" +msgstr "" + +msgid "Invalid VLESS URL: missing or invalid server/port format" +msgstr "" + +msgid "Invalid VLESS URL: missing query parameters" +msgstr "" + +msgid "Invalid VLESS URL: missing type parameter" +msgstr "" + +msgid "Invalid VLESS URL: missing security parameter" +msgstr "" + +msgid "Invalid VLESS URL: missing pbk parameter for reality security" +msgstr "" + +msgid "Invalid VLESS URL: missing fp parameter for reality security" +msgstr "" + +msgid "Invalid VLESS URL: missing sni parameter for tls security" +msgstr "" + +msgid "Invalid URL format: %s" +msgstr "" + msgid "Community Domain Lists" msgstr "" @@ -64,292 +364,169 @@ msgstr "" msgid "Enable and manage your custom list of domains for selective routing" msgstr "" -msgid "User Domains" -msgstr "" - -msgid "Enter domain names without protocols (example: sub.example.com or example.com)" -msgstr "" - -msgid "Remote Domain Lists" -msgstr "" - -msgid "Download and use domain lists from remote URLs" -msgstr "" - -msgid "Remote Domain URLs" -msgstr "" - -msgid "Enter full URLs starting with http:// or https://" -msgstr "" - -msgid "User Subnet List" -msgstr "" - -msgid "Enable and manage your custom list of IP subnets for selective routing" -msgstr "" - -msgid "User Subnets" -msgstr "" - -msgid "Enter subnet in CIDR notation (example: 103.21.244.0/22)" -msgstr "" - -msgid "Remote Subnet Lists" -msgstr "" - -msgid "Download and use subnet lists from remote URLs" -msgstr "" - -msgid "Remote Subnet URLs" -msgstr "" - -msgid "Domain Exclusions" -msgstr "" - -msgid "Exclude specific domains from routing rules" -msgstr "" - -msgid "Excluded Domains" -msgstr "" - -msgid "Domains to be excluded from routing" -msgstr "" - -msgid "IP for full redirection" -msgstr "" - -msgid "Specify local IP addresses whose traffic will always use the configured route" -msgstr "" - -msgid "Local IPs" -msgstr "" - -msgid "Enter valid IPv4 addresses" -msgstr "" - -msgid "IP for exclusion" -msgstr "" - -msgid "Specify local IP addresses that will never use the configured route" -msgstr "" - -msgid "List Update Frequency" -msgstr "" - -msgid "Select how often the lists will be updated" -msgstr "" - -msgid "Every hour" -msgstr "" - -msgid "Every 2 hours" -msgstr "" - -msgid "Every 4 hours" -msgstr "" - -msgid "Every 6 hours" -msgstr "" - -msgid "Every 12 hours" -msgstr "" - -msgid "Once a day at 04:00" -msgstr "" - -msgid "Once a week on Sunday at 04:00" -msgstr "" - -msgid "Yacd enable" -msgstr "" - -msgid "Mixed enable" -msgstr "" - -msgid "Browser port: 2080" -msgstr "" - -msgid "Exclude NTP" -msgstr "" - -msgid "For issues with open connections sing-box" -msgstr "" - -msgid "Service Domain List Enable" -msgstr "" - -msgid "Enable predefined service domain lists for routing" -msgstr "" - -msgid "Service List" -msgstr "" - -msgid "Select predefined services for routing" -msgstr "" - -msgid "Domains" -msgstr "" - -msgid "Subnet List" -msgstr "" - -msgid "Configure custom subnets for routing" -msgstr "" - -msgid "Subnets" -msgstr "" - -msgid "Invalid domain format. Enter domain without protocol (example: sub.example.com)" -msgstr "" - -msgid "URL must use http:// or https:// protocol" -msgstr "" - -msgid "Invalid URL format. URL must start with http:// or https://" -msgstr "" - -msgid "Invalid subnet format. Use format: X.X.X.X/Y (like 103.21.244.0/22)" -msgstr "" - -msgid "IP address parts must be between 0 and 255" -msgstr "" - -msgid "CIDR must be between 0 and 32" -msgstr "" - -msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)" -msgstr "" - -msgid "User Domain List Type" -msgstr "" - -msgid "Select how to add your custom domains" -msgstr "" - -msgid "Disabled" -msgstr "" - -msgid "Dynamic List" -msgstr "" - -msgid "Text List" -msgstr "" - msgid "User Domains List" msgstr "" msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" msgstr "" -msgid "Invalid domain format: %s. Enter domain without protocol" +msgid "Remote Domain Lists URL" msgstr "" -msgid "User Subnet List Type" +msgid "Enter URL to download domain list" msgstr "" -msgid "Select how to add your custom subnets" +msgid "Update Interval" msgstr "" -msgid "Text List (comma/space/newline separated)" +msgid "Select how often to update the lists" msgstr "" -msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" +msgid "Last Update" msgstr "" -msgid "User Subnets List" +msgid "Last update time" msgstr "" -msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline" +msgid "Next Update" msgstr "" -msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y" +msgid "Next scheduled update time" msgstr "" -msgid "IP parts must be between 0 and 255 in: %s" +msgid "Version" msgstr "" -msgid "Configuration Type" +msgid "Component version" msgstr "" -msgid "Select how to configure the proxy" +msgid "Installed" msgstr "" -msgid "Connection URL" +msgid "Not installed" msgstr "" -msgid "Outbound Config" +msgid "Unknown version" msgstr "" -msgid "Outbound Configuration" +msgid "Error parsing version" msgstr "" -msgid "Enter complete outbound configuration in JSON format" +msgid "Error parsing status" msgstr "" -msgid "JSON must contain at least type, server and server_port fields" +msgid "Service is running" msgstr "" -msgid "Invalid JSON format" +msgid "Service is stopped" msgstr "" -msgid "Diagnostics" +msgid "Service is enabled" msgstr "" -msgid "Main Check" +msgid "Service is disabled" msgstr "" -msgid "Run a comprehensive diagnostic check of all components" +msgid "Service Status" msgstr "" -msgid "Run Check" +msgid "working" msgstr "" -msgid "Full Diagnostic Results" +msgid "not working" msgstr "" -msgid "Failed to copy: " +msgid "check error" msgstr "" -msgid "Copy to Clipboard" +msgid "Diagnostic check in progress..." msgstr "" -msgid "Close" +msgid "Diagnostic check completed" msgstr "" -msgid "No output" +msgid "Diagnostic check failed" msgstr "" -msgid "System Logs" +msgid "Update in progress..." msgstr "" -msgid "View recent system logs related to Podkop" +msgid "Update completed" msgstr "" -msgid "View Logs" +msgid "Update failed" msgstr "" -msgid "Failed to copy logs: " +msgid "Check in progress..." msgstr "" -msgid "Show Config" +msgid "Check completed" msgstr "" -msgid "Show current podkop configuration with masked sensitive data" +msgid "Check failed" msgstr "" -msgid "Podkop Configuration" +msgid "Version Information" msgstr "" -msgid "Update lists" +msgid "Copied!" msgstr "" -msgid "Update all lists in config" +msgid "Podkop Status" msgstr "" -msgid "List Update" +msgid "Start Podkop" msgstr "" -msgid "Lists will be updated in background. You can check the progress in system logs." +msgid "Stop Podkop" +msgstr "" + +msgid "Restart Podkop" +msgstr "" + +msgid "Enable Podkop" +msgstr "" + +msgid "Disable Podkop" +msgstr "" + +msgid "Loading diagnostics..." +msgstr "" + +msgid "Error loading diagnostics" +msgstr "" + +msgid "Sing-box Status" +msgstr "" + +msgid "Diagnostic Tools" +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Device Model: " +msgstr "" + +msgid "OpenWrt Version: " +msgstr "" + +msgid "Sing-box: " +msgstr "" + +msgid "LuCI App: " +msgstr "" + +msgid "Podkop: " +msgstr "" + +msgid "Check NFT Rules" +msgstr "" + +msgid "Update Lists" +msgstr "" + +msgid "Lists Update Results" msgstr "" msgid "Extra configurations" @@ -361,25 +538,7 @@ msgstr "" msgid "Add Section" msgstr "" -msgid "QUIC disable" -msgstr "" - -msgid "For issues with the video stream" -msgstr "" - -msgid "Community Lists" -msgstr "" - -msgid "Local Domain Lists" -msgstr "" - -msgid "Use the list from the router filesystem" -msgstr "" - -msgid "Local Domain Lists Path" -msgstr "" - -msgid "Enter to the list file path" +msgid "Lists Update Results" msgstr "" msgid "Proxy Check" @@ -463,18 +622,236 @@ msgstr "" msgid "Lists Update Results" msgstr "" -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX -msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgid "Warning" msgstr "" -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX -msgid "Warning: Russia inside can only be used with Meta, Twitter, Discord, and Telegram. %s have been removed from selection." +msgid "Success" msgstr "" -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX -msgid "Regional options cannot be used together" +msgid "Info" msgstr "" -#: applications/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:XXX -msgid "Russia inside restrictions" +msgid "Error" +msgstr "" + +msgid "Debug" +msgstr "" + +msgid "Trace" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "No" +msgstr "" + +msgid "OK" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Apply" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Add" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Move Up" +msgstr "" + +msgid "Move Down" +msgstr "" + +msgid "Expand" +msgstr "" + +msgid "Collapse" +msgstr "" + +msgid "Show" +msgstr "" + +msgid "Hide" +msgstr "" + +msgid "Enable" +msgstr "" + +msgid "Disable" +msgstr "" + +msgid "Start" +msgstr "" + +msgid "Stop" +msgstr "" + +msgid "Restart" +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Refresh" +msgstr "" + +msgid "Update" +msgstr "" + +msgid "Install" +msgstr "" + +msgid "Uninstall" +msgstr "" + +msgid "Configure" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Advanced" +msgstr "" + +msgid "Basic" +msgstr "" + +msgid "General" +msgstr "" + +msgid "Details" +msgstr "" + +msgid "Status" +msgstr "" + +msgid "Information" +msgstr "" + +msgid "Configuration" +msgstr "" + +msgid "Management" +msgstr "" + +msgid "System" +msgstr "" + +msgid "Network" +msgstr "" + +msgid "Services" +msgstr "" + +msgid "Remote Domain Lists URL" +msgstr "" + +msgid "Enter URL to download domain list" +msgstr "" + +msgid "Update Interval" +msgstr "" + +msgid "Select how often to update the lists" +msgstr "" + +msgid "Last Update" +msgstr "" + +msgid "Last update time" +msgstr "" + +msgid "Next Update" +msgstr "" + +msgid "Next scheduled update time" +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Component version" +msgstr "" + +msgid "Installed" +msgstr "" + +msgid "Not installed" +msgstr "" + +msgid "Unknown version" +msgstr "" + +msgid "Error parsing version" +msgstr "" + +msgid "Error parsing status" +msgstr "" + +msgid "Service is running" +msgstr "" + +msgid "Service is stopped" +msgstr "" + +msgid "Service is enabled" +msgstr "" + +msgid "Service is disabled" +msgstr "" + +msgid "Service Status" +msgstr "" + +msgid "working" +msgstr "" + +msgid "not working" +msgstr "" + +msgid "check error" +msgstr "" + +msgid "Diagnostic check in progress..." +msgstr "" + +msgid "Diagnostic check completed" +msgstr "" + +msgid "Diagnostic check failed" +msgstr "" + +msgid "Update in progress..." +msgstr "" + +msgid "Update completed" +msgstr "" + +msgid "Update failed" +msgstr "" + +msgid "Check in progress..." +msgstr "" + +msgid "Check completed" +msgstr "" + +msgid "Check failed" msgstr "" \ No newline at end of file diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index c208076..ed8eaa2 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -7,7 +7,7 @@ script=$(readlink "$initscript") NAME="$(basename ${script:-$initscript})" config_load "$NAME" -EXTRA_COMMANDS="main list_update check_proxy check_nft check_github check_logs check_sing_box_connections check_sing_box_logs check_dnsmasq show_config show_version show_sing_box_config" +EXTRA_COMMANDS="main list_update check_proxy check_nft check_github check_logs check_sing_box_connections check_sing_box_logs check_dnsmasq show_config show_version show_sing_box_config show_luci_version show_sing_box_version show_system_info get_status get_sing_box_status" EXTRA_HELP=" list_update Updating domain and subnet lists check_proxy Check if sing-box proxy works correctly check_nft Show PodkopTable nftables rules @@ -18,7 +18,11 @@ EXTRA_HELP=" list_update Updating domain and subnet lists check_dnsmasq Show current DNSMasq configuration show_config Show current configuration with masked sensitive data show_version Show current version - show_sing_box_config Show current sing-box configuration" + show_sing_box_config Show current sing-box configuration + show_luci_version Show LuCI app version + show_sing_box_version Show sing-box version + show_system_info Show OpenWrt version and device model + get_sing_box_status Get sing-box status" [ ! -L /usr/sbin/podkop ] && ln -s /etc/init.d/podkop /usr/sbin/podkop @@ -1641,3 +1645,125 @@ show_version() { local version=$(opkg info podkop | grep -m 1 "Version:" | cut -d' ' -f2) echo "$version" } + +show_luci_version() { + local version=$(opkg info luci-app-podkop | grep -m 1 "Version:" | cut -d' ' -f2) + echo "$version" +} + +show_sing_box_version() { + local version=$(opkg info sing-box | grep -m 1 "Version:" | cut -d' ' -f2) + echo "$version" +} + +show_system_info() { + echo "=== OpenWrt Version ===" + grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2 + echo + echo "=== Device Model ===" + cat /tmp/sysinfo/model +} + +get_sing_box_status() { + local running=0 + local enabled=0 + local status="" + local version="" + + # Check if service is enabled + if [ -x /etc/rc.d/S99sing-box ]; then + enabled=1 + fi + + # Check if service is running + if pgrep -f "sing-box" >/dev/null; then + running=1 + version=$(sing-box version | head -n 1 | awk '{print $3}') + fi + + # Format status message + if [ $running -eq 1 ]; then + if [ $enabled -eq 1 ]; then + status="running & enabled" + else + status="running but disabled" + fi + else + if [ $enabled -eq 1 ]; then + status="stopped but enabled" + else + status="stopped & disabled" + fi + fi + + echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" +} + +get_status() { + local running=0 + local enabled=0 + local status="" + + # Check if service is enabled + if [ -x /etc/rc.d/S99podkop ]; then + enabled=1 + fi + + # Check if service is running + if pgrep -f "sing-box" >/dev/null; then + running=1 + fi + + # Format status message + if [ $running -eq 1 ]; then + if [ $enabled -eq 1 ]; then + status="running & enabled" + else + status="running but disabled" + fi + else + if [ $enabled -eq 1 ]; then + status="stopped but enabled" + else + status="stopped & disabled" + fi + fi + + echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}" +} + +sing_box_add_secure_dns_probe_domain() { + local domain="httpbin.org" + local override_address="numbersapi.com" + + if [ -z "$override_address" ]; then + log "Error: Could not get br-lan IP address" + return 1 + fi + + log "Adding DNS probe domain ${domain} to fakeip-server configuration" + + jq \ + --arg domain "$domain" \ + --arg override "$override_address" \ + '.dns.rules |= map( + if .server == "fakeip-server" then + { + "server": .server, + "domain": $domain, + "rule_set": .rule_set + } + else + . + end + ) | + .route.rules |= . + [ + { + "domain": $domain, + "action": "route-options", + "override_address": $override + } + ]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG" + + log "DNS probe domain ${domain} configured with override to ${override_address}" +} \ No newline at end of file