Clear the stack update pill immediately after a manual/auto update (0.38.4)
The amber image-update indicator is fed from update_service._CACHE, which only the background loop refreshed — after a per-stack Update/Pull the stale digests kept the pill on until the next pass. Now the local digests are reconciled with the cached remote digests right after a successful pull/update (local backend, agent lifecycle, auto-update pass), and the frontend invalidates the stack-updates queries after actions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e651029ab2
commit
9119f94536
@@ -374,6 +374,8 @@ async def lifecycle(stack_id: str, action: str) -> dict:
|
|||||||
"detail": result.get("stderr", "").strip()[-2000:],
|
"detail": result.get("stderr", "").strip()[-2000:],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if action in ("pull", "update"):
|
||||||
|
update_service.refresh_stack_local(stack_id)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -329,12 +329,16 @@ async def restart_stack(stack_id: str, request: Request, session: Session = Depe
|
|||||||
|
|
||||||
@router.post("/{stack_id}/pull")
|
@router.post("/{stack_id}/pull")
|
||||||
async def pull_stack(stack_id: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin)):
|
async def pull_stack(stack_id: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin)):
|
||||||
return await _lifecycle(compose_service.pull, "pull", stack_id, request, session, user)
|
result = await _lifecycle(compose_service.pull, "pull", stack_id, request, session, user)
|
||||||
|
update_service.refresh_stack_local(stack_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{stack_id}/update")
|
@router.post("/{stack_id}/update")
|
||||||
async def update_stack_images(stack_id: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin)):
|
async def update_stack_images(stack_id: str, request: Request, session: Session = Depends(get_session), user: User = Depends(require_admin)):
|
||||||
return await _lifecycle(compose_service.update, "update", stack_id, request, session, user)
|
result = await _lifecycle(compose_service.update, "update", stack_id, request, session, user)
|
||||||
|
update_service.refresh_stack_local(stack_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{stack_id}/down")
|
@router.post("/{stack_id}/down")
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ async def _run_local(session: Session, policy: AutoUpdate) -> None:
|
|||||||
_record(session, policy, "error", str(exc))
|
_record(session, policy, "error", str(exc))
|
||||||
await _safe_notify(EVENT_PULL_FAILED, f"Auto-update of '{stack_id}' failed", str(exc), session)
|
await _safe_notify(EVENT_PULL_FAILED, f"Auto-update of '{stack_id}' failed", str(exc), session)
|
||||||
return
|
return
|
||||||
|
update_service.refresh_stack_local(stack_id)
|
||||||
_record(session, policy, "updated", stale)
|
_record(session, policy, "updated", stale)
|
||||||
await _safe_notify(
|
await _safe_notify(
|
||||||
EVENT_STACK_AUTO_UPDATED, f"Stack '{stack_id}' auto-updated",
|
EVENT_STACK_AUTO_UPDATED, f"Stack '{stack_id}' auto-updated",
|
||||||
|
|||||||
@@ -269,6 +269,25 @@ async def stack_updates(stack_id: str, refresh: bool = True) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_stack_local(stack_id: str) -> None:
|
||||||
|
"""Re-read the local digests of one stack's images and reconcile them with
|
||||||
|
the cached remote digests (no registry calls). Called right after a manual
|
||||||
|
pull/update so the amber indicator clears immediately instead of lingering
|
||||||
|
until the next background pass."""
|
||||||
|
for image in stack_images(stack_id):
|
||||||
|
status = _CACHE.get(image)
|
||||||
|
if status is None:
|
||||||
|
continue
|
||||||
|
local = _local_digest(image)
|
||||||
|
status.current_digest = local
|
||||||
|
status.update_available = bool(
|
||||||
|
local and status.remote_digest and local != status.remote_digest
|
||||||
|
)
|
||||||
|
status.checked_at = time.time()
|
||||||
|
if not status.update_available:
|
||||||
|
_NOTIFIED.discard(image)
|
||||||
|
|
||||||
|
|
||||||
async def check_all() -> dict[str, dict]:
|
async def check_all() -> dict[str, dict]:
|
||||||
images = _all_running_images()
|
images = _all_running_images()
|
||||||
for image in images:
|
for image in images:
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
"""Single source of truth for the StackPilot release version."""
|
"""Single source of truth for the StackPilot release version."""
|
||||||
|
|
||||||
APP_VERSION = "0.38.3"
|
APP_VERSION = "0.38.4"
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function AgentStacksSection({ agent, isAdmin }: { agent: Agent; isAdmin:
|
|||||||
await agentsApi.action(agent.id, id, action);
|
await agentsApi.action(agent.id, id, action);
|
||||||
toast.success(`${label} ${id} ✓`, { id: t });
|
toast.success(`${label} ${id} ✓`, { id: t });
|
||||||
qc.invalidateQueries({ queryKey: ["agent-stacks", agent.id] });
|
qc.invalidateQueries({ queryKey: ["agent-stacks", agent.id] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["agent-stack-updates", agent.id] });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(apiErrorMessage(e), { id: t });
|
toast.error(apiErrorMessage(e), { id: t });
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function useStackActions() {
|
|||||||
toast.success(`${label} ${id} ✓`, { id: t });
|
toast.success(`${label} ${id} ✓`, { id: t });
|
||||||
qc.invalidateQueries({ queryKey: ["stacks"] });
|
qc.invalidateQueries({ queryKey: ["stacks"] });
|
||||||
qc.invalidateQueries({ queryKey: ["stack", id] });
|
qc.invalidateQueries({ queryKey: ["stack", id] });
|
||||||
|
qc.invalidateQueries({ queryKey: ["stack-updates"] });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(apiErrorMessage(err), { id: t });
|
toast.error(apiErrorMessage(err), { id: t });
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user