fix: apply_update concurrency lock, boot.js settings-fail guard, dead workspace code, test_updates URL param
- api/updates.py: add _apply_lock to prevent concurrent stash/pull/pop - static/boot.js: set check_for_updates:false on settings fetch failure - static/panels.js: remove dead settingsWorkspace references (element removed from HTML) - api/routes.py + static/boot.js: add ?test_updates=1 URL param for testing banner without being behind on git (localhost-only simulate endpoint)
This commit is contained in:
@@ -231,7 +231,15 @@ def handle_get(handler, parsed) -> bool:
|
|||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
if not settings.get('check_for_updates', True):
|
if not settings.get('check_for_updates', True):
|
||||||
return j(handler, {'disabled': True})
|
return j(handler, {'disabled': True})
|
||||||
force = parse_qs(parsed.query).get('force', ['0'])[0] == '1'
|
qs = parse_qs(parsed.query)
|
||||||
|
force = qs.get('force', ['0'])[0] == '1'
|
||||||
|
# ?simulate=1 returns fake behind counts for UI testing (localhost only)
|
||||||
|
if qs.get('simulate', ['0'])[0] == '1' and handler.client_address[0] == '127.0.0.1':
|
||||||
|
return j(handler, {
|
||||||
|
'webui': {'name': 'webui', 'behind': 3, 'current_sha': 'abc1234', 'latest_sha': 'def5678', 'branch': 'master'},
|
||||||
|
'agent': {'name': 'agent', 'behind': 1, 'current_sha': 'aaa0001', 'latest_sha': 'bbb0002', 'branch': 'master'},
|
||||||
|
'checked_at': 0,
|
||||||
|
})
|
||||||
from api.updates import check_for_updates
|
from api.updates import check_for_updates
|
||||||
return j(handler, check_for_updates(force=force))
|
return j(handler, check_for_updates(force=force))
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ except ImportError:
|
|||||||
_update_cache = {'webui': None, 'agent': None, 'checked_at': 0}
|
_update_cache = {'webui': None, 'agent': None, 'checked_at': 0}
|
||||||
_cache_lock = threading.Lock()
|
_cache_lock = threading.Lock()
|
||||||
_check_in_progress = False
|
_check_in_progress = False
|
||||||
|
_apply_lock = threading.Lock() # prevents concurrent stash/pull/pop on same repo
|
||||||
CACHE_TTL = 1800 # 30 minutes
|
CACHE_TTL = 1800 # 30 minutes
|
||||||
|
|
||||||
|
|
||||||
@@ -108,6 +109,16 @@ def check_for_updates(force=False):
|
|||||||
|
|
||||||
def apply_update(target):
|
def apply_update(target):
|
||||||
"""Stash, pull --ff-only, pop for the given target repo."""
|
"""Stash, pull --ff-only, pop for the given target repo."""
|
||||||
|
if not _apply_lock.acquire(blocking=False):
|
||||||
|
return {'ok': False, 'message': 'Update already in progress'}
|
||||||
|
try:
|
||||||
|
return _apply_update_inner(target)
|
||||||
|
finally:
|
||||||
|
_apply_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_update_inner(target):
|
||||||
|
"""Inner implementation of apply_update, called under _apply_lock."""
|
||||||
if target == 'webui':
|
if target == 'webui':
|
||||||
path = REPO_ROOT
|
path = REPO_ROOT
|
||||||
elif target == 'agent':
|
elif target == 'agent':
|
||||||
|
|||||||
@@ -309,10 +309,13 @@ document.querySelectorAll('.suggestion').forEach(btn=>{
|
|||||||
(async()=>{
|
(async()=>{
|
||||||
// Load send key preference
|
// Load send key preference
|
||||||
let _bootSettings={};
|
let _bootSettings={};
|
||||||
try{const s=await api('/api/settings');_bootSettings=s;window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;window._showCliSessions=!!s.show_cli_sessions;const _theme=s.theme||'dark';document.documentElement.dataset.theme=_theme;localStorage.setItem('hermes-theme',_theme);}catch(e){window._sendKey='enter';window._showTokenUsage=false;window._showCliSessions=false;}
|
try{const s=await api('/api/settings');_bootSettings=s;window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;window._showCliSessions=!!s.show_cli_sessions;const _theme=s.theme||'dark';document.documentElement.dataset.theme=_theme;localStorage.setItem('hermes-theme',_theme);}catch(e){window._sendKey='enter';window._showTokenUsage=false;window._showCliSessions=false;_bootSettings={check_for_updates:false};}
|
||||||
// Non-blocking update check (fire-and-forget, once per tab session)
|
// Non-blocking update check (fire-and-forget, once per tab session)
|
||||||
if(_bootSettings.check_for_updates!==false&&!sessionStorage.getItem('hermes-update-checked')&&!sessionStorage.getItem('hermes-update-dismissed')){
|
// ?test_updates=1 in URL forces banner display for testing (bypasses sessionStorage guards)
|
||||||
api('/api/updates/check').then(d=>{sessionStorage.setItem('hermes-update-checked','1');if((d.webui&&d.webui.behind>0)||(d.agent&&d.agent.behind>0))_showUpdateBanner(d);}).catch(()=>{});
|
const _testUpdates=new URLSearchParams(location.search).get('test_updates')==='1';
|
||||||
|
if(_testUpdates||(_bootSettings.check_for_updates!==false&&!sessionStorage.getItem('hermes-update-checked')&&!sessionStorage.getItem('hermes-update-dismissed'))){
|
||||||
|
const _checkUrl='/api/updates/check'+(_testUpdates?'?simulate=1':'');
|
||||||
|
api(_checkUrl).then(d=>{if(!_testUpdates)sessionStorage.setItem('hermes-update-checked','1');if((d.webui&&d.webui.behind>0)||(d.agent&&d.agent.behind>0))_showUpdateBanner(d);}).catch(()=>{});
|
||||||
}
|
}
|
||||||
// Fetch active profile
|
// Fetch active profile
|
||||||
try{const p=await api('/api/profile/active');S.activeProfile=p.name||'default';}catch(e){S.activeProfile='default';}
|
try{const p=await api('/api/profile/active');S.activeProfile=p.name||'default';}catch(e){S.activeProfile='default';}
|
||||||
|
|||||||
@@ -995,21 +995,6 @@ async function loadSettingsPanel(){
|
|||||||
modelSel.value=settings.default_model||'';
|
modelSel.value=settings.default_model||'';
|
||||||
modelSel.addEventListener('change',_markSettingsDirty,{once:false});
|
modelSel.addEventListener('change',_markSettingsDirty,{once:false});
|
||||||
}
|
}
|
||||||
// Populate workspace dropdown from /api/workspaces
|
|
||||||
const wsSel=$('settingsWorkspace');
|
|
||||||
if(wsSel){
|
|
||||||
wsSel.innerHTML='';
|
|
||||||
try{
|
|
||||||
const wsData=await api('/api/workspaces');
|
|
||||||
for(const w of (wsData.workspaces||[])){
|
|
||||||
const opt=document.createElement('option');
|
|
||||||
opt.value=w.path;opt.textContent=w.name||w.path;
|
|
||||||
wsSel.appendChild(opt);
|
|
||||||
}
|
|
||||||
}catch(e){}
|
|
||||||
wsSel.value=settings.default_workspace||'';
|
|
||||||
wsSel.addEventListener('change',_markSettingsDirty,{once:false});
|
|
||||||
}
|
|
||||||
// Send key preference
|
// Send key preference
|
||||||
const sendKeySel=$('settingsSendKey');
|
const sendKeySel=$('settingsSendKey');
|
||||||
if(sendKeySel){sendKeySel.value=settings.send_key||'enter';sendKeySel.addEventListener('change',_markSettingsDirty,{once:false});}
|
if(sendKeySel){sendKeySel.value=settings.send_key||'enter';sendKeySel.addEventListener('change',_markSettingsDirty,{once:false});}
|
||||||
@@ -1043,7 +1028,6 @@ async function loadSettingsPanel(){
|
|||||||
|
|
||||||
async function saveSettings(andClose){
|
async function saveSettings(andClose){
|
||||||
const model=($('settingsModel')||{}).value;
|
const model=($('settingsModel')||{}).value;
|
||||||
const workspace=($('settingsWorkspace')||{}).value;
|
|
||||||
const sendKey=($('settingsSendKey')||{}).value;
|
const sendKey=($('settingsSendKey')||{}).value;
|
||||||
const showTokenUsage=!!($('settingsShowTokenUsage')||{}).checked;
|
const showTokenUsage=!!($('settingsShowTokenUsage')||{}).checked;
|
||||||
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
|
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
|
||||||
@@ -1051,7 +1035,7 @@ async function saveSettings(andClose){
|
|||||||
const theme=($('settingsTheme')||{}).value||'dark';
|
const theme=($('settingsTheme')||{}).value||'dark';
|
||||||
const body={};
|
const body={};
|
||||||
if(model) body.default_model=model;
|
if(model) body.default_model=model;
|
||||||
if(workspace) body.default_workspace=workspace;
|
|
||||||
if(sendKey) body.send_key=sendKey;
|
if(sendKey) body.send_key=sendKey;
|
||||||
body.theme=theme;
|
body.theme=theme;
|
||||||
body.show_token_usage=showTokenUsage;
|
body.show_token_usage=showTokenUsage;
|
||||||
|
|||||||
Reference in New Issue
Block a user