Skip to content

feat(web): update-available banner in web UI#311

Open
ChuckBuilds wants to merge 1 commit intomainfrom
feat/update-banner
Open

feat(web): update-available banner in web UI#311
ChuckBuilds wants to merge 1 commit intomainfrom
feat/update-banner

Conversation

@ChuckBuilds
Copy link
Copy Markdown
Owner

@ChuckBuilds ChuckBuilds commented Apr 16, 2026

Summary

  • Adds a dismissible banner between header and nav tabs that appears when local repo is behind origin/main
  • New GET /api/v3/system/check-update endpoint compares local HEAD vs origin/main SHA (cached 5 min)
  • "Update Now" button triggers existing git_pull action; banner hides on success
  • Dismiss persists for the browser session (sessionStorage)
  • Styled for both light and dark themes
  • Re-checks every 30 minutes for long-lived tabs

Test plan

  • Load web UI when repo is behind main — banner should appear with commit count
  • Click "Update Now" — should pull, show success notification, hide banner
  • Click dismiss (×) — banner hides, stays hidden on page refresh within same session
  • Open new browser session — banner reappears if still behind
  • Toggle dark/light theme — banner should look clean in both
  • When repo is up to date — no banner shown

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Automatic update checking: System now periodically scans for available software updates and displays a notification banner when one is found.
    • Update management: Users can apply updates or dismiss notifications directly from the interface with dedicated action buttons.
    • Dark mode support: Update banner adapts to the selected theme.

Adds a polite, dismissible banner between the header and navigation
tabs that appears when the local repo is behind origin/main. Shows
commit count and a one-click "Update Now" button that triggers the
existing git_pull action.

- New GET /api/v3/system/check-update endpoint (5-min cache, compares
  local HEAD vs origin/main SHA)
- Banner auto-checks on page load then every 30 minutes
- Dismiss persists for the browser session via sessionStorage
- Styled for both light and dark themes
- Cache invalidated after successful git_pull so banner hides immediately

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

A new update-availability checking system is introduced. The backend provides a cached API endpoint using git commands to detect available updates, while the frontend displays an update banner with dismiss and apply functionality.

Changes

Cohort / File(s) Summary
Backend Update Check
web_interface/blueprints/api_v3.py
Added new GET /system/check-update endpoint with 5-minute TTL caching. Computes local/remote git SHAs and commit count via git commands (git fetch, git rev-parse, git log). Returns structured update availability data or error responses. Clears cache after git_pull executions.
Frontend Styling
web_interface/static/v3/app.css
Added CSS classes for update banner component (.update-banner, .update-banner-action, .update-banner-dismiss) with light/dark theme variants, including hover states for actions and dismiss button opacity transitions.
Frontend UI & Logic
web_interface/templates/v3/base.html
Added hidden update banner markup with dynamic text and two action buttons. Implemented client-side polling (30-minute interval) to GET /api/v3/system/check-update, manages banner visibility via dismissal flag in sessionStorage, and executes update via POST /api/v3/system/action with git_pull action.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser
    participant SessionStorage
    participant Server
    participant Git

    Browser->>+Server: GET /api/v3/system/check-update (every 30 min)
    Server->>+Git: git fetch origin main
    Git-->>-Server: fetch complete
    Server->>Git: git rev-parse HEAD
    Server->>Git: git rev-parse origin/main
    Server->>Git: git log HEAD..origin/main --oneline
    Git-->>Server: commit info
    Server-->>-Browser: {update_available, local_sha, remote_sha, commits_behind}
    
    Browser->>SessionStorage: Check update-dismissed flag
    alt update_available && !dismissed
        Browser->>Browser: Show update banner
    else
        Browser->>Browser: Hide update banner
    end

    User->>Browser: Click "Update Now"
    Browser->>+Server: POST /api/v3/system/action {action: 'git_pull'}
    Server->>Git: Execute git pull
    Git-->>Server: pull complete
    Server->>Server: Clear _update_check_cache
    Server-->>-Browser: Success response
    Browser->>Browser: Hide banner & show notification
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an update-available banner to the web UI, which is the primary feature implemented across all three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/update-banner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 1344-1345: The shared cache _update_check_cache is accessed
concurrently causing race conditions; protect all reads and writes (including
clear() and checks using _UPDATE_CHECK_TTL) by introducing a dedicated
threading.Lock (e.g., _update_check_lock) and wrapping every access to
_update_check_cache and its keys in a with _update_check_lock block inside
check_for_update(), git_pull(), and any other functions that touch the cache so
ts/data are updated and read atomically.
- Around line 1370-1386: The current logic treats any local_sha != remote_sha as
an available update; change it to detect whether origin/main is actually ahead
of the checkout by running a git compare and only set update_available=True when
the remote has commits the local lacks. For example, after obtaining local_sha
and remote_sha (existing variables), run a git rev-list --left-right --count
local_sha...origin/main (or use git rev-list local_sha..origin/main) in
project_dir and parse the counts to determine commits_behind (remote-only
count); set update_available = True only when commits_behind > 0 and populate
latest_message/commits_behind from that result (use the existing log_result
parsing for commit messages when commits_behind > 0).
- Around line 1357-1377: The git subprocess calls in the update-check block (the
fetch, the two rev-parse calls that populate local_sha/remote_sha, and the git
log that assigns log_result) must have their return codes validated before
building a success payload; update the code around subprocess.run calls in
api_v3.py to either use check=True or inspect each CompletedProcess.returncode
and on failure log/process stderr (from .stderr) and return an error payload
(status: "error" or similar) instead of treating empty/stale SHAs as success,
ensuring you don't cache a success response when any of fetch, rev-parse, or log
failed; reference variables: the subprocess.run for fetch, local_sha assignment,
remote_sha assignment, and log_result when adding these checks.

In `@web_interface/templates/v3/base.html`:
- Around line 951-955: The dismiss button markup that invokes
dismissUpdateBanner() and uses class "update-banner-dismiss" is icon-only and
needs explicit accessibility attributes: add aria-label="Dismiss update" (or
similar descriptive text) to the <button> and also include type="button" to
avoid default form-submit behavior if moved inside a form; keep the existing
title if desired for tooltip support.
- Around line 4948-4972: applyUpdate currently hides the `#update-banner` and
removes sessionStorage regardless of whether the API succeeded, and only
restores the `#update-banner-btn` state in the .catch branch, which can leave the
button stuck; change applyUpdate to check the real success condition (e.g.,
inspect fetch Response.ok before calling r.json() or check data.status from the
parsed JSON) and only hide `#update-banner` and remove sessionStorage when the
response indicates success, while ensuring the button (element id
update-banner-btn) is always restored to its original innerHTML and
disabled=false in both success and error paths (use a finally-equivalent or
duplicate the restore logic), and still call showNotification with the
message/status returned by the API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0491dab3-8779-4b1a-9e5f-f8116dcba8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 9412915 and 3ad331e.

📒 Files selected for processing (3)
  • web_interface/blueprints/api_v3.py
  • web_interface/static/v3/app.css
  • web_interface/templates/v3/base.html

Comment on lines +1344 to +1345
_update_check_cache: Dict = {}
_UPDATE_CHECK_TTL = 300 # 5 minutes
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Synchronize the update cache.

This cache is shared across requests, but ts and data are read/written independently. A second request can observe ts before data is set, or hit git_pull's clear() between the freshness check and the read, which makes check_for_update() intermittently raise KeyError or serve stale state.

🔒 Suggested fix
+import threading
+
 _update_check_cache: Dict = {}
+_update_check_lock = threading.Lock()
 _UPDATE_CHECK_TTL = 300  # 5 minutes
@@
     import time as _time
     now = _time.time()
-    if _update_check_cache.get('ts', 0) + _UPDATE_CHECK_TTL > now:
-        return jsonify(_update_check_cache['data'])
+    with _update_check_lock:
+        cached = _update_check_cache.copy()
+    if cached.get('ts', 0) + _UPDATE_CHECK_TTL > now and 'data' in cached:
+        return jsonify(cached['data'])
@@
-    _update_check_cache['ts'] = now
-    _update_check_cache['data'] = data
+    with _update_check_lock:
+        _update_check_cache.clear()
+        _update_check_cache.update({'ts': now, 'data': data})
     return jsonify(data)
@@
-            _update_check_cache.clear()
+            with _update_check_lock:
+                _update_check_cache.clear()

Also applies to: 1352-1353, 1391-1392, 1504-1505

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/blueprints/api_v3.py` around lines 1344 - 1345, The shared
cache _update_check_cache is accessed concurrently causing race conditions;
protect all reads and writes (including clear() and checks using
_UPDATE_CHECK_TTL) by introducing a dedicated threading.Lock (e.g.,
_update_check_lock) and wrapping every access to _update_check_cache and its
keys in a with _update_check_lock block inside check_for_update(), git_pull(),
and any other functions that touch the cache so ts/data are updated and read
atomically.

Comment on lines +1357 to +1377
subprocess.run(
['git', 'fetch', 'origin', 'main'],
capture_output=True, text=True, timeout=15, cwd=project_dir
)
local_sha = subprocess.run(
['git', 'rev-parse', 'HEAD'],
capture_output=True, text=True, timeout=5, cwd=project_dir
).stdout.strip()
remote_sha = subprocess.run(
['git', 'rev-parse', 'origin/main'],
capture_output=True, text=True, timeout=5, cwd=project_dir
).stdout.strip()

if local_sha == remote_sha:
data = {'status': 'success', 'update_available': False,
'local_sha': local_sha[:8], 'remote_sha': remote_sha[:8]}
else:
log_result = subprocess.run(
['git', 'log', 'HEAD..origin/main', '--oneline'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Check each git command result before building a success payload.

These subprocess calls only fail via non-zero returncode; they do not raise by default. Right now a failed fetch/rev-parse/log can still fall through as "status": "success" with empty or stale SHAs, and that bad payload gets cached for 5 minutes.

🛑 Suggested fix
     project_dir = str(PROJECT_ROOT)
     try:
-        subprocess.run(
+        fetch_result = subprocess.run(
             ['git', 'fetch', 'origin', 'main'],
             capture_output=True, text=True, timeout=15, cwd=project_dir
         )
-        local_sha = subprocess.run(
+        if fetch_result.returncode != 0:
+            raise RuntimeError(fetch_result.stderr.strip() or 'git fetch origin main failed')
+
+        local_result = subprocess.run(
             ['git', 'rev-parse', 'HEAD'],
             capture_output=True, text=True, timeout=5, cwd=project_dir
-        ).stdout.strip()
-        remote_sha = subprocess.run(
+        )
+        if local_result.returncode != 0:
+            raise RuntimeError(local_result.stderr.strip() or 'git rev-parse HEAD failed')
+        local_sha = local_result.stdout.strip()
+
+        remote_result = subprocess.run(
             ['git', 'rev-parse', 'origin/main'],
             capture_output=True, text=True, timeout=5, cwd=project_dir
-        ).stdout.strip()
+        )
+        if remote_result.returncode != 0:
+            raise RuntimeError(remote_result.stderr.strip() or 'git rev-parse origin/main failed')
+        remote_sha = remote_result.stdout.strip()

As per coding guidelines, "Validate inputs and handle errors early (Fail Fast principle)".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
subprocess.run(
['git', 'fetch', 'origin', 'main'],
capture_output=True, text=True, timeout=15, cwd=project_dir
)
local_sha = subprocess.run(
['git', 'rev-parse', 'HEAD'],
capture_output=True, text=True, timeout=5, cwd=project_dir
).stdout.strip()
remote_sha = subprocess.run(
['git', 'rev-parse', 'origin/main'],
capture_output=True, text=True, timeout=5, cwd=project_dir
).stdout.strip()
if local_sha == remote_sha:
data = {'status': 'success', 'update_available': False,
'local_sha': local_sha[:8], 'remote_sha': remote_sha[:8]}
else:
log_result = subprocess.run(
['git', 'log', 'HEAD..origin/main', '--oneline'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
fetch_result = subprocess.run(
['git', 'fetch', 'origin', 'main'],
capture_output=True, text=True, timeout=15, cwd=project_dir
)
if fetch_result.returncode != 0:
raise RuntimeError(fetch_result.stderr.strip() or 'git fetch origin main failed')
local_result = subprocess.run(
['git', 'rev-parse', 'HEAD'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
if local_result.returncode != 0:
raise RuntimeError(local_result.stderr.strip() or 'git rev-parse HEAD failed')
local_sha = local_result.stdout.strip()
remote_result = subprocess.run(
['git', 'rev-parse', 'origin/main'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
if remote_result.returncode != 0:
raise RuntimeError(remote_result.stderr.strip() or 'git rev-parse origin/main failed')
remote_sha = remote_result.stdout.strip()
if local_sha == remote_sha:
data = {'status': 'success', 'update_available': False,
'local_sha': local_sha[:8], 'remote_sha': remote_sha[:8]}
else:
log_result = subprocess.run(
['git', 'log', 'HEAD..origin/main', '--oneline'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
🧰 Tools
🪛 Ruff (0.15.10)

[error] 1358-1358: Starting a process with a partial executable path

(S607)


[error] 1362-1362: Starting a process with a partial executable path

(S607)


[error] 1366-1366: Starting a process with a partial executable path

(S607)


[error] 1375-1375: Starting a process with a partial executable path

(S607)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/blueprints/api_v3.py` around lines 1357 - 1377, The git
subprocess calls in the update-check block (the fetch, the two rev-parse calls
that populate local_sha/remote_sha, and the git log that assigns log_result)
must have their return codes validated before building a success payload; update
the code around subprocess.run calls in api_v3.py to either use check=True or
inspect each CompletedProcess.returncode and on failure log/process stderr (from
.stderr) and return an error payload (status: "error" or similar) instead of
treating empty/stale SHAs as success, ensuring you don't cache a success
response when any of fetch, rev-parse, or log failed; reference variables: the
subprocess.run for fetch, local_sha assignment, remote_sha assignment, and
log_result when adding these checks.

Comment on lines +1370 to +1386
if local_sha == remote_sha:
data = {'status': 'success', 'update_available': False,
'local_sha': local_sha[:8], 'remote_sha': remote_sha[:8]}
else:
log_result = subprocess.run(
['git', 'log', 'HEAD..origin/main', '--oneline'],
capture_output=True, text=True, timeout=5, cwd=project_dir
)
lines = [l for l in log_result.stdout.strip().split('\n') if l]
data = {
'status': 'success',
'update_available': True,
'local_sha': local_sha[:8],
'remote_sha': remote_sha[:8],
'commits_behind': len(lines),
'latest_message': lines[0].split(' ', 1)[1] if lines else '',
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only mark updates available when this checkout is actually behind origin/main.

local_sha != remote_sha is also true when the repo is ahead of origin/main or has diverged from it. In those cases this endpoint still reports update_available=True, which doesn't match the feature contract and will surface a false banner.

↕️ Suggested fix
-        if local_sha == remote_sha:
+        counts_result = subprocess.run(
+            ['git', 'rev-list', '--left-right', '--count', 'HEAD...origin/main'],
+            capture_output=True, text=True, timeout=5, cwd=project_dir
+        )
+        ahead_count, behind_count = map(int, counts_result.stdout.strip().split())
+
+        if behind_count == 0:
             data = {'status': 'success', 'update_available': False,
                     'local_sha': local_sha[:8], 'remote_sha': remote_sha[:8]}
         else:
             log_result = subprocess.run(
                 ['git', 'log', 'HEAD..origin/main', '--oneline'],
                 capture_output=True, text=True, timeout=5, cwd=project_dir
             )
             lines = [l for l in log_result.stdout.strip().split('\n') if l]
             data = {
                 'status': 'success',
                 'update_available': True,
                 'local_sha': local_sha[:8],
                 'remote_sha': remote_sha[:8],
-                'commits_behind': len(lines),
+                'commits_behind': behind_count,
                 'latest_message': lines[0].split(' ', 1)[1] if lines else '',
             }
🧰 Tools
🪛 Ruff (0.15.10)

[error] 1375-1375: Starting a process with a partial executable path

(S607)


[error] 1378-1378: Ambiguous variable name: l

(E741)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/blueprints/api_v3.py` around lines 1370 - 1386, The current
logic treats any local_sha != remote_sha as an available update; change it to
detect whether origin/main is actually ahead of the checkout by running a git
compare and only set update_available=True when the remote has commits the local
lacks. For example, after obtaining local_sha and remote_sha (existing
variables), run a git rev-list --left-right --count local_sha...origin/main (or
use git rev-list local_sha..origin/main) in project_dir and parse the counts to
determine commits_behind (remote-only count); set update_available = True only
when commits_behind > 0 and populate latest_message/commits_behind from that
result (use the existing log_result parsing for commit messages when
commits_behind > 0).

Comment on lines +951 to +955
<button onclick="dismissUpdateBanner()"
class="update-banner-dismiss rounded p-1 transition-colors duration-150"
title="Dismiss">
<i class="fas fa-times text-sm"></i>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an explicit accessible label to the icon-only dismiss button.

The close control is icon-only and currently relies on title. Please add aria-label (and preferably type="button" for safety if this ever moves inside a form).

♿ Suggested patch
-                    <button onclick="dismissUpdateBanner()"
+                    <button type="button"
+                            onclick="dismissUpdateBanner()"
                             class="update-banner-dismiss rounded p-1 transition-colors duration-150"
-                            title="Dismiss">
+                            title="Dismiss"
+                            aria-label="Dismiss update banner">
                         <i class="fas fa-times text-sm"></i>
                     </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/templates/v3/base.html` around lines 951 - 955, The dismiss
button markup that invokes dismissUpdateBanner() and uses class
"update-banner-dismiss" is icon-only and needs explicit accessibility
attributes: add aria-label="Dismiss update" (or similar descriptive text) to the
<button> and also include type="button" to avoid default form-submit behavior if
moved inside a form; keep the existing title if desired for tooltip support.

Comment on lines +4948 to +4972
window.applyUpdate = function() {
var btn = document.getElementById('update-banner-btn');
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Updating...';
btn.disabled = true;
fetch('/api/v3/system/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'git_pull' })
})
.then(function(r) { return r.json(); })
.then(function(data) {
document.getElementById('update-banner').style.display = 'none';
if (typeof showNotification === 'function') {
showNotification(data.message || 'Update complete', data.status || 'success');
}
try { sessionStorage.removeItem('update-dismissed'); } catch(e) {}
})
.catch(function() {
btn.innerHTML = '<i class="fas fa-download mr-1"></i> Update Now';
btn.disabled = false;
if (typeof showNotification === 'function') {
showNotification('Update failed — check your connection', 'error');
}
});
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only hide the banner on successful update, and always restore button state.

applyUpdate() currently hides the banner for any JSON response and only re-enables the button in .catch(). If the API returns an error payload (or the banner reappears later), the button can stay stuck in “Updating…”.

🐛 Suggested patch
     window.applyUpdate = function() {
         var btn = document.getElementById('update-banner-btn');
         btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Updating...';
         btn.disabled = true;
         fetch('/api/v3/system/action', {
             method: 'POST',
             headers: { 'Content-Type': 'application/json' },
             body: JSON.stringify({ action: 'git_pull' })
         })
-        .then(function(r) { return r.json(); })
-        .then(function(data) {
-            document.getElementById('update-banner').style.display = 'none';
-            if (typeof showNotification === 'function') {
-                showNotification(data.message || 'Update complete', data.status || 'success');
-            }
-            try { sessionStorage.removeItem('update-dismissed'); } catch(e) {}
+        .then(function(r) {
+            return r.json().then(function(data) {
+                return { ok: r.ok, data: data };
+            });
+        })
+        .then(function(res) {
+            var data = res.data || {};
+            var isSuccess = res.ok && data.status === 'success';
+            if (isSuccess) {
+                document.getElementById('update-banner').style.display = 'none';
+                try { sessionStorage.removeItem('update-dismissed'); } catch(e) {}
+            }
+            if (typeof showNotification === 'function') {
+                showNotification(
+                    data.message || (isSuccess ? 'Update complete' : 'Update failed'),
+                    isSuccess ? 'success' : 'error'
+                );
+            }
         })
         .catch(function() {
-            btn.innerHTML = '<i class="fas fa-download mr-1"></i> Update Now';
-            btn.disabled = false;
             if (typeof showNotification === 'function') {
                 showNotification('Update failed — check your connection', 'error');
             }
+        })
+        .finally(function() {
+            btn.innerHTML = '<i class="fas fa-download mr-1"></i> Update Now';
+            btn.disabled = false;
         });
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
window.applyUpdate = function() {
var btn = document.getElementById('update-banner-btn');
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Updating...';
btn.disabled = true;
fetch('/api/v3/system/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'git_pull' })
})
.then(function(r) { return r.json(); })
.then(function(data) {
document.getElementById('update-banner').style.display = 'none';
if (typeof showNotification === 'function') {
showNotification(data.message || 'Update complete', data.status || 'success');
}
try { sessionStorage.removeItem('update-dismissed'); } catch(e) {}
})
.catch(function() {
btn.innerHTML = '<i class="fas fa-download mr-1"></i> Update Now';
btn.disabled = false;
if (typeof showNotification === 'function') {
showNotification('Update failed — check your connection', 'error');
}
});
};
window.applyUpdate = function() {
var btn = document.getElementById('update-banner-btn');
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Updating...';
btn.disabled = true;
fetch('/api/v3/system/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'git_pull' })
})
.then(function(r) {
return r.json().then(function(data) {
return { ok: r.ok, data: data };
});
})
.then(function(res) {
var data = res.data || {};
var isSuccess = res.ok && data.status === 'success';
if (isSuccess) {
document.getElementById('update-banner').style.display = 'none';
try { sessionStorage.removeItem('update-dismissed'); } catch(e) {}
}
if (typeof showNotification === 'function') {
showNotification(
data.message || (isSuccess ? 'Update complete' : 'Update failed'),
isSuccess ? 'success' : 'error'
);
}
})
.catch(function() {
if (typeof showNotification === 'function') {
showNotification('Update failed — check your connection', 'error');
}
})
.finally(function() {
btn.innerHTML = '<i class="fas fa-download mr-1"></i> Update Now';
btn.disabled = false;
});
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web_interface/templates/v3/base.html` around lines 4948 - 4972, applyUpdate
currently hides the `#update-banner` and removes sessionStorage regardless of
whether the API succeeded, and only restores the `#update-banner-btn` state in the
.catch branch, which can leave the button stuck; change applyUpdate to check the
real success condition (e.g., inspect fetch Response.ok before calling r.json()
or check data.status from the parsed JSON) and only hide `#update-banner` and
remove sessionStorage when the response indicates success, while ensuring the
button (element id update-banner-btn) is always restored to its original
innerHTML and disabled=false in both success and error paths (use a
finally-equivalent or duplicate the restore logic), and still call
showNotification with the message/status returned by the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant