feat: harden authenticated deployment

This commit is contained in:
2026-07-29 22:04:40 -04:00
parent 432dd2176f
commit 892479dceb
20 changed files with 3677 additions and 1298 deletions
+90 -7
View File
@@ -1,9 +1,92 @@
const csrf = () => document.cookie.split("; ").find((v) => v.startsWith("rp_csrf="))?.split("=")[1] || "";
const setText = (el, text, error = false) => { const node = document.createElement("p"); node.textContent = text; if (error) node.style.color="#a8371a"; el.replaceChildren(node); };
const csrf = () =>
document.cookie
.split("; ")
.find((v) => v.startsWith("rp_csrf="))
?.split("=")[1] || "";
const setText = (el, text, error = false) => {
const node = document.createElement("p");
node.textContent = text;
if (error) node.style.color = "#a8371a";
el.replaceChildren(node);
};
export function initAlogPanel({ state, recompute }) {
const fileInput=document.getElementById("alog-file"), libraryBtn=document.getElementById("alog-library-refresh"), libraryList=document.getElementById("alog-library-list"), resultEl=document.getElementById("alog-result");
fileInput.addEventListener("change", async(e)=>{const file=e.target.files?.[0];if(!file)return;setText(resultEl,"Parsing…");try{const res=await fetch("/api/alog",{method:"POST",headers:{"content-type":"application/json","x-csrf-token":csrf()},body:JSON.stringify({filename:file.name,content:await file.text()})});applyResult(await res.json());}catch(err){setText(resultEl,err.message,true);}});
libraryBtn.addEventListener("click",async()=>{libraryList.classList.remove("hidden");libraryList.replaceChildren(Object.assign(document.createElement("li"),{textContent:"Loading…"}));try{const body=await (await fetch("/api/alog/library")).json();if(!body.ok||!body.files.length){libraryList.replaceChildren(Object.assign(document.createElement("li"),{textContent:"No .alog files found. Set ALOG_DIR or drop files in ~/Roastetta."}));return;}libraryList.replaceChildren(...body.files.map(f=>{const li=document.createElement("li");li.textContent=`${f.filename} (${Math.round(f.sizeBytes/1024)} KB)`;li.onclick=async()=>{setText(resultEl,"Loading…");applyResult(await (await fetch(`/api/alog/library/${encodeURIComponent(f.filename)}`)).json());};return li;}));}catch(err){const li=document.createElement("li");li.textContent=err.message;li.style.color="#a8371a";libraryList.replaceChildren(li);}});
function applyResult(body){if(!body.ok){setText(resultEl,body.error??body.code,true);return;}state.plan.reference=body;recompute();setText(resultEl,`${body.roast.title}${body.roast.roastDate||"no date"}; first crack ${fmt(body.derived?.firstCrackS)}; development ${fmt(body.derived?.developmentS)}; drop ${fmt(body.derived?.dropS)}; DTR ${body.derived?.dtrPct??"—"}%.`);}
const fileInput = document.getElementById("alog-file"),
libraryBtn = document.getElementById("alog-library-refresh"),
libraryList = document.getElementById("alog-library-list"),
resultEl = document.getElementById("alog-result");
fileInput.addEventListener("change", async (e) => {
const file = e.target.files?.[0];
if (!file) return;
setText(resultEl, "Parsing…");
try {
const res = await fetch("/api/alog", {
method: "POST",
headers: { "content-type": "application/json", "x-csrf-token": csrf() },
body: JSON.stringify({
filename: file.name,
content: await file.text(),
}),
});
applyResult(await res.json());
} catch (err) {
setText(resultEl, err.message, true);
}
});
libraryBtn.addEventListener("click", async () => {
libraryList.classList.remove("hidden");
libraryList.replaceChildren(
Object.assign(document.createElement("li"), { textContent: "Loading…" }),
);
try {
const body = await (await fetch("/api/alog/library")).json();
if (!body.ok || !body.files.length) {
libraryList.replaceChildren(
Object.assign(document.createElement("li"), {
textContent:
"No .alog files found. Set ALOG_DIR or drop files in ~/Roastetta.",
}),
);
return;
}
libraryList.replaceChildren(
...body.files.map((f) => {
const li = document.createElement("li");
li.textContent = `${f.filename} (${Math.round(f.sizeBytes / 1024)} KB)`;
li.onclick = async () => {
setText(resultEl, "Loading…");
applyResult(
await (
await fetch(
`/api/alog/library/${encodeURIComponent(f.filename)}`,
)
).json(),
);
};
return li;
}),
);
} catch (err) {
const li = document.createElement("li");
li.textContent = err.message;
li.style.color = "#a8371a";
libraryList.replaceChildren(li);
}
});
function applyResult(body) {
if (!body.ok) {
setText(resultEl, body.error ?? body.code, true);
return;
}
state.plan.reference = body;
recompute();
setText(
resultEl,
`${body.roast.title}${body.roast.roastDate || "no date"}; first crack ${fmt(body.derived?.firstCrackS)}; development ${fmt(body.derived?.developmentS)}; drop ${fmt(body.derived?.dropS)}; DTR ${body.derived?.dtrPct ?? "—"}%.`,
);
}
}
function fmt(seconds) {
if (seconds == null) return "—";
const s = Math.round(seconds);
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
}
function fmt(seconds){if(seconds==null)return "—";const s=Math.round(seconds);return `${Math.floor(s/60)}:${String(s%60).padStart(2,"0")}`;}