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 83e2b82..149b543 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 @@ -12,6 +12,8 @@ const STATUS_COLORS = { WARNING: '#ff9800' }; +const ERROR_POLL_INTERVAL = 5000; // 5 seconds + async function safeExec(command, args = [], timeout = 7000) { try { const controller = new AbortController(); @@ -850,6 +852,121 @@ function checkDNSAvailability() { }); } +async function getPodkopErrors() { + try { + const result = await safeExec('/usr/bin/podkop', ['check_logs']); + if (!result || !result.stdout) return []; + + const logs = result.stdout.split('\n'); + const errors = logs.filter(log => + // log.includes('saved for future filters') || + log.includes('[critical]') + ); + + console.log('Found errors:', errors); + return errors; + } catch (error) { + console.error('Error getting podkop logs:', error); + return []; + } +} + +function createErrorModal(errors) { + return [ + E('div', { + 'class': 'panel-body', + style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; ' + + 'font-family: monospace; white-space: pre-wrap; word-wrap: break-word; ' + + 'line-height: 1.5; font-size: 14px; background-color: #1e1e1e; color: #e0e0e0;' + }, [ + E('pre', { style: 'margin: 0; white-space: pre-wrap;' }, errors) + ]), + E('div', { + 'class': 'right', + style: 'margin-top: 1em;' + }, [ + E('button', { + 'class': 'btn', + 'click': ev => copyToClipboard(errors, ev.target) + }, _('Copy')), + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Close')) + ]) + ]; +} + +let errorPollTimer = null; +let lastErrorsSet = new Set(); +let isInitialCheck = true; + +function showErrorNotification(error, isMultiple = false) { + const notificationContent = E('div', { 'class': 'alert-message error' }, [ + E('pre', { 'class': 'error-log' }, error) + ]); + + const notification = ui.addNotification(null, notificationContent); +} + +function startErrorPolling() { + if (errorPollTimer) { + clearInterval(errorPollTimer); + } + + async function checkErrors() { + const result = await safeExec('/usr/bin/podkop', ['check_logs']); + if (!result || !result.stdout) return; + + // Get all logs since last "Starting podkop" + const logs = result.stdout; + + // Extract all error messages + const errorLines = logs.split('\n').filter(line => + line.includes('error') || + line.includes('Error') || + line.includes('ERROR') + ); + + if (errorLines.length > 0) { + // Create a set of current errors + const currentErrors = new Set(errorLines); + + if (isInitialCheck) { + // При первой проверке показываем все ошибки в одном уведомлении + if (errorLines.length > 0) { + showErrorNotification(errorLines.join('\n'), true); + } + isInitialCheck = false; + } else { + // Find new errors that weren't shown before + const newErrors = [...currentErrors].filter(error => !lastErrorsSet.has(error)); + + // Show each new error as a separate notification + newErrors.forEach(error => { + showErrorNotification(error, false); + }); + } + + // Update the set of shown errors + lastErrorsSet = currentErrors; + } + } + + // Initial check + checkErrors(); + + // Set up polling + errorPollTimer = setInterval(checkErrors, ERROR_POLL_INTERVAL); +} + +function stopErrorPolling() { + if (errorPollTimer) { + clearInterval(errorPollTimer); + errorPollTimer = null; + } +} + return view.extend({ async render() { document.head.insertAdjacentHTML('beforeend', ` @@ -1348,8 +1465,10 @@ return view.extend({ const diagnosticsContainer = document.getElementById('diagnostics-status'); if (document.hidden) { stopDiagnosticsUpdates(); + stopErrorPolling(); } else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) { startDiagnosticsUpdates(); + startErrorPolling(); } }); @@ -1360,6 +1479,7 @@ return view.extend({ if (!this.hasAttribute('data-loading')) { this.setAttribute('data-loading', 'true'); startDiagnosticsUpdates(); + startErrorPolling(); } }); } @@ -1375,9 +1495,11 @@ return view.extend({ if (container && !container.hasAttribute('data-loading')) { container.setAttribute('data-loading', 'true'); startDiagnosticsUpdates(); + startErrorPolling(); } } else { stopDiagnosticsUpdates(); + stopErrorPolling(); } } }); @@ -1388,6 +1510,7 @@ return view.extend({ if (container && !container.hasAttribute('data-loading')) { container.setAttribute('data-loading', 'true'); startDiagnosticsUpdates(); + startErrorPolling(); } } } diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 0a17d76..e33707b 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -45,6 +45,7 @@ nolog() { } start() { + log "Starting podkop" migration config_foreach process_validate_service @@ -1857,12 +1858,24 @@ check_fakeip() { check_logs() { nolog "Showing podkop logs from system journal..." - if command -v logread >/dev/null 2>&1; then - logread -e podkop | tail -n 50 - else + if ! command -v logread >/dev/null 2>&1; then nolog "Error: logread command not found" return 1 fi + + # Get all logs first + local all_logs=$(logread) + + # Find the last occurrence of "Starting podkop" + local start_line=$(echo "$all_logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1) + + if [ -z "$start_line" ]; then + nolog "No 'Starting podkop' message found in logs" + return 1 + fi + + # Output all logs from the last start + echo "$all_logs" | tail -n +"$start_line" } show_sing_box_config() {