Menu
Public repository / 0c236c0a909a7e427ba31e516b9f4fa15cbcd6ce
AkurAI-Platform / frontend/portal.js
Latest change 6401b9edc6e7367bac167df29ad7cbad46e44b68 - fix: verify managed platform lifecycle and portal (AKURAI-PLATFORM-10, AKURAI-PLATFORM-11, AKURAI-PLATFORM-12, AKURAI-PLATFORM-13, AKURAI-PLATFORM-14, AKURAI-PLATFORM-16, AKURAI-PLATFORM-18) by Ólafur Búi Ólafsson
const portal = document.querySelector("[data-control-plane]");
if (portal) {
const state = portal.querySelector("[data-portal-state]");
const organizations = portal.querySelector("[data-portal-organizations]");
const refresh = portal.querySelector("[data-portal-refresh]");
function setState(message, tone = "") {
state.textContent = message;
state.dataset.tone = tone;
}
function text(value) {
return String(value ?? "—");
}
function card(label, value) {
const item = document.createElement("li");
item.innerHTML = `<span>${label}</span><strong></strong>`;
item.querySelector("strong").textContent = text(value);
return item;
}
async function api(path, init) {
const response = await fetch(path, { credentials: "same-origin", headers: { accept: "application/json", ...(init?.headers ?? {}) }, ...init });
if (!response.ok) throw Object.assign(new Error("request_failed"), { status: response.status });
return response.json();
}
async function loadOrganization(id, root) {
setState("Loading organization control plane…");
try {
const [organization, members, entitlements, installations, domains, backups, invoices, audit, subscription, deliveries] = await Promise.all([
api(`/api/organizations/${encodeURIComponent(id)}`),
api(`/api/organizations/${encodeURIComponent(id)}/members`),
api(`/api/organizations/${encodeURIComponent(id)}/entitlements`),
api(`/api/organizations/${encodeURIComponent(id)}/installations`),
api(`/api/organizations/${encodeURIComponent(id)}/domains`),
api(`/api/organizations/${encodeURIComponent(id)}/backups`),
api(`/api/organizations/${encodeURIComponent(id)}/invoices`),
api(`/api/organizations/${encodeURIComponent(id)}/audit`),
api(`/api/organizations/${encodeURIComponent(id)}/subscription-management`),
api(`/api/organizations/${encodeURIComponent(id)}/subscription-management/deliveries`),
]);
const facts = root.querySelector("[data-portal-facts]");
facts.replaceChildren(
card("Role", organization.role),
card("Plan", subscription.status),
card("Members", members.memberships.length),
card("Entitlements", entitlements.entitlements.length),
card("Deployment health", `${installations.installations.filter((item) => item.status === "installed").length}/${installations.installations.length} installed`),
card("Domains", domains.domains.length),
card("Verified backups", backups.backups.filter((item) => item.status === "verified").length),
card("Invoices", invoices.invoices.length),
card("Immutable audit events", audit.lifecycle.length + audit.payments.length),
card("Recovery deliveries", deliveries.deliveries.length),
);
root.querySelector("[data-portal-recovery]").hidden = false;
setState("Organization control plane loaded.", "ok");
} catch (error) {
setState(error.status === 401 ? "Sign in with AkurAI ID to load your organizations." : "The organization control plane is temporarily unavailable.", "error");
}
}
async function load() {
organizations.replaceChildren();
setState("Loading your organizations…");
try {
const result = await api("/api/organizations");
if (!result.organizations.length) {
setState("No organizations are linked to this AkurAI ID yet.");
return;
}
for (const organization of result.organizations) {
const root = document.createElement("article");
root.className = "portal-organization";
const title = document.createElement("h3");
title.textContent = organization.name;
const open = document.createElement("button");
open.className = "btn btn-secondary";
open.type = "button";
open.textContent = "Open control plane";
const facts = document.createElement("ul");
facts.className = "portal-facts";
facts.dataset.portalFacts = "";
const recovery = document.createElement("button");
recovery.className = "btn btn-tertiary";
recovery.type = "button";
recovery.hidden = true;
recovery.dataset.portalRecovery = "";
recovery.textContent = "Send a new management link to the verified customer email";
recovery.addEventListener("click", async () => {
recovery.disabled = true;
try {
await api(`/api/organizations/${encodeURIComponent(organization.organizationId)}/subscription-management/recovery`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
setState("A management link is being delivered to the verified customer email.", "ok");
} catch {
setState("Delivery was not accepted. Its failure is recorded and can be retried.", "error");
} finally {
recovery.disabled = false;
}
});
open.addEventListener("click", () => loadOrganization(organization.organizationId, root));
root.append(title, open, facts, recovery);
organizations.append(root);
}
setState("Choose an organization to load its control plane.", "ok");
} catch (error) {
setState(error.status === 401 ? "Sign in with AkurAI ID to access your organization control plane." : "The organization portal is temporarily unavailable.", "error");
}
}
refresh.addEventListener("click", load);
load();
}