diff --git a/lib/routes.js b/lib/routes.js index 8322809..6372370 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -71,54 +71,60 @@ const csrfField = (req) => { }; -const layout = (title, body, flash) => ` - - -${escape(title)} - - - +${navPills(req, [ + ["/admin/dev-deploy/", "Dashboard"], + ["/admin/dev-deploy/ops", "Journal"], + ["/admin/dev-deploy/peers", "Peers"], + ["/admin/dev-deploy/plan", "Plan"], + ["/admin/dev-deploy/tables", "Tables"], + ["/admin/dev-deploy/conflicts", "Conflicts"], +])} ${flash || ""} ${body} -`; +

${escape(PLUGIN_NAME)} v${escape(PLUGIN_VERSION)}

`; const flashMsg = (req) => { const m = req.query.msg; const e = req.query.err; - if (m) return `
${escape(m)}
`; - if (e) return `
${escape(e)}
`; + if (m) return `
${escape(m)}
`; + if (e) return `
${escape(e)}
`; return ""; }; @@ -145,7 +151,7 @@ const dashboard = async (req, res) => { const body = `

dev-deploy dashboard

- +
@@ -156,11 +162,11 @@ const dashboard = async (req, res) => {
Env ID${escape(env ? env.env_id : "?")}
Label${env && env.env_label ? escape(env.env_label) : '(unset)'}
Destructive-op policy${escape(env ? env.on_destructive_op : "?")}
Pending conflicts${conflictCount > 0 ? `${escape(conflictCount)}` : "0"}

Ops by type

- ${opsByKindRows}
op_typecount
+ ${opsByKindRows}
op_typecount

Entities tracked

- ${entRows}
kindcount
+ ${entRows}
kindcount
`; - res.type("text/html").send(layout("dev-deploy dashboard", body, flashMsg(req))); + res.sendWrap("dev-deploy dashboard", layout(req, body, flashMsg(req))); }; @@ -225,13 +231,13 @@ const opsView = async (req, res) => { const body = `

Journal

Showing up to ${escape(limit)} ops${since ? `, since op ${escape(since.slice(0, 8))}` : ""}, offset ${escape(offset)}. Newest first. Revert appends a compensating op rather than rewriting history.

- +
${rows}
opop_typeentityparentstatuscreatedactionspayload

${prevLink}   ${nextLink}

`; - res.type("text/html").send(layout("dev-deploy journal", body, flashMsg(req))); + res.sendWrap("dev-deploy journal", layout(req, body, flashMsg(req))); }; @@ -276,7 +282,7 @@ const peersView = async (req, res) => { const body = `

Peers

This instance's env_id is ${escape(env ? env.env_id : "?")}. Paste this into the other instance's peer form.

- +
${rows}
idlabelenv_idbase_urllast seenactions
@@ -299,7 +305,7 @@ const peersView = async (req, res) => {

`; - res.type("text/html").send(layout("dev-deploy peers", body, flashMsg(req))); + res.sendWrap("dev-deploy peers", layout(req, body, flashMsg(req))); }; @@ -327,7 +333,7 @@ const peersAdd = async (req, res) => {

If this is a brand-new pairing, give the peer this side's env_id too.

Back to peers

`; - res.type("text/html").send(layout("Peer paired", body)); + res.sendWrap("Peer paired", layout(req, body)); } catch (err) { res.redirect("/admin/dev-deploy/peers?err=" + encodeURIComponent(err.message)); } @@ -346,7 +352,7 @@ const peersRotate = async (req, res) => {

Paste this on the other side via Rotate or by re-pairing.

Back to peers

`; - res.type("text/html").send(layout("Secret rotated", body)); + res.sendWrap("Secret rotated", layout(req, body)); } catch (err) { res.redirect("/admin/dev-deploy/peers?err=" + encodeURIComponent(err.message)); } @@ -382,7 +388,7 @@ const planView = async (req, res) => { `; - res.type("text/html").send(layout("dev-deploy plan", body, flashMsg(req))); + res.sendWrap("dev-deploy plan", layout(req, body, flashMsg(req))); return; } const peerId = parseInt(peerIdRaw, 10); @@ -421,7 +427,7 @@ const planView = async (req, res) => {

Plan: promote to ${escape(peer.label || peer.env_id)}

Anchor: ${anchor ? `${escape(anchor.last_op_id.slice(0, 8))}` : '(none — will send from epoch)'}

Ops that would be sent: ${escape(planRows.length)}

- +
${rowsHtml}
opop_typeentitystatuscreated
@@ -431,7 +437,7 @@ const planView = async (req, res) => {

`; - res.type("text/html").send(layout("dev-deploy plan", body, flashMsg(req))); + res.sendWrap("dev-deploy plan", layout(req, body, flashMsg(req))); }; @@ -628,12 +634,12 @@ const conflictsView = async (req, res) => {
  • Use theirs: applies the incoming op now (overwrites local change). The local op stays in the journal but its effect is overwritten.
  • Use mine: marks the incoming op as rejected. The local state stands. The peer may re-send the op on future syncs; subsequent pulls will skip it via idempotency.
  • - +
    ${rowsHtml}
    TheirsMineAction
    `; - res.type("text/html").send(layout("dev-deploy conflicts", body, flashMsg(req))); + res.sendWrap("dev-deploy conflicts", layout(req, body, flashMsg(req))); }; @@ -686,7 +692,7 @@ const conflictsMergeView = async (req, res) => { const ent = diff.instance ? `

    Entity: ${escape(diff.kind)} ${escape(diff.instance.name || diff.instance.role || diff.instance.id)} (local id ${escape(diff.instance.id)})

    ` - : `

    ${escape(diff.reason || "no entity diff available")}

    `; + : `

    ${escape(diff.reason || "no entity diff available")}

    `; let formBody; if (diffs.length === 0) { @@ -705,7 +711,7 @@ const conflictsMergeView = async (req, res) => { `).join(""); formBody = ` - +
    ${rows}
    fieldcurrent (mine)incoming (theirs)resolution
    @@ -727,7 +733,7 @@ const conflictsMergeView = async (req, res) => {

    `; - res.type("text/html").send(layout("dev-deploy merge", body, flashMsg(req))); + res.sendWrap("dev-deploy merge", layout(req, body, flashMsg(req))); } catch (err) { res.redirect("/admin/dev-deploy/conflicts?err=" + encodeURIComponent(err.message)); } @@ -834,12 +840,12 @@ const tablesView = async (req, res) => {
  • managed — rows always sync from source. Source is canonical; target's edits get overwritten or surface as conflicts. Good for catalogs, lookup tables, anything dev-curated.
  • The Saltcorn users table is locked to user and cannot be changed.

    - +
    ${rowsHtml}
    tableuuidlocal iddata_modeupdated_at
    `; - res.type("text/html").send(layout("dev-deploy tables", body, flashMsg(req))); + res.sendWrap("dev-deploy tables", layout(req, body, flashMsg(req))); };