From f435c8aeaf6674fb14da6c007537a6a0b921628f Mon Sep 17 00:00:00 2001
From: Alwin
`): + +```js +function showToast(msg, type) { + type = type || 'is-success'; + var t = document.createElement('div'); + t.className = 'morz-toast ' + type; + t.innerHTML = msg + ''; + document.body.appendChild(t); + requestAnimationFrame(function() { t.classList.add('show'); }); + setTimeout(function() { t.classList.remove('show'); setTimeout(function() { t.remove(); }, 300); }, 3500); +} +``` + +Shared status-polling helper: + +```js +function pollScreenStatus(slugToId) { + // slugToId: object mapping screen_id -> DOM element id prefix (e.g. 'status-') + function update() { + fetch('/api/v1/screens/status') + .then(function(r) { return r.ok ? r.json() : null; }) + .then(function(data) { + if (!data || !data.screens) return; + data.screens.forEach(function(s) { + var el = document.getElementById('status-' + s.screen_id); + if (!el) return; + var state = s.derived_state || 'unknown'; + el.className = 'status-dot ' + (state === 'online' ? 'online' : state === 'degraded' ? 'stale' : 'offline'); + el.title = state; + }); + }).catch(function(){}); + } + update(); + setInterval(update, 30000); +} +``` + +Shared CSRF helper: + +```js +function getCsrfToken() { + var m = document.cookie.match('(?:^|; )morz_csrf=([^;]*)'); + return m ? decodeURIComponent(m[1]) : ''; +} +function injectCSRF() { + var token = getCsrfToken(); + if (!token) return; + document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) { + if (!f.querySelector('input[name="csrf_token"]')) { + var inp = document.createElement('input'); + inp.type = 'hidden'; inp.name = 'csrf_token'; inp.value = token; + f.appendChild(inp); + } + }); +} +if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', injectCSRF); +else injectCSRF(); +``` + +--- + +## File Map + +| File | What changes | +|---|---| +| `server/backend/internal/httpapi/manage/templates.go` | Rewrite all 5 template consts: `loginTmpl`, `provisionTmpl`, `adminTmpl`, `manageTmpl`, `screenOverviewTmpl` | +| `server/backend/internal/httpapi/tenant/templates.go` | Rewrite `tenantDashTmpl` | +| `server/backend/internal/httpapi/manage/ui.go` | `HandleUpdateItemUI`: return 204 when `X-Requested-With: fetch` header present | + +--- + +## Task 1: HandleUpdateItemUI — return 204 for fetch callers + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/ui.go:781-788` +- Test: `server/backend/internal/httpapi/router_test.go` (add test) + +The handler currently always redirects. Inline editing JS will call it via `fetch`, which needs a 204 back (not a redirect). + +- [ ] **Step 1: Find the redirect line** + +In `ui.go`, the last line of `HandleUpdateItemUI` is: +```go +http.Redirect(w, r, "/manage/"+screenSlug+"?msg=saved", http.StatusSeeOther) +``` + +- [ ] **Step 2: Replace with conditional response** + +Replace that redirect with: +```go +if r.Header.Get("X-Requested-With") == "fetch" { + w.WriteHeader(http.StatusNoContent) + return +} +http.Redirect(w, r, "/manage/"+screenSlug+"?msg=saved", http.StatusSeeOther) +``` + +- [ ] **Step 3: Build to verify** + +```bash +cd server/backend && go build ./... +``` +Expected: no errors. + +- [ ] **Step 4: Commit** + +```bash +git add server/backend/internal/httpapi/manage/ui.go +git commit -m "fix(manage): HandleUpdateItemUI returns 204 for fetch callers" +``` + +--- + +## Task 2: Login page (`loginTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `loginTmpl` const, lines 3–99) + +- [ ] **Step 1: Replace `loginTmpl`** + +Replace the entire `loginTmpl` const with: + +```go +const loginTmpl = ` + +
+ + +
+ + + +
+