fix: clarify update network failures
This commit is contained in:
BIN
docs/pr-media/1321/update-network-error.png
Normal file
BIN
docs/pr-media/1321/update-network-error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
30
static/ui.js
30
static/ui.js
@@ -3202,8 +3202,29 @@ function dismissUpdate(){
|
||||
const b=$('updateBanner');if(b)b.classList.remove('visible');
|
||||
sessionStorage.setItem('hermes-update-dismissed','1');
|
||||
}
|
||||
function _isUpdateApplyNetworkError(error){
|
||||
const message=(error&&error.message)||String(error||'');
|
||||
return /Failed to fetch|NetworkError|Load failed/i.test(message);
|
||||
}
|
||||
function _formatUpdateApplyExceptionMessage(error){
|
||||
if(_isUpdateApplyNetworkError(error)){
|
||||
return 'Update failed: could not reach the WebUI server. It may have restarted or the connection was interrupted. Please wait a few seconds, reload the page, then check the server if it still does not come back.';
|
||||
}
|
||||
const message=(error&&error.message)||String(error||'unknown error');
|
||||
return 'Update failed: '+message;
|
||||
}
|
||||
async function applyUpdates(){
|
||||
if(window._updateApplyInFlight) return;
|
||||
window._updateApplyInFlight=true;
|
||||
const btn=$('btnApplyUpdate');
|
||||
const resetApplyButton=(delayMs)=>{
|
||||
const reset=()=>{
|
||||
window._updateApplyInFlight=false;
|
||||
if(btn){btn.disabled=false;btn.textContent='Update Now';}
|
||||
};
|
||||
if(delayMs>0) setTimeout(reset,delayMs);
|
||||
else reset();
|
||||
};
|
||||
if(btn){btn.disabled=true;btn.textContent='Updating\u2026';}
|
||||
const errEl=$('updateError');
|
||||
if(errEl){errEl.style.display='none';errEl.textContent='';}
|
||||
@@ -3219,7 +3240,7 @@ async function applyUpdates(){
|
||||
const res=await api('/api/updates/apply',{method:'POST',body:JSON.stringify({target})});
|
||||
if(!res.ok){
|
||||
_showUpdateError(target,res);
|
||||
if(btn){btn.disabled=false;btn.textContent='Update Now';}
|
||||
resetApplyButton(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3228,9 +3249,10 @@ async function applyUpdates(){
|
||||
sessionStorage.removeItem('hermes-update-dismissed');
|
||||
_waitForServerThenReload();
|
||||
}catch(e){
|
||||
if(errEl){errEl.textContent='Update failed: '+e.message;errEl.style.display='block';}
|
||||
else showToast('Update failed: '+e.message);
|
||||
if(btn){btn.disabled=false;btn.textContent='Update Now';}
|
||||
const msg=_formatUpdateApplyExceptionMessage(e);
|
||||
if(errEl){errEl.textContent=msg;errEl.style.display='block';}
|
||||
else showToast(msg);
|
||||
resetApplyButton(_isUpdateApplyNetworkError(e)?5000:0);
|
||||
}
|
||||
}
|
||||
function _showUpdateError(target,res){
|
||||
|
||||
42
tests/test_update_apply_ui.py
Normal file
42
tests/test_update_apply_ui.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Frontend regression coverage for Update Now apply failures (#1321)."""
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
UI_JS = ROOT / "static" / "ui.js"
|
||||
|
||||
|
||||
def _ui_js() -> str:
|
||||
return UI_JS.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_update_apply_network_error_has_recovery_message_not_raw_failed_to_fetch():
|
||||
"""Network/interrupted update apply failures should not surface raw fetch text alone."""
|
||||
src = _ui_js()
|
||||
assert "function _formatUpdateApplyExceptionMessage" in src
|
||||
assert "could not reach the WebUI server" in src
|
||||
assert "restarted or the connection was interrupted" in src
|
||||
assert "wait a few seconds, reload the page, then check the server" in src
|
||||
assert "Update failed: '+e.message" not in src
|
||||
assert 'Update failed: "+e.message' not in src
|
||||
|
||||
|
||||
def test_update_apply_structured_server_errors_still_use_json_message_path():
|
||||
"""Server-reachable JSON errors must keep the existing targeted message path."""
|
||||
src = _ui_js()
|
||||
apply_start = src.index("async function applyUpdates()")
|
||||
show_error_call = src.index("_showUpdateError(target,res);", apply_start)
|
||||
reset_button = src.index("resetApplyButton(0);", show_error_call)
|
||||
assert show_error_call < reset_button
|
||||
assert "const msg='Update failed ('+target+'): '+(res.message||'unknown error');" in src
|
||||
|
||||
|
||||
def test_update_apply_prevents_duplicate_apply_requests_while_in_flight():
|
||||
"""Double-clicks should not send a second update apply request during restart race windows."""
|
||||
src = _ui_js()
|
||||
apply_start = src.index("async function applyUpdates()")
|
||||
next_fn = src.index("function _showUpdateError", apply_start)
|
||||
body = src[apply_start:next_fn]
|
||||
assert "window._updateApplyInFlight" in body
|
||||
assert "if(window._updateApplyInFlight) return;" in body
|
||||
assert "window._updateApplyInFlight=true;" in body
|
||||
assert "window._updateApplyInFlight=false;" in body
|
||||
Reference in New Issue
Block a user