mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-11-03 17:22:20 +01:00
An instance published behind an access list or Cloudflare Zero Trust gets
an HTML login or error page in place of JSON once that session expires.
`resp.json()` then throws "Unexpected token '<', "<!DOCTYPE "...", and the
repeater panes printed that at the user. Diagnosed on the production
server 2026-09-08: the origin had answered a clean 504 JSON ("No reply
from repeater within 10s") and nginx passed it through untouched, so the
HTML could only have come from in front of it.
New window.fetchJson() (fetch-utils.js, loaded from _head_i18n.html so all
eight entry points get it) returns the parsed body — `data.success` and
`data.error` checks are unchanged — and reports anything else in the same
shape the API uses: {success:false, error, http_status, non_json|offline}.
A 401/403 with a non-JSON body is named as an expired login, since that is
the case the user can act on; an error the API sent as JSON keeps its own
message. 88 call sites converted; those that deliberately inspect the
response (resp.ok/status, blob, uploads) still use fetch directly.
Also stops three catch blocks in the repeater panes from rendering the raw
exception text.
Verified in a browser: normal calls unchanged, an HTML answer comes back
as a readable sentence, a refused connection as a network error, and all
seven pages load with no console errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
27 lines
1.4 KiB
HTML
27 lines
1.4 KiB
HTML
{# Translation catalog + runtime.
|
|
|
|
Both must block: window.t has to exist before any inline handler or page script runs,
|
|
so never add defer/async. Include this in every <head>, immediately after the theme
|
|
pre-apply IIFE, giving a uniform ordering: theme -> i18n -> everything else.
|
|
|
|
The catalog URL is content-hashed and served immutable, so all 8 entry points
|
|
(including the 6 iframes) share a single cached fetch. #}
|
|
<script src="{{ i18n_catalog_url }}"></script>
|
|
<script src="{{ url_for('static', filename='js/i18n-runtime.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/datetime-utils.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/clipboard-utils.js') }}"></script>
|
|
{# fetchJson() needs window.t, which the two scripts above have already set up. #}
|
|
<script src="{{ url_for('static', filename='js/fetch-utils.js') }}"></script>
|
|
|
|
{# Demo mode. Injected here rather than through MC_CONFIG because only index.html
|
|
and dm.html define that object, and the console — the page most in need of
|
|
locking — is not one of them. Must be set before demo-lock.js runs. #}
|
|
<script>
|
|
window.MC_DEMO = {
|
|
enabled: {{ 'true' if demo_mode else 'false' }},
|
|
unlocked: {{ 'true' if demo_unlocked else 'false' }},
|
|
hasUnlockCode: {{ 'true' if demo_has_unlock_code else 'false' }}
|
|
};
|
|
</script>
|
|
<script src="{{ url_for('static', filename='js/demo-lock.js') }}"></script>
|