fix(web): repair array-table.js syntax error and version static assets (#357)

Two issues left the v3 web UI's Overview (and other Alpine-driven tabs)
blank:

1. array-table.js had two safeSetHTML(target, `...`) calls that closed the
   template-literal argument with `; instead of `); — a SyntaxError that
   aborts the script and halts widget registration / Alpine initialization.

2. Static assets are served `Cache-Control: public, max-age=31536000,
   immutable` but were referenced without a cache-busting version (the header
   comment assumed "versioning via query params", which was only ever applied
   by hand to app.css). So edited JS/CSS never reached browsers — including
   fix #1.

Add a Flask url_defaults hook that appends each static file's mtime as a ?v=
param to every url_for('static', ...), so changed files get a new URL and are
refetched while unchanged files keep the long immutable cache. Drop the now
redundant manual ?v= on app.css.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ron Pierce
2026-06-01 08:00:40 -07:00
committed by GitHub
parent 4961697251
commit ac3a15bfaa
3 changed files with 19 additions and 3 deletions

View File

@@ -391,6 +391,22 @@ def captive_portal_redirect():
# Redirect to lightweight captive portal setup page (not the full UI) # Redirect to lightweight captive portal setup page (not the full UI)
return redirect(url_for('pages_v3.captive_setup'), code=302) return redirect(url_for('pages_v3.captive_setup'), code=302)
# Append a content-version query param (file mtime) to every static URL so the
# long-lived `immutable` cache (see add_security_headers below) is actually safe:
# when a static file changes its URL changes, so browsers refetch it. Without
# this, edited JS/CSS were served immutable under an unchanging URL and never
# reached clients until a manual cache clear.
@app.url_defaults
def add_static_version(endpoint, values):
if endpoint == 'static' and values.get('filename'):
try:
file_path = os.path.join(app.static_folder, values['filename'])
values['v'] = int(os.path.getmtime(file_path))
except OSError:
# File missing (e.g. plugin asset not yet installed) — skip versioning.
pass
# Add security headers and caching to all responses # Add security headers and caching to all responses
@app.after_request @app.after_request
def add_security_headers(response): def add_security_headers(response):

View File

@@ -440,7 +440,7 @@
<h3 class="text-base font-semibold text-gray-900">Advanced Properties</h3> <h3 class="text-base font-semibold text-gray-900">Advanced Properties</h3>
<button type="button" onclick="window.closeArrayTableRowEditor()" <button type="button" onclick="window.closeArrayTableRowEditor()"
class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button> class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>`; </div>`);
const body = document.createElement('div'); const body = document.createElement('div');
body.className = 'px-5 py-4 space-y-4'; body.className = 'px-5 py-4 space-y-4';
@@ -512,7 +512,7 @@
<button type="button" onclick="window.closeArrayTableRowEditor()" <button type="button" onclick="window.closeArrayTableRowEditor()"
class="px-4 py-2 text-sm text-gray-700 border border-gray-300 rounded-md hover:bg-gray-100">Cancel</button> class="px-4 py-2 text-sm text-gray-700 border border-gray-300 rounded-md hover:bg-gray-100">Cancel</button>
<button type="button" id="array-row-editor-save" <button type="button" id="array-row-editor-save"
class="px-4 py-2 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md">Save</button>`; class="px-4 py-2 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md">Save</button>`);
// Save handler // Save handler
footer.querySelector('#array-row-editor-save').onclick = function() { footer.querySelector('#array-row-editor-save').onclick = function() {

View File

@@ -867,7 +867,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom v3 styles --> <!-- Custom v3 styles -->
<link rel="stylesheet" href="{{ url_for('static', filename='v3/app.css') }}?v=20260216b"> <link rel="stylesheet" href="{{ url_for('static', filename='v3/app.css') }}">
</head> </head>
<body x-data="app()" class="bg-gray-50 min-h-screen"> <body x-data="app()" class="bg-gray-50 min-h-screen">
<!-- Header --> <!-- Header -->