Dev build 2026-06-19 13:05
This commit is contained in:
parent
99f0e100df
commit
335bfd9591
@ -10,11 +10,11 @@ window.__authReady = (async function authGate() {
|
||||
const r = await fetch('/api/auth/me', { credentials: 'same-origin' });
|
||||
if (r.status === 401) {
|
||||
const setup = await fetch('/api/auth/setup-required').then(function (x) { return x.json(); }).catch(function () { return { setup_required: false }; });
|
||||
window.location.replace(setup.setup_required ? '/setup.html' : '/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + (setup.setup_required ? '/setup.html' : '/login.html'));
|
||||
return false;
|
||||
}
|
||||
if (!r.ok) {
|
||||
window.location.replace('/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/login.html');
|
||||
return false;
|
||||
}
|
||||
const me = await r.json();
|
||||
@ -27,7 +27,7 @@ window.__authReady = (async function authGate() {
|
||||
delete document.documentElement.dataset.authPending;
|
||||
return true;
|
||||
} catch (e) {
|
||||
window.location.replace('/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/login.html');
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
@ -53,7 +53,7 @@ function renderUserBadge(me) {
|
||||
btn.textContent = 'Sign out';
|
||||
btn.addEventListener('click', async function () {
|
||||
await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
|
||||
window.location.replace('/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/login.html');
|
||||
});
|
||||
wrap.append(btn);
|
||||
slot.append(wrap);
|
||||
@ -176,7 +176,7 @@ function renderUserBadge(me) {
|
||||
async function requestJson(url, options) {
|
||||
const response = await fetch(url, Object.assign({ credentials: 'same-origin' }, options || {}));
|
||||
if (response.status === 401) {
|
||||
window.location.replace('/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/login.html');
|
||||
throw new Error('unauthenticated');
|
||||
}
|
||||
if (!response.ok) {
|
||||
@ -1335,7 +1335,9 @@ function renderUserBadge(me) {
|
||||
if (siteFilter) {
|
||||
url += '?site_url=' + encodeURIComponent(siteFilter);
|
||||
}
|
||||
window.location.href = url;
|
||||
// Browser navigation (file download), not fetch — the fetch shim doesn't
|
||||
// apply here, so prefix the path explicitly when behind the proxy.
|
||||
window.location.href = (window.__BASE_PATH__ || '') + url;
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Clearview — Sign in</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body class="auth-page">
|
||||
<main class="auth-card">
|
||||
@ -17,12 +17,12 @@
|
||||
<p id="loginError" class="auth-error" hidden></p>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/auth.js"></script>
|
||||
<script src="auth.js"></script>
|
||||
<script>
|
||||
(async function () {
|
||||
const setup = await ClearviewAuth.getJson('/api/auth/setup-required');
|
||||
if (setup.ok && setup.data && setup.data.setup_required) {
|
||||
window.location.replace('/setup.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/setup.html');
|
||||
return;
|
||||
}
|
||||
const form = document.getElementById('loginForm');
|
||||
@ -37,7 +37,7 @@
|
||||
remember: fd.get('remember') === 'on',
|
||||
});
|
||||
if (res.ok) {
|
||||
window.location.replace('/');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/');
|
||||
} else {
|
||||
err.textContent = (res.data && res.data.detail) || 'Sign-in failed';
|
||||
err.hidden = false;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Clearview — First-time setup</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body class="auth-page">
|
||||
<main class="auth-card">
|
||||
@ -16,12 +16,12 @@
|
||||
<p id="setupError" class="auth-error" hidden></p>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/auth.js"></script>
|
||||
<script src="auth.js"></script>
|
||||
<script>
|
||||
(async function () {
|
||||
const probe = await ClearviewAuth.getJson('/api/auth/setup-required');
|
||||
if (!probe.ok || !probe.data || !probe.data.setup_required) {
|
||||
window.location.replace('/login.html');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/login.html');
|
||||
return;
|
||||
}
|
||||
const form = document.getElementById('setupForm');
|
||||
@ -35,7 +35,7 @@
|
||||
password: fd.get('password'),
|
||||
});
|
||||
if (res.ok) {
|
||||
window.location.replace('/');
|
||||
window.location.replace((window.__BASE_PATH__ || '') + '/');
|
||||
} else {
|
||||
err.textContent = (res.data && res.data.detail) || 'Setup failed';
|
||||
err.hidden = false;
|
||||
|
||||
@ -8,8 +8,8 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi import Depends, FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .api_jobs import router as jobs_router
|
||||
@ -67,9 +67,51 @@ app.include_router(onboarding_router, dependencies=_protected)
|
||||
# Static files (mounted last so explicit API routes take precedence)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Path-prefix support: when running behind a path-stripping proxy (landing on
|
||||
# :8210), an X-Forwarded-Prefix header tells the page where it is mounted. We
|
||||
# inject window.__BASE_PATH__ and a small fetch shim that prefixes same-origin
|
||||
# absolute API calls (fetch('/api/...')); navigation/asset URLs are made
|
||||
# prefix-relative in the site files. No header → empty prefix → no-op, so direct
|
||||
# access behaves exactly as before.
|
||||
_FETCH_SHIM = (
|
||||
"(function(){var b=window.__BASE_PATH__;if(!b)return;"
|
||||
"var f=window.fetch.bind(window);"
|
||||
"window.fetch=function(i,o){"
|
||||
'if(typeof i==="string"&&i.charAt(0)==="/")i=b+i;return f(i,o);};})();'
|
||||
)
|
||||
|
||||
|
||||
def _render_page(filename: str, prefix: str) -> HTMLResponse:
|
||||
raw = (SITE_DIR / filename).read_text(encoding="utf-8")
|
||||
head = (
|
||||
"<head>\n<script>\n"
|
||||
f'window.__BASE_PATH__ = "{prefix}";\n'
|
||||
f"{_FETCH_SHIM}\n"
|
||||
"</script>"
|
||||
)
|
||||
html = raw.replace("<head>", head, 1)
|
||||
# no-store on the HTML shell so a browser never serves a stale page after a
|
||||
# redeploy or a move behind/along the proxy.
|
||||
return HTMLResponse(html, headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
def _prefix(request: Request) -> str:
|
||||
return request.headers.get("x-forwarded-prefix", "").rstrip("/")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index() -> FileResponse:
|
||||
return FileResponse(SITE_DIR / "index.html")
|
||||
def index(request: Request) -> HTMLResponse:
|
||||
return _render_page("index.html", _prefix(request))
|
||||
|
||||
|
||||
@app.get("/login.html")
|
||||
def login_page(request: Request) -> HTMLResponse:
|
||||
return _render_page("login.html", _prefix(request))
|
||||
|
||||
|
||||
@app.get("/setup.html")
|
||||
def setup_page(request: Request) -> HTMLResponse:
|
||||
return _render_page("setup.html", _prefix(request))
|
||||
|
||||
|
||||
app.mount("/", StaticFiles(directory=SITE_DIR, html=True), name="site")
|
||||
|
||||
@ -7,7 +7,7 @@ history, so operators can see exactly which image build is running.
|
||||
from __future__ import annotations
|
||||
|
||||
VERSION = "v0.2.0"
|
||||
BUILD = 0
|
||||
BUILD = 1
|
||||
|
||||
|
||||
def display_version() -> str:
|
||||
|
||||
@ -2,6 +2,20 @@
|
||||
|
||||
This file documents changes on the develop branch of this project.
|
||||
|
||||
## 2026-06-19 — Path-prefix routing support (run behind the landing proxy)
|
||||
|
||||
### Added
|
||||
- Clearview can now be served under a path prefix (e.g. `http://10.19.3.32:8210/clearview/`) by the landing reverse proxy, which strips the prefix and passes it in `X-Forwarded-Prefix` — part of the shared path-prefix routing effort. With no header the app behaves exactly as on direct access.
|
||||
- `main.py` now serves the three HTML pages (`index.html`, `login.html`, `setup.html`) through `_render_page`, which injects `<script>window.__BASE_PATH__ = "{prefix}"</script>` plus a small **fetch shim** that prefixes same-origin absolute API calls (`fetch('/api/...')`). This is a vanilla-JS app (no build step) with ~35 absolute `/api` calls across `app.js`/`auth.js`/inline scripts, so shimming `fetch` in one place is far less invasive than rewriting each call. `index.html`/`login.html`/`setup.html` got explicit routes (registered before the `StaticFiles` mount) and the pages are now sent with `Cache-Control: no-store` so a stale shell is never served after a redeploy.
|
||||
|
||||
### Changed
|
||||
- Made the few **non-fetch** URLs prefix-aware in the site files, since the fetch shim only covers `fetch()`:
|
||||
- `login.html`/`setup.html`: stylesheet/`auth.js` references made relative (`/styles.css` → `styles.css`, `/auth.js` → `auth.js`) so they resolve under the prefix via the document URL.
|
||||
- Page navigations (`window.location.replace('/login.html' | '/setup.html' | '/')` in `app.js`, `login.html`, `setup.html`) now prepend `window.__BASE_PATH__`.
|
||||
- The scan-job export download (`window.location.href = '/api/scan-jobs/.../export'`, a browser navigation, not `fetch`) prepends `window.__BASE_PATH__`.
|
||||
- `index.html` needed no changes — its asset refs were already relative and it uses hash routing. `auth.js` needed none — its `fetch()` calls go through the shim.
|
||||
- Verified: `main.py` compiles, the injection produces a single `window.__BASE_PATH__` assignment + shim per page (empty-prefix variant is a no-op), and no breaking absolute asset/nav refs remain in the site.
|
||||
|
||||
## 2026-05-28 — Released as v0.2.0
|
||||
|
||||
## 2026-05-28 — Release: drop unused `version.txt`
|
||||
|
||||
Reference in New Issue
Block a user