const state = { userId: localStorage.getItem("userId") || "", displayName: localStorage.getItem("displayName") || "", }; const $ = (sel) => document.querySelector(sel), setText = (sel, v) => { const el = $(sel); if (el) el.textContent = v; }; const authStatusEl = $("#authStatus"), listStatusEl = $("#listStatus"), createStatusEl = $("#createStatus"), runNowStatusEl = $("#runNowStatus"), schedulesTbody = $("#schedulesTbody"), templatesUl = $("#templatesUl"); // update login ui from state function paintAuth() { $("#userId").value = state.userId || ""; $("#displayName").value = state.displayName || ""; if (state.userId) { authStatusEl.textContent = `logged in as ${state.displayName ? state.displayName + " ยท " : "" }${state.userId}`; } else { authStatusEl.textContent = "not logged in"; } } // wrap fetch to always attach x-user-id async function apiFetch(url, options = {}) { const headers = new Headers(options.headers || {}); if (!state.userId) throw new Error( "no user id set โ€” use the login form first" ); headers.set("x-user-id", state.userId); // custom header if ( !headers.has("content-type") && options.body && !(options.body instanceof FormData) ) { headers.set("content-type", "application/json"); } const resp = await fetch(url, { ...options, headers }); if (!resp.ok) { // try to surface json error bodies let msg = `${resp.status} ${resp.statusText}`; try { const data = await resp.json(); if (data && data.error) msg = data.error; } catch { } throw new Error(msg); } return resp; } // render list function renderSchedules(items = []) { schedulesTbody.innerHTML = ""; items.forEach((it) => { const tr = document.createElement("tr"); const tRef = it.templateRef ? it.templateRef.clusterScope ? `(cluster) ${it.templateRef.name}` : it.templateRef.name : ""; tr.innerHTML = ` ${escapeHtml(it.displayName || it.name || "")} ${(it.schedules || []).map(escapeHtml).join("
")} ${escapeHtml(it.timezone || "")} ${escapeHtml(tRef)} ${escapeHtml(it.entrypoint || "")} ${it.oneShot ? "yes" : "no"} `; schedulesTbody.appendChild(tr); }); } // tiny escape helper function escapeHtml(s = "") { return String(s) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">"); } // wire up events $("#loginForm").addEventListener("submit", (e) => { e.preventDefault(); const userId = $("#userId").value.trim(), displayName = $("#displayName").value.trim(); if (!userId) { authStatusEl.textContent = "please enter a user id"; return; } state.userId = userId; state.displayName = displayName; localStorage.setItem("userId", state.userId); localStorage.setItem("displayName", state.displayName); paintAuth(); }); $("#logoutBtn").addEventListener("click", () => { localStorage.removeItem("userId"); localStorage.removeItem("displayName"); state.userId = ""; state.displayName = ""; paintAuth(); }); $("#refreshBtn").addEventListener("click", async () => { try { listStatusEl.textContent = "loading..."; const res = await apiFetch("/api/schedules"); const data = await res.json(); renderSchedules(data.items || []); listStatusEl.textContent = `loaded ${Array.isArray(data.items) ? data.items.length : 0 } schedule(s)`; } catch (e) { listStatusEl.textContent = `error: ${e.message}`; } }); // delete handler (delegated) schedulesTbody.addEventListener("click", async (e) => { const target = e.target; if (!(target instanceof HTMLButtonElement)) return; const name = target.getAttribute("data-del"); if (!name) return; try { target.disabled = true; const res = await apiFetch(`/schedules/${name}`, { method: "DELETE", }); if (res.status === 204) { target.closest("tr")?.remove(); listStatusEl.textContent = "deleted"; } else { listStatusEl.textContent = "unexpected response"; } } catch (err) { listStatusEl.textContent = `error: ${err.message}`; } finally { target.disabled = false; } }); // create/update schedule $("#createForm").addEventListener("submit", async (e) => { e.preventDefault(); try { createStatusEl.textContent = "saving..."; const name = $("#name").value.trim(), tz = $("#tz").value.trim() || "America/New_York", iso = $("#iso").value ? new Date($("#iso").value).toISOString() : "", cron = $("#cron").value.trim(), templateName = $("#templateName").value.trim(), entrypoint = $("#entrypoint").value.trim(), clusterScope = $("#clusterScope").checked, oneShot = $("#oneShot").checked, paramsRaw = $("#params").value.trim(); let parameters = {}; if (paramsRaw) { try { parameters = JSON.parse(paramsRaw); } catch { throw new Error("parameters must be valid json"); } } const payload = { name, when: cron ? { cron } : { iso }, tz, oneShot, template: { name: templateName, clusterScope }, parameters, entrypoint: entrypoint || undefined, }; await apiFetch("/schedules", { method: "POST", body: JSON.stringify(payload), }); createStatusEl.textContent = "saved โœ…"; $("#refreshBtn").click(); } catch (err) { createStatusEl.textContent = `error: ${err.message}`; } }); // run now $("#runNowForm").addEventListener("submit", async (e) => { e.preventDefault(); try { runNowStatusEl.textContent = "starting..."; const name = $("#rnName").value.trim() || "ad-hoc", templateName = $("#rnTemplateName").value.trim(), entrypoint = $("#rnEntrypoint").value.trim(), clusterScope = $("#rnClusterScope").checked, paramsRaw = $("#rnParams").value.trim(); let parameters = {}; if (paramsRaw) { try { parameters = JSON.parse(paramsRaw); } catch { throw new Error("parameters must be valid json"); } } const payload = { name, template: { name: templateName, clusterScope }, entrypoint: entrypoint || undefined, parameters, }; await apiFetch("/run-now", { method: "POST", body: JSON.stringify(payload), }); runNowStatusEl.textContent = "started โœ…"; } catch (err) { runNowStatusEl.textContent = `error: ${err.message}`; } }); // load workflow templates for convenience $("#loadTemplatesBtn").addEventListener("click", async () => { try { templatesUl.innerHTML = ""; templatesUl.parentElement.open = true; const res = await apiFetch("/api/workflowtemplates"), data = await res.json(); (data.items || []).forEach((t) => { const li = document.createElement("li"); li.textContent = t.name; templatesUl.appendChild(li); }); } catch (e) { templatesUl.innerHTML = `
  • error: ${escapeHtml( e.message )}
  • `; } }); // boot paintAuth(); // auto-refresh if already logged in if (state.userId) $("#refreshBtn").click();