From 1e406c3e06c1217348796ed3222fd53a0401e692 Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Sat, 15 Aug 2026 09:23:07 +0300 Subject: [PATCH] =?UTF-8?q?fix(updates):=20=D0=BF=D1=80=D0=B5=D0=B4=D0=BE?= =?UTF-8?q?=D1=82=D0=B2=D1=80=D0=B0=D1=82=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=BD=D0=BE=D0=B5=20=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20=D0=B8=20=D0=B7=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=BE=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=BA=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Исключить повторное открытие окна прогресса для уже завершенных задач (handledComponentJobs) - Актуализировать состояние фоновых задач в get_ui_state и нормализовать таймаут stale PID - Исправить определение WAN интерфейса в event_controller во избежание ложного перезапуска (#31) - Улучшить сверку версий при самообновлении --- .gitignore | 3 +- .../src/tachyon/methods/shell/index.ts | 18 +++-- .../tachyon/tabs/dashboard/initController.ts | 10 +-- .../partials/renderAdvancedSettings.ts | 1 - .../tachyon/tabs/diagnostic/initController.ts | 5 +- .../tachyon/tabs/updates/initController.ts | 19 ++++-- .../partials/renderUpdateProgressModal.ts | 29 ++++---- .../resources/view/tachyon/main.js | 62 +++++++----------- .../files/usr/lib/service/event_controller.uc | 22 ++++++- tachyon/files/usr/lib/service/ui.uc | 7 +- uc.out | Bin 5353 -> 0 bytes 11 files changed, 90 insertions(+), 86 deletions(-) delete mode 100644 uc.out diff --git a/.gitignore b/.gitignore index 8a708277..c867190a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ build-out/ scratch/ tmp_*/ tmp_test*.uc -test_out.txt \ No newline at end of file +test_out.txt +uc.out \ No newline at end of file diff --git a/fe-app-tachyon/src/tachyon/methods/shell/index.ts b/fe-app-tachyon/src/tachyon/methods/shell/index.ts index a88431f9..c49f2e86 100644 --- a/fe-app-tachyon/src/tachyon/methods/shell/index.ts +++ b/fe-app-tachyon/src/tachyon/methods/shell/index.ts @@ -659,6 +659,12 @@ export const TachyonShellMethods = { COMPONENT_ACTION_TRANSIENT_RPC_GRACE_MS, ); + const versionsMatch = (a: string, b: string) => { + const cleanA = a.replace(/^v/i, '').trim(); + const cleanB = b.replace(/^v/i, '').trim(); + return cleanA === cleanB; + }; + // A self-update restarts the service mid-worker: the worker can die // before writing its result, the state file can stay "running" forever // (recycled pid), and RPC can go away during the swap. The installed @@ -673,10 +679,14 @@ export const TachyonShellMethods = { } const version = await readTachyonVersion(); if (!version) return ''; - if (targetVersion && version === targetVersion) { + if (targetVersion && versionsMatch(version, targetVersion)) { return version; } - if (!targetVersion && baselineVersion && version !== baselineVersion) { + if ( + !targetVersion && + baselineVersion && + !versionsMatch(version, baselineVersion) + ) { return version; } return ''; @@ -691,8 +701,8 @@ export const TachyonShellMethods = { } const version = await readTachyonVersion(); if ( - version === baselineVersion && - (!targetVersion || version === targetVersion) + versionsMatch(version, baselineVersion) && + (!targetVersion || versionsMatch(version, targetVersion)) ) { return version; } diff --git a/fe-app-tachyon/src/tachyon/tabs/dashboard/initController.ts b/fe-app-tachyon/src/tachyon/tabs/dashboard/initController.ts index 587ddd4d..7658fd3b 100644 --- a/fe-app-tachyon/src/tachyon/tabs/dashboard/initController.ts +++ b/fe-app-tachyon/src/tachyon/tabs/dashboard/initController.ts @@ -75,8 +75,6 @@ let dashboardDataUpdatesStarted = false; let dashboardDataUpdatesId = 0; let connectionsRefreshTimer: ReturnType | null = null; let currentConnections: IConnection[] = []; -let connectionsLoading = true; -let connectionsFailed = false; let pageUnloading = false; const followedSubscriptionJobs = new Set(); const followedLatencyJobs = new Set(); @@ -1596,13 +1594,9 @@ async function fetchConnections() { currentConnections = Array.from(map.values()).sort( (a, b) => b.download + b.upload - (a.download + a.upload), ); - connectionsLoading = false; - connectionsFailed = false; - } else { - connectionsFailed = true; } - } catch (e) { - connectionsFailed = true; + } catch (_e) { + // Ignore connections fetch errors } renderConnectionsWidget(); } diff --git a/fe-app-tachyon/src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts b/fe-app-tachyon/src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts index 9bbb70c8..f4e57f08 100644 --- a/fe-app-tachyon/src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts +++ b/fe-app-tachyon/src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts @@ -1,6 +1,5 @@ import { TachyonShellMethods } from '../../../methods'; import { showToast } from '../../../../helpers/showToast'; -import { Tachyon } from '../../../types'; import { getConfigSections } from '../../../methods/custom/getConfigSections'; import { TACHYON_UCI_PACKAGE } from '../../../../constants'; diff --git a/fe-app-tachyon/src/tachyon/tabs/diagnostic/initController.ts b/fe-app-tachyon/src/tachyon/tabs/diagnostic/initController.ts index 56aa2f2e..28ba5a50 100644 --- a/fe-app-tachyon/src/tachyon/tabs/diagnostic/initController.ts +++ b/fe-app-tachyon/src/tachyon/tabs/diagnostic/initController.ts @@ -42,7 +42,6 @@ import { import { TachyonShellMethods } from '../../methods'; import { fetchServicesInfo } from '../../fetchers/fetchServicesInfo'; import { normalizeCompiledVersion } from '../../../helpers/normalizeCompiledVersion'; -import { copyToClipboard } from '../../../helpers/copyToClipboard'; import { renderModal, renderButton } from '../../../partials'; import { TACHYON_LUCI_APP_VERSION } from '../../../constants'; import { renderWikiDisclaimer } from './partials/renderWikiDisclaimer'; @@ -785,7 +784,7 @@ function getAiDoctorHistory(): AiDoctorHistoryEntry[] { try { const raw = localStorage.getItem('tachyon_ai_doctor_history'); return raw ? (JSON.parse(raw) as AiDoctorHistoryEntry[]) : []; - } catch (e) { + } catch (_e) { return []; } } @@ -795,7 +794,7 @@ function saveAiDoctorHistory(entry: AiDoctorHistoryEntry) { const current = getAiDoctorHistory(); const updated = [entry, ...current].slice(0, 5); localStorage.setItem('tachyon_ai_doctor_history', JSON.stringify(updated)); - } catch (e) { + } catch (_e) { // Ignore storage errors } } diff --git a/fe-app-tachyon/src/tachyon/tabs/updates/initController.ts b/fe-app-tachyon/src/tachyon/tabs/updates/initController.ts index 97b55cd2..4490f96b 100644 --- a/fe-app-tachyon/src/tachyon/tabs/updates/initController.ts +++ b/fe-app-tachyon/src/tachyon/tabs/updates/initController.ts @@ -647,11 +647,12 @@ async function followComponentActionState( const jobId = state.job_id; const key = getComponentActionKey(state.component, state.action); - if (!jobId || !key || followedComponentJobs.has(jobId)) { - return; - } - - if (!state.running && handledComponentJobs.has(jobId)) { + if ( + !jobId || + !key || + followedComponentJobs.has(jobId) || + handledComponentJobs.has(jobId) + ) { return; } @@ -714,12 +715,16 @@ async function followAlreadyRunningComponentAction( (item) => item.running && item.component === button.component && - item.action === button.action, + item.action === button.action && + (!item.job_id || !handledComponentJobs.has(item.job_id)), ); if (!state) { state = uiState.actions.component.find( - (item) => item.running && item.component === button.component, + (item) => + item.running && + item.component === button.component && + (!item.job_id || !handledComponentJobs.has(item.job_id)), ); } diff --git a/fe-app-tachyon/src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts b/fe-app-tachyon/src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts index 48557ec3..bd3a70ce 100644 --- a/fe-app-tachyon/src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts +++ b/fe-app-tachyon/src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts @@ -118,7 +118,6 @@ export function showUpdateProgressModal( let elapsedSeconds = 0; let timerInterval: ReturnType | null = null; - let isCompleted = false; let currentModalVersions = { ...options }; const timerBadgeEl = E( @@ -149,21 +148,17 @@ export function showUpdateProgressModal( class: 'tachyon-update-modal__log', }) as HTMLPreElement; - const logPanelEl = E( - 'div', - { class: 'tachyon-update-modal__log-panel' }, - [ - E('div', { class: 'tachyon-update-modal__log-header' }, [ - E('b', {}, _('Operation log')), - renderButton({ - classNames: ['cbi-button-action', 'tachyon-update-modal__log-copy'], - text: _('Copy log'), - onClick: copyLog, - }), - ]), - logPreEl, - ], - ); + const logPanelEl = E('div', { class: 'tachyon-update-modal__log-panel' }, [ + E('div', { class: 'tachyon-update-modal__log-header' }, [ + E('b', {}, _('Operation log')), + renderButton({ + classNames: ['cbi-button-action', 'tachyon-update-modal__log-copy'], + text: _('Copy log'), + onClick: copyLog, + }), + ]), + logPreEl, + ]); const actionButtonContainer = E( 'div', @@ -312,7 +307,6 @@ export function showUpdateProgressModal( titleBadgeEl.textContent = renderVersionBadgeText(currentModalVersions); }, completeSuccess: (message?: string, opts?: CompleteSuccessOptions) => { - isCompleted = true; cleanupTimers(); finishLogTracking(); @@ -400,7 +394,6 @@ export function showUpdateProgressModal( } }, completeError: (errorMessage: string) => { - isCompleted = true; cleanupTimers(); finishLogTracking(); diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js index c9d8c431..fc455e6d 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js @@ -3587,6 +3587,11 @@ var TachyonShellMethods = { const transientRpc = createTransientRpcGraceTracker( COMPONENT_ACTION_TRANSIENT_RPC_GRACE_MS ); + const versionsMatch = (a, b) => { + const cleanA = a.replace(/^v/i, "").trim(); + const cleanB = b.replace(/^v/i, "").trim(); + return cleanA === cleanB; + }; const confirmedByVersion = async () => { if (!isSelfUpdate) return ""; if (Date.now() - jobStartedAt < COMPONENT_ACTION_MIN_ELAPSED_FOR_SELF_UPDATE_MS) { @@ -3594,10 +3599,10 @@ var TachyonShellMethods = { } const version = await readTachyonVersion(); if (!version) return ""; - if (targetVersion && version === targetVersion) { + if (targetVersion && versionsMatch(version, targetVersion)) { return version; } - if (!targetVersion && baselineVersion && version !== baselineVersion) { + if (!targetVersion && baselineVersion && !versionsMatch(version, baselineVersion)) { return version; } return ""; @@ -3607,7 +3612,7 @@ var TachyonShellMethods = { return ""; } const version = await readTachyonVersion(); - if (version === baselineVersion && (!targetVersion || version === targetVersion)) { + if (versionsMatch(version, baselineVersion) && (!targetVersion || versionsMatch(version, targetVersion))) { return version; } return ""; @@ -6459,8 +6464,6 @@ var dashboardDataUpdatesStarted = false; var dashboardDataUpdatesId = 0; var connectionsRefreshTimer = null; var currentConnections = []; -var connectionsLoading = true; -var connectionsFailed = false; var pageUnloading = false; var followedSubscriptionJobs = /* @__PURE__ */ new Set(); var followedLatencyJobs = /* @__PURE__ */ new Set(); @@ -7625,13 +7628,8 @@ async function fetchConnections() { currentConnections = Array.from(map.values()).sort( (a, b) => b.download + b.upload - (a.download + a.upload) ); - connectionsLoading = false; - connectionsFailed = false; - } else { - connectionsFailed = true; } - } catch (e) { - connectionsFailed = true; + } catch (_e) { } renderConnectionsWidget(); } @@ -12167,7 +12165,7 @@ function getAiDoctorHistory() { try { const raw = localStorage.getItem("tachyon_ai_doctor_history"); return raw ? JSON.parse(raw) : []; - } catch (e) { + } catch (_e) { return []; } } @@ -12176,7 +12174,7 @@ function saveAiDoctorHistory(entry) { const current = getAiDoctorHistory(); const updated = [entry, ...current].slice(0, 5); localStorage.setItem("tachyon_ai_doctor_history", JSON.stringify(updated)); - } catch (e) { + } catch (_e) { } } async function handleViewLogs() { @@ -15323,7 +15321,6 @@ function showUpdateProgressModal(options) { } let elapsedSeconds = 0; let timerInterval = null; - let isCompleted = false; let currentModalVersions = { ...options }; const timerBadgeEl = E( "div", @@ -15349,21 +15346,17 @@ function showUpdateProgressModal(options) { const logPreEl = E("pre", { class: "tachyon-update-modal__log" }); - const logPanelEl = E( - "div", - { class: "tachyon-update-modal__log-panel" }, - [ - E("div", { class: "tachyon-update-modal__log-header" }, [ - E("b", {}, _("Operation log")), - renderButton({ - classNames: ["cbi-button-action", "tachyon-update-modal__log-copy"], - text: _("Copy log"), - onClick: copyLog - }) - ]), - logPreEl - ] - ); + const logPanelEl = E("div", { class: "tachyon-update-modal__log-panel" }, [ + E("div", { class: "tachyon-update-modal__log-header" }, [ + E("b", {}, _("Operation log")), + renderButton({ + classNames: ["cbi-button-action", "tachyon-update-modal__log-copy"], + text: _("Copy log"), + onClick: copyLog + }) + ]), + logPreEl + ]); const actionButtonContainer = E( "div", { class: "tachyon-update-modal__actions" }, @@ -15484,7 +15477,6 @@ function showUpdateProgressModal(options) { titleBadgeEl.textContent = renderVersionBadgeText(currentModalVersions); }, completeSuccess: (message, opts) => { - isCompleted = true; cleanupTimers(); finishLogTracking(); const successMsg = message || (isCheckAction ? _("Check completed!") : options.action === "remove" ? _("Removal completed successfully!") : _("Update completed successfully!")); @@ -15548,7 +15540,6 @@ function showUpdateProgressModal(options) { } }, completeError: (errorMessage) => { - isCompleted = true; cleanupTimers(); finishLogTracking(); actionButtonContainer.replaceChildren( @@ -16015,10 +16006,7 @@ async function completeComponentActionJob(key, jobId, response) { async function followComponentActionState(state) { const jobId = state.job_id; const key = getComponentActionKey(state.component, state.action); - if (!jobId || !key || followedComponentJobs.has(jobId)) { - return; - } - if (!state.running && handledComponentJobs.has(jobId)) { + if (!jobId || !key || followedComponentJobs.has(jobId) || handledComponentJobs.has(jobId)) { return; } followedComponentJobs.add(jobId); @@ -16067,11 +16055,11 @@ async function followAlreadyRunningComponentAction(button) { return false; } let state = uiState.actions.component.find( - (item) => item.running && item.component === button.component && item.action === button.action + (item) => item.running && item.component === button.component && item.action === button.action && (!item.job_id || !handledComponentJobs.has(item.job_id)) ); if (!state) { state = uiState.actions.component.find( - (item) => item.running && item.component === button.component + (item) => item.running && item.component === button.component && (!item.job_id || !handledComponentJobs.has(item.job_id)) ); } if (!state) { diff --git a/tachyon/files/usr/lib/service/event_controller.uc b/tachyon/files/usr/lib/service/event_controller.uc index c09fd0b9..d710ec77 100644 --- a/tachyon/files/usr/lib/service/event_controller.uc +++ b/tachyon/files/usr/lib/service/event_controller.uc @@ -732,13 +732,31 @@ function controller(bus, opts) { } if (setting("recovery_bypass", "0") == "1") return; - let proto = uci_core.get("network", "wan", "proto") || "pppoe"; - let device = trim(uci_core.get("network", "wan", "device") || "eth0"); + let proto = uci_core.get("network", "wan", "proto") || "dhcp"; + let device = trim(setting("output_network_interface", "") || + uci_core.get("network", "wan", "device") || + uci_core.get("network", "wan", "ifname") || ""); + if (device == "") { + let def_route = command_capture("ip -4 route show default 2>/dev/null").output; + let m_dev = match(def_route, /dev\s+([^\s]+)/); + if (m_dev) device = m_dev[1]; + } + if (device == "") device = "eth0"; let iface = (proto == "pppoe") ? "pppoe-wan" : device; let addr_out = command_capture("ip addr show " + shell_quote(iface) + " 2>/dev/null").output; let no_address = index(addr_out, "inet ") < 0; + if (no_address) { + let ubus_data = command_output_from_args([ "ubus", "-S", "call", "network.interface.wan", "status" ]); + try { + let ubus_obj = json(ubus_data); + if (type(ubus_obj) == "object" && ubus_obj.up && length(common.array_or_empty(ubus_obj["ipv4-address"])) > 0) { + no_address = false; + } + } catch (e) {} + } + let route_out = command_capture("ip route 2>/dev/null").output; let no_gateway = index(route_out, "default") < 0; diff --git a/tachyon/files/usr/lib/service/ui.uc b/tachyon/files/usr/lib/service/ui.uc index e43bfe49..23bbb8ab 100644 --- a/tachyon/files/usr/lib/service/ui.uc +++ b/tachyon/files/usr/lib/service/ui.uc @@ -723,16 +723,12 @@ function refresh_pid_job_state(path, stale_message) { return; let now = now_seconds(); - let updated_at = arg_number(value.updated_at || value.started_at); let pid = as_string(value.pid || ""); if (job_pid_valid(pid) && pid_running(pid)) return; - if (updated_at > 0 && (now - updated_at) < 180) - return; - - let within_grace = job_started_at_within_grace(value.started_at, now, 600); + let within_grace = job_started_at_within_grace(value.started_at, now, int(ACTION_STALE_GRACE_SECONDS)); if (!within_grace) write_stale_action_state(path, stale_message); } @@ -812,6 +808,7 @@ function action_state_from_dir(dir) { } function action_state_from_dirs() { + refresh_action_dirs(); return { service: action_state_from_dir(SERVICE_ACTION_DIR), latency: action_state_from_dir(LATENCY_ACTION_DIR), diff --git a/uc.out b/uc.out deleted file mode 100644 index 9fce5152094c9b0df1543414b2cd96d77bca5c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5353 zcmeHK-ESOM6`%F)W^Biaov)^e9Xd&zI^Nyc@w!f&n#7Q_Zt?)Aa$6z<$Qtk7T@T)! zna+pX2%&Bs{8k<+kV;hv9{LoJA)=z>1B42)gg`tXgv8&%hr;=tduL{MaSTX2a;1Cj zJ?H$+x#!+F_uSp{XRSC4taZn=gu7+Lb*~{tC*%6MrfGTVIqEm9jvHAUUKm*|E3)ew zcRV*8n@(GV?4#|hr_xXaTTWeAvFo^DWVhSZxK5sAd+od3kN*6J?)%*je{{F|z3+VR z+lMsl-0lAG(a!zu_ilM0r{itwwoO1+*}Rz)Qa0YbOQFPHBC8c2UOfTkIleT z_+jvoP5WRwZM4XvUsrYqSEPJQ%C$qgQ9$VAT(!g7Qa`>c#|gzBVSLk0);YE!^~cqC zqR7YMiUlcuM%kTI`e%2g{FE9`L*Cl-y=hHsc^wfbyUC)&O)_rNx7EI;fdBH%)$7xu z6~w-2(z$7o^R}RU<{wBKrW3}p)eu{j8@JoW{POcPr91xB*X1QJ-bm8>>jvp&UyD57 zcifg?yA7k^g!XzHT!L1YqFSv=N5?D?)h)+$qG}_#Inb0|Y73h@w~W{~noc0zq6@@6 z-DX5SMQ8*u(ZtcYRS`yB0E31bcI@y?qwcv)rxnxpa6C7`P4apQk}X*U80$`2QOCEQ zu0ON4-Ylga;6>A(<8@>RsXJ9S%yoNHqBKqJzaIykDfc5925qY4xORJBUq&x$P z-;8~AmaywPPqhumjaTD<++-hO&(lflRXi$Q*pKmkGQsi=d-->3eSxOm63WR8fT3x0hFpQ)%yJSUL*DMD$n=y zqf~uplL0(OOCtgUKcqTpFnGMt=w}pPpb`C?;&CSEFDU*1jp!?i z*J(sgVgOdtvbS@)(W$Swv3gUrAb*x_0j zp*Bf=xb3w_AY>+M+L81tuJtBE?sxjXYqR=nrILZZm`(SXl``48+4TRj|Jeyt_40|4 zSzVviKhWj;l3p=&>eu%+BUIEdY~dZz(T@I@{61tRxOVM=%o z<_OITI%pnY9)_?0lE1-1{XF*ZCCrOUC;)jVhv2XPws??;23e6y0pCh!xjf`e2AWU} zEiF`bWezfFRZDxg5-BrirIyn+^i4@Nl)L3N`jsKj)oq!=?rlE0_? z<|BPI3A^Ps1jrN)d+rYyLJ;CsqwZaZ`d*$P(u2-uR zdf9xL5Hhx-d)CKX@_mNt^l5fDo3PS2XVVh+9HQrpn}FuXX|PXKA01G0+{XssML0SR zasuQTkdq+K!dhl>w)S4=Q~wqE^!{jeHVMoW$Qh8&JT;QN(9i9UHsJ9r2ygY=0GiL} z{J*FN3z&+B)8LmuMGHP{31NP*t6601T%H-K_JWyFxhHLJ*%yzt9^E_)t2^Ojn%!Y zrQWV)-m- z`3C-rqcr}PW&A^0`aW?jMC~Hup2j>JqEjpEsP{$H(85la6Ij~ zOS+01;l2SgskyAbP8Jd@XVzCTExhWr1i}`T8VePvVC5>Ui>SFR08KOcq0WXpEs@Xv q6k5~ih~R3TO)B5gXR*kv+#DWtT2W6tR;1XX9pID`TUei@xA317>*hWH