const state = {
view: location.hash.slice(1) || "overview",
apiKey: sessionStorage.getItem("maester_api_key") || "",
workspaceId: "",
campaigns: [], variants: [], jobs: [], assets: [], calendar: [], workspaces: [],
accounts: [], integrations: [], experiments: [], rules: [], audit: [], overview: {}, selectedVariantId: null,
};
const $ = (query, root = document) => root.querySelector(query);
const $$ = (query, root = document) => [...root.querySelectorAll(query)];
const escapeHtml = (value = "") => String(value).replace(/[&<>'"]/g, char => ({"&":"&","<":"<",">":">","'":"'",'"':"""}[char]));
const badge = value => `${escapeHtml(String(value).replaceAll("_", " "))}`;
const date = value => value ? new Date(value).toLocaleString() : "-";
const tags = values => `
${(values || []).map(value => `${escapeHtml(String(value).replaceAll("_", " "))}`).join("")}
`;
const jsonPost = body => ({method: "POST", body: JSON.stringify(body)});
function toast(message, error = false) {
const node = document.createElement("div");
node.className = `toast${error ? " error" : ""}`;
node.textContent = message;
$("#toast-region").append(node);
setTimeout(() => node.remove(), 4000);
}
async function api(path, options = {}) {
const headers = new Headers(options.headers || {});
if (state.apiKey) headers.set("X-API-Key", state.apiKey);
if (options.body && !(options.body instanceof FormData)) headers.set("Content-Type", "application/json");
const response = await fetch(path, {...options, headers, credentials: "same-origin"});
if (response.status === 401) {
$("#settings-dialog").showModal();
throw new Error("API key required");
}
if (!response.ok) {
const body = await response.json().catch(() => ({detail: response.statusText}));
throw new Error(typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail));
}
return response.status === 204 ? null : response.json();
}
async function loadData() {
const scope = state.workspaceId ? `?workspace_id=${encodeURIComponent(state.workspaceId)}` : "";
const values = await Promise.all([
api(`/automation/overview${scope}`), api("/automation/campaigns"), api("/automation/variants"), api("/automation/jobs"),
api("/automation/assets"), api("/automation/calendar"), api("/automation/workspaces"), api("/automation/accounts"),
api("/automation/integrations"), api("/automation/experiments"), api("/automation/rules"), api("/automation/audit?limit=100"),
]);
[state.overview, state.campaigns, state.variants, state.jobs, state.assets, state.calendar, state.workspaces, state.accounts, state.integrations, state.experiments, state.rules, state.audit] = values;
$(".status-dot").classList.add("online");
$("#connection-label").textContent = "Gateway online";
fillSelectors();
}
function fillSelectors() {
$("#workspace-filter").innerHTML = `${state.workspaces.map(item => ``).join("")}`;
$("#workspace-filter").value = state.workspaceId;
const campaignOptions = state.campaigns.map(item => ``).join("");
const editorValue = $("#editor-campaign-filter").value;
$("#editor-campaign-filter").innerHTML = `${campaignOptions}`;
$("#editor-campaign-filter").value = editorValue;
$("#calendar-campaign").innerHTML = campaignOptions;
const variantOptions = state.variants.map(item => ``).join("");
$("#analytics-variant").innerHTML = variantOptions;
$("#review-variants").innerHTML = variantOptions;
}
function metric(label, value, detail = "") {
return `${label}
${value}
${escapeHtml(detail)}
`;
}
function renderOverview() {
const data = state.overview;
$("#overview-metrics").innerHTML = metric("Campaigns", data.campaigns || 0, "Production briefs") + metric("Variants", data.variants || 0, "Platform cuts") + metric("Queued", data.queue?.states?.queued || 0, `${data.queue?.total || 0} jobs`) + metric("Published", data.approvals?.published || 0, "Live variants");
const queue = data.queue?.states || {};
$("#overview-queue").innerHTML = Object.keys(queue).length ? Object.entries(queue).map(([name, count]) => `${escapeHtml(name)}
${count} `).join("") : `Queue is clear
`;
const scores = data.analytics?.platform_average_scores || {};
$("#overview-performance").innerHTML = Object.keys(scores).length ? Object.entries(scores).map(([name, score]) => `${escapeHtml(name)}
${score} `).join("") : `No performance data
`;
$("#overview-approvals").innerHTML = `${Object.entries(data.approvals || {}).map(([name, count]) => metric(name.replaceAll("_", " "), count)).join("")}
`;
}
function renderCampaigns() {
const counts = state.variants.reduce((result, item) => ({...result, [item.campaign_id]: (result[item.campaign_id] || 0) + 1}), {});
$("#campaign-table").innerHTML = state.campaigns.map(item => `| ${escapeHtml(item.name)} ${date(item.updated_at)} | ${escapeHtml(item.topic)} | ${tags(item.platforms)} | ${counts[item.id] || 0} | ${badge(item.status)} | |
`).join("") || `No campaigns |
`;
}
function renderEditor() {
const campaignId = $("#editor-campaign-filter").value;
const variants = state.variants.filter(item => !campaignId || item.campaign_id === campaignId);
$("#variant-list").innerHTML = variants.map(item => ``).join("") || `No variants
`;
const item = state.variants.find(variant => variant.id === state.selectedVariantId);
if (!item) return;
$("#variant-editor").innerHTML = ``;
$("#variant-form").addEventListener("submit", saveVariant);
renderInspector(item);
}
async function renderInspector(item) {
try {
const quality = await api(`/automation/quality/variants/${item.id}`);
$("#variant-inspector").innerHTML = `Inspector
${badge(item.job_state)}${quality.hook.score}Hook
${quality.script.score}Script
${quality.caption.score}Caption
${Math.round(quality.retention.predicted_completion_rate * 100)}%Completion
Render payload
${escapeHtml(JSON.stringify(item.render_payload, null, 2))}
`;
} catch (error) { $("#variant-inspector").innerHTML = `${escapeHtml(error.message)}
`; }
}
function renderCalendar() {
const variants = Object.fromEntries(state.variants.map(item => [item.id, item]));
$("#calendar-table").innerHTML = state.calendar.sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at)).map(item => `| ${date(item.scheduled_at)} | ${tags([item.platform])} | ${escapeHtml(variants[item.variant_id]?.title || item.variant_id)} | ${badge(item.status)} |
`).join("") || `No scheduled content |
`;
}
function renderJobs() {
const filter = $("#job-filter").value;
$("#job-table").innerHTML = state.jobs.filter(item => !filter || item.state === filter).map(item => `| ${escapeHtml(item.id)} ${escapeHtml(item.idempotency_key || "")} | ${escapeHtml(item.kind)} | ${badge(item.state)} | ${item.attempts}/${item.max_attempts} | ${date(item.updated_at)} | ${["failed", "cancelled"].includes(item.state) ? `` : ""}${!["published", "ready", "failed", "cancelled"].includes(item.state) ? `` : ""} |
`).join("") || `No matching jobs |
`;
}
function renderAnalytics() {
const data = state.overview.analytics || {}, winners = data.winners || [];
$("#analytics-metrics").innerHTML = metric("Winners", winners.length, "Viral score ≥ 20") + metric("Platforms", Object.keys(data.platform_average_scores || {}).length, "Reporting sources") + metric("Experiments", state.experiments.length, "Controlled tests") + metric("Top score", winners[0]?.score || 0, winners[0]?.platform || "No data");
$("#winner-board").innerHTML = winners.length ? `${winners.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.platform)}
${badge(String(item.score))}
`).join("")}
` : `No winners yet
`;
$("#experiment-list").innerHTML = state.experiments.length ? `${state.experiments.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.variable)} · ${item.variant_ids.length} variants
${badge(item.status)}
`).join("")}
` : `No experiments
`;
}
function renderAssets() {
$("#asset-table").innerHTML = state.assets.map(item => `| ${escapeHtml(item.filename)} ${escapeHtml(item.checksum.slice(0, 12))} | ${badge(item.kind)} | ${Math.ceil(item.size_bytes / 1024)} KB | ${escapeHtml(item.license)} ${escapeHtml(item.attribution)} | ${tags(item.tags)} | |
`).join("") || `No assets |
`;
}
function renderReviews() {
const pending = state.variants.filter(item => ["draft", "needs_review"].includes(item.approval_state));
$("#pending-reviews").innerHTML = pending.length ? `${pending.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.platform)}
${badge(item.approval_state)}
`).join("")}
` : `Nothing awaiting review
`;
}
function renderIntegrations() {
$("#account-list").innerHTML = state.accounts.length ? `${state.accounts.map(item => `
${escapeHtml(item.display_name || item.handle)}${escapeHtml(item.platform)} · ${escapeHtml(item.auth_status)}
`).join("")}
` : `No connected accounts
`;
$("#integration-list").innerHTML = state.integrations.length ? `${state.integrations.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.kind)}
${badge(item.active ? "ready" : "disabled")}
`).join("")}
` : `No delivery integrations
`;
$("#rule-list").innerHTML = state.rules.length ? `${state.rules.map(item => `
${escapeHtml(item.name)}${escapeHtml(item.event)} · ${item.actions.length} actions
${badge(item.active ? "ready" : "disabled")}
`).join("")}
` : `No automation rules
`;
}
async function renderOperations() {
const [providers, services] = await Promise.all([api("/automation/providers"), api("/services")]);
$("#ops-metrics").innerHTML = metric("Workspaces", state.workspaces.length, "Tenant boundaries") + metric("Accounts", state.accounts.length, "Publishing targets") + metric("Assets", state.assets.length, "Managed media") + metric("Audit events", state.audit.length, "Recent operations");
$("#provider-status").innerHTML = `${Object.entries(providers.providers).map(([name, item]) => `
${escapeHtml(name)}${escapeHtml(item.model)}
${badge(item.configured ? "ready" : "not_configured")}
`).join("")}
`;
$("#service-status").innerHTML = `${services.services.map(item => `
${escapeHtml(item.title)}${escapeHtml(item.path)}
${badge(item.mounted ? "ready" : item.enabled ? "failed" : "disabled")}
`).join("")}
`;
$("#audit-table").innerHTML = state.audit.map(item => `| ${date(item.created_at)} | ${escapeHtml(item.actor)} | ${escapeHtml(item.action)} | ${escapeHtml(item.resource_type)} · ${escapeHtml(item.resource_id)} |
`).join("") || `No audit events |
`;
}
const renderers = {overview: renderOverview, campaigns: renderCampaigns, editor: renderEditor, calendar: renderCalendar, jobs: renderJobs, analytics: renderAnalytics, assets: renderAssets, reviews: renderReviews, integrations: renderIntegrations, operations: renderOperations};
async function refresh() { try { await loadData(); await renderers[state.view]?.(); } catch (error) { $(".status-dot").classList.remove("online"); $("#connection-label").textContent = "Gateway unavailable"; toast(error.message, true); } }
function switchView(view) { state.view = view; location.hash = view; $$(".view").forEach(item => item.classList.toggle("active", item.id === `view-${view}`)); $$(".nav-item").forEach(item => item.classList.toggle("active", item.dataset.view === view)); $("#view-title").textContent = view[0].toUpperCase() + view.slice(1); $("#sidebar").classList.remove("open"); renderers[view]?.(); }
async function saveVariant(event) {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
data.hashtags = data.hashtags.split(",").map(item => item.trim()).filter(Boolean);
data.duration_seconds = Number(data.duration_seconds);
try { await api(`/automation/variants/${state.selectedVariantId}`, {method: "PATCH", body: JSON.stringify(data)}); toast("Variant saved"); await refresh(); } catch (error) { toast(error.message, true); }
}
async function action(name, id) {
try {
if (name === "process-queue") { const result = await api("/automation/queue/process", {method: "POST"}); toast(`${result.count} job processed`); }
if (name === "select-variant") { state.selectedVariantId = id; renderEditor(); return; }
if (name === "open-campaign") { $("#editor-campaign-filter").value = id; state.selectedVariantId = state.variants.find(item => item.campaign_id === id)?.id; switchView("editor"); return; }
if (name === "generate-ai") await api(`/automation/campaigns/${id}/generate-ai`, jsonPost({}));
if (name === "schedule-campaign") await api(`/automation/schedule/apply?campaign_id=${id}`, {method: "POST"});
if (name === "apply-schedule") { const start = $("#calendar-start").value; await api(`/automation/schedule/apply?campaign_id=${$("#calendar-campaign").value}${start ? `&start_at=${encodeURIComponent(new Date(start).toISOString())}` : ""}`, {method: "POST"}); }
if (name === "approve-variant") await api(`/automation/variants/${id}/approval`, jsonPost({state: "approved", reviewer: "Studio operator", notes: "Approved in Content Studio"}));
if (name === "render-variant") await api(`/automation/variants/${id}/render-job`, {method: "POST"});
if (name === "publish-variant") await api("/automation/publish", jsonPost({variant_id: id, draft: true}));
if (name === "rewrite-variant") await api(`/automation/variants/${id}/rewrite`, {method: "POST"});
if (name === "retry-job") await api(`/automation/jobs/${id}/retry`, {method: "POST"});
if (name === "cancel-job") await api(`/automation/jobs/${id}/cancel`, {method: "POST"});
if (name === "delete-asset") await api(`/automation/assets/${id}`, {method: "DELETE"});
if (name === "sync-account") await api(`/automation/accounts/${id}/analytics-sync`, {method: "POST"});
if (name === "evaluate-experiment") await api(`/automation/experiments/${id}/evaluate`, {method: "POST"});
if (name === "new-experiment") { const campaign = state.campaigns[0]; if (!campaign) throw new Error("Create a campaign first"); await api("/automation/experiments", jsonPost({campaign_id: campaign.id, name: `${campaign.name} hook test`, variable: "hook", variant_ids: state.variants.filter(item => item.campaign_id === campaign.id).map(item => item.id)})); }
if (name === "new-rule") await api("/automation/rules", jsonPost({name: "Render approved variants", event: "variant_approved", actions: [{type: "render"}], active: true}));
toast("Operation completed"); await refresh();
} catch (error) { toast(error.message, true); }
}
document.addEventListener("click", event => { const nav = event.target.closest("[data-view]"); if (nav) switchView(nav.dataset.view); const dialog = event.target.closest("[data-dialog]"); if (dialog) $(`#${dialog.dataset.dialog}`).showModal(); const button = event.target.closest("[data-action]"); if (button) action(button.dataset.action, button.dataset.id); });
$("#menu-button").addEventListener("click", () => $("#sidebar").classList.toggle("open"));
$("#refresh-button").addEventListener("click", refresh);
$("#settings-button").addEventListener("click", () => $("#settings-dialog").showModal());
$("#settings-form").addEventListener("submit", event => { event.preventDefault(); state.apiKey = $("#api-key").value.trim(); sessionStorage.setItem("maester_api_key", state.apiKey); $("#settings-dialog").close(); refresh(); });
$("#workspace-filter").addEventListener("change", event => { state.workspaceId = event.target.value; refresh(); });
$("#editor-campaign-filter").addEventListener("change", renderEditor);
$("#job-filter").addEventListener("change", renderJobs);
$("#campaign-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = Object.fromEntries(form); data.platforms = form.getAll("platforms"); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.brand_kit_id ||= null; try { await api("/automation/campaigns/generate", jsonPost(data)); event.currentTarget.reset(); $("#campaign-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#repurpose-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.quantity = Number(data.quantity); data.workspace_id = state.workspaceId || null; data.platforms = ["tiktok", "instagram_reels", "youtube_shorts"]; try { await api("/automation/repurpose", jsonPost(data)); event.currentTarget.reset(); $("#repurpose-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#analytics-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); ["views", "likes", "comments", "shares"].forEach(key => data[key] = Number(data[key])); data.completion_rate = Number(data.completion_rate); try { await api("/automation/analytics/events", jsonPost(data)); await refresh(); } catch (error) { toast(error.message, true); } });
$("#asset-form").addEventListener("submit", async event => { event.preventDefault(); const data = new FormData(event.currentTarget); if (state.workspaceId) data.set("workspace_id", state.workspaceId); try { await api("/automation/assets", {method: "POST", body: data}); event.currentTarget.reset(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#review-form").addEventListener("submit", async event => { event.preventDefault(); const form = new FormData(event.currentTarget); const data = {variant_ids: form.getAll("variant_ids"), expires_in_days: Number(form.get("expires_in_days")), created_by: form.get("created_by"), workspace_id: state.workspaceId || null}; try { const result = await api("/automation/reviews", jsonPost(data)); const url = new URL(result.review_url, location.origin).href; $("#review-result").innerHTML = ``; await refresh(); } catch (error) { toast(error.message, true); } });
$("#account-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; data.metadata = {token_env: data.token_env, publish_endpoint: data.publish_endpoint, analytics_endpoint: data.analytics_endpoint}; delete data.token_env; delete data.publish_endpoint; delete data.analytics_endpoint; try { await api("/automation/accounts", jsonPost(data)); $("#account-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#integration-form").addEventListener("submit", async event => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); data.workspace_id = state.workspaceId || null; try { await api("/automation/integrations", jsonPost(data)); $("#integration-dialog").close(); await refresh(); } catch (error) { toast(error.message, true); } });
$("#api-key").value = state.apiKey;
switchView(state.view in renderers ? state.view : "overview");
refresh();