Harden lab status refresh
This commit is contained in:
parent
381ecb4ea0
commit
8703e6933a
@ -14,6 +14,15 @@ from .. import settings
|
|||||||
_LAB_STATUS_CACHE: dict[str, Any] = {"ts": 0.0, "value": None}
|
_LAB_STATUS_CACHE: dict[str, Any] = {"ts": 0.0, "value": None}
|
||||||
|
|
||||||
|
|
||||||
|
def _status_response(payload: dict[str, Any]) -> Any:
|
||||||
|
"""Return lab status JSON with browser and proxy caching disabled."""
|
||||||
|
|
||||||
|
response = jsonify(payload)
|
||||||
|
response.headers["Cache-Control"] = "no-store, max-age=0"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _vm_query(expr: str) -> float | None:
|
def _vm_query(expr: str) -> float | None:
|
||||||
"""Run one instant VictoriaMetrics query and return the largest value."""
|
"""Run one instant VictoriaMetrics query and return the largest value."""
|
||||||
|
|
||||||
@ -171,7 +180,7 @@ def register(app) -> None:
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
cached = _LAB_STATUS_CACHE.get("value")
|
cached = _LAB_STATUS_CACHE.get("value")
|
||||||
if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC):
|
if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC):
|
||||||
return jsonify(cached)
|
return _status_response(cached)
|
||||||
|
|
||||||
t_total = time.perf_counter()
|
t_total = time.perf_counter()
|
||||||
timings_ms: dict[str, int] = {}
|
timings_ms: dict[str, int] = {}
|
||||||
@ -242,4 +251,4 @@ def register(app) -> None:
|
|||||||
|
|
||||||
_LAB_STATUS_CACHE["ts"] = now
|
_LAB_STATUS_CACHE["ts"] = now
|
||||||
_LAB_STATUS_CACHE["value"] = payload
|
_LAB_STATUS_CACHE["value"] = payload
|
||||||
return jsonify(payload)
|
return _status_response(payload)
|
||||||
|
|||||||
@ -129,6 +129,8 @@ def test_lab_status_uses_cache_and_probe_fallbacks(monkeypatch) -> None:
|
|||||||
payload = response.get_json()
|
payload = response.get_json()
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Cache-Control"] == "no-store, max-age=0"
|
||||||
|
assert response.headers["Pragma"] == "no-cache"
|
||||||
assert payload["connected"] is True
|
assert payload["connected"] is True
|
||||||
assert payload["atlas"]["source"] == "grafana"
|
assert payload["atlas"]["source"] == "grafana"
|
||||||
assert payload["dedicated_hosts"]["up"] is True
|
assert payload["dedicated_hosts"]["up"] is True
|
||||||
@ -144,6 +146,7 @@ def test_lab_status_uses_cache_and_probe_fallbacks(monkeypatch) -> None:
|
|||||||
|
|
||||||
second = client.get("/api/lab/status")
|
second = client.get("/api/lab/status")
|
||||||
assert second.get_json() == payload
|
assert second.get_json() == payload
|
||||||
|
assert second.headers["Cache-Control"] == "no-store, max-age=0"
|
||||||
|
|
||||||
lab._LAB_STATUS_CACHE["ts"] = 0.0
|
lab._LAB_STATUS_CACHE["ts"] = 0.0
|
||||||
lab._LAB_STATUS_CACHE["value"] = None
|
lab._LAB_STATUS_CACHE["value"] = None
|
||||||
|
|||||||
@ -37,7 +37,8 @@ async function refreshLabStatus() {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = window.setTimeout(() => controller.abort(), 10000);
|
const timeoutId = window.setTimeout(() => controller.abort(), 10000);
|
||||||
try {
|
try {
|
||||||
const resp = await fetch("/api/lab/status", {
|
const resp = await fetch(`/api/lab/status?ts=${Date.now()}`, {
|
||||||
|
cache: "no-store",
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
@ -45,11 +46,13 @@ async function refreshLabStatus() {
|
|||||||
labStatus.value = await resp.json();
|
labStatus.value = await resp.json();
|
||||||
statusError.value = "";
|
statusError.value = "";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
labStatus.value = null;
|
if (!labStatus.value) {
|
||||||
if (err?.name === "AbortError") {
|
labStatus.value = null;
|
||||||
statusError.value = "Live data timed out";
|
if (err?.name === "AbortError") {
|
||||||
} else {
|
statusError.value = "Live data timed out";
|
||||||
statusError.value = "Live data unavailable";
|
} else {
|
||||||
|
statusError.value = "Live data unavailable";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
@ -69,7 +72,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
function scheduleNextPoll() {
|
function scheduleNextPoll() {
|
||||||
if (pollTimerId) window.clearTimeout(pollTimerId);
|
if (pollTimerId) window.clearTimeout(pollTimerId);
|
||||||
const delayMs = labStatus.value ? 30000 : 8000;
|
const delayMs = labStatus.value ? 30000 : 4000;
|
||||||
pollTimerId = window.setTimeout(refreshLabStatus, delayMs);
|
pollTimerId = window.setTimeout(refreshLabStatus, delayMs);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ test.beforeEach(async ({ page }) => {
|
|||||||
body: JSON.stringify({ enabled: true, reset_url: "https://sso.example.dev/reset" }),
|
body: JSON.stringify({ enabled: true, reset_url: "https://sso.example.dev/reset" }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await page.route("**/api/lab/status", async (route) => {
|
await page.route("**/api/lab/status*", async (route) => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
@ -106,7 +106,7 @@ test("keeps the homepage usable when public status fails", async ({ page }) => {
|
|||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
window.__LAB_STATUS_FAIL__ = true;
|
window.__LAB_STATUS_FAIL__ = true;
|
||||||
});
|
});
|
||||||
await page.route("**/api/lab/status", async (route) => {
|
await page.route("**/api/lab/status*", async (route) => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 503,
|
status: 503,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
|
|||||||
@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => {
|
|||||||
body: JSON.stringify({ enabled: false }),
|
body: JSON.stringify({ enabled: false }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await page.route("**/api/lab/status", async (route) => {
|
await page.route("**/api/lab/status*", async (route) => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
|
|||||||
@ -215,6 +215,13 @@ describe("static shell views and components", () => {
|
|||||||
});
|
});
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
expect(app.text()).toContain("true");
|
expect(app.text()).toContain("true");
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/^\/api\/lab\/status\?ts=\d+$/),
|
||||||
|
expect.objectContaining({
|
||||||
|
cache: "no-store",
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await app.unmount();
|
await app.unmount();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user