From ecaa113c6806bb1de3e072a4adda599b21fff2b4 Mon Sep 17 00:00:00 2001
From: ZdenekSrotyr
{% if is_override %} -
- Overridden by {{ updated_by }} on - {{ updated_at.strftime("%Y-%m-%d %H:%M UTC") if updated_at else "—" }}. -
+ Overridden by {{ updated_by }} on + {{ updated_at.strftime("%Y-%m-%d %H:%M UTC") if updated_at else "—" }}. {% else %} -Using shipped default.
+ Using shipped default. {% endif %} +
@@ -50,40 +50,75 @@
const $ = (id) => document.getElementById(id);
const result = $("result");
+ async function refreshStatus() {
+ const r = await fetch("/api/admin/welcome-template", {credentials: "include"});
+ if (!r.ok) return;
+ const data = await r.json();
+ const status = $("status-line");
+ if (data.content !== null) {
+ const when = data.updated_at
+ ? new Date(data.updated_at).toISOString().slice(0, 16).replace("T", " ") + " UTC"
+ : "—";
+ status.innerHTML = "Overridden by " + (data.updated_by || "—") + " on " + when + ".";
+ $("content").value = data.content;
+ } else {
+ status.textContent = "Using shipped default.";
+ $("content").value = data.default;
+ }
+ }
+
$("save-btn").addEventListener("click", async () => {
result.textContent = "Saving…";
const r = await fetch("/api/admin/welcome-template", {
method: "PUT",
+ credentials: "include",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({content: $("content").value}),
});
if (r.ok) {
result.textContent = "Saved.";
+ await refreshStatus();
} else {
- const err = await r.json();
- result.textContent = "Error: " + (err.detail || r.statusText);
+ let detail = r.statusText;
+ try { detail = (await r.json()).detail || detail; } catch {}
+ result.textContent = "Error: " + detail;
}
});
$("reset-btn").addEventListener("click", async () => {
if (!confirm("Reset to OSS default? Your override will be lost.")) return;
- const r = await fetch("/api/admin/welcome-template", {method: "DELETE"});
+ const r = await fetch("/api/admin/welcome-template", {
+ method: "DELETE",
+ credentials: "include",
+ });
if (r.ok) {
- result.textContent = "Reset. Reload to see the default.";
+ result.textContent = "Reset to default.";
+ await refreshStatus();
} else {
- result.textContent = "Error: " + r.statusText;
+ let detail = r.statusText;
+ try { detail = (await r.json()).detail || detail; } catch {}
+ result.textContent = "Error: " + detail;
}
});
$("preview-btn").addEventListener("click", async () => {
- const r = await fetch("/api/welcome?server_url=" + encodeURIComponent(window.location.origin));
+ result.textContent = "Rendering preview…";
+ const r = await fetch("/api/admin/welcome-template/preview", {
+ method: "POST",
+ credentials: "include",
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({content: $("content").value}),
+ });
if (r.ok) {
const j = await r.json();
$("preview").textContent = j.content;
$("preview").hidden = false;
+ result.textContent = "Preview rendered.";
} else {
- const err = await r.json();
- result.textContent = "Render error: " + (err.detail || r.statusText);
+ let detail = r.statusText;
+ try { detail = (await r.json()).detail || detail; } catch {}
+ result.textContent = "Render error: " + detail;
+ $("preview").hidden = true;
}
});
diff --git a/tests/test_welcome_template_api.py b/tests/test_welcome_template_api.py
index 86538b8..a71aadb 100644
--- a/tests/test_welcome_template_api.py
+++ b/tests/test_welcome_template_api.py
@@ -119,3 +119,39 @@ def test_get_welcome_500_includes_reset_hint_on_render_failure(seeded_app, monke
)
assert r.status_code == 500
assert "/admin/welcome" in r.json()["detail"]
+
+
+def test_admin_preview_renders_arbitrary_content(seeded_app):
+ """Preview endpoint must render the supplied content (not whatever's
+ stored), so the admin UI can show pre-save preview."""
+ c = seeded_app["client"]
+ admin = _auth(seeded_app["admin_token"])
+ r = c.post(
+ "/api/admin/welcome-template/preview",
+ json={"content": "# Preview {{ user.email }}"},
+ headers=admin,
+ )
+ assert r.status_code == 200
+ assert r.json()["content"].startswith("# Preview admin@test.com")
+
+
+def test_preview_rejects_invalid_template(seeded_app):
+ c = seeded_app["client"]
+ admin = _auth(seeded_app["admin_token"])
+ r = c.post(
+ "/api/admin/welcome-template/preview",
+ json={"content": "{% for x in y %}"},
+ headers=admin,
+ )
+ assert r.status_code == 400
+
+
+def test_preview_requires_admin(seeded_app):
+ c = seeded_app["client"]
+ analyst = _auth(seeded_app["analyst_token"])
+ r = c.post(
+ "/api/admin/welcome-template/preview",
+ json={"content": "# x"},
+ headers=analyst,
+ )
+ assert r.status_code == 403