252 lines
7.9 KiB
JavaScript
252 lines
7.9 KiB
JavaScript
let _lastMintedToken = "";
|
|
|
|
function showNotice(msg, isError) {
|
|
const banner = document.getElementById("status-banner");
|
|
if (!banner) return;
|
|
banner.textContent = msg;
|
|
banner.style.display = "block";
|
|
banner.style.background = isError ? "var(--danger-bg)" : "var(--success-bg)";
|
|
banner.style.color = isError ? "var(--danger-text)" : "var(--success-text)";
|
|
banner.style.border = isError
|
|
? "1px solid var(--danger-border)"
|
|
: "1px solid var(--success-border)";
|
|
setTimeout(() => {
|
|
banner.style.display = "none";
|
|
}, 5000);
|
|
}
|
|
globalThis.showNotice = showNotice;
|
|
|
|
function openDelegateDrawer() {
|
|
const drawer = document.getElementById("delegateDrawer");
|
|
if (drawer) {
|
|
drawer.style.display = "block";
|
|
drawer.offsetHeight;
|
|
drawer.classList.add("open");
|
|
const panel = drawer.querySelector(".drawer-panel");
|
|
if (panel) panel.classList.add("open");
|
|
}
|
|
}
|
|
globalThis.openDelegateDrawer = openDelegateDrawer;
|
|
|
|
function closeDelegateDrawer() {
|
|
const drawer = document.getElementById("delegateDrawer");
|
|
if (drawer) {
|
|
drawer.classList.remove("open");
|
|
const panel = drawer.querySelector(".drawer-panel");
|
|
if (panel) panel.classList.remove("open");
|
|
setTimeout(() => {
|
|
drawer.style.display = "none";
|
|
}, 250);
|
|
}
|
|
}
|
|
globalThis.closeDelegateDrawer = closeDelegateDrawer;
|
|
|
|
function selectLifespan(btn, hours) {
|
|
document.querySelectorAll(".lifespan-pill").forEach((b) =>
|
|
b.classList.remove("active")
|
|
);
|
|
btn.classList.add("active");
|
|
const input = document.getElementById("delegateHours");
|
|
if (input) input.value = hours;
|
|
}
|
|
globalThis.selectLifespan = selectLifespan;
|
|
|
|
function selectMode(btn, mode) {
|
|
document.querySelectorAll(".mode-pill").forEach((b) =>
|
|
b.classList.remove("active")
|
|
);
|
|
btn.classList.add("active");
|
|
const input = document.getElementById("delegateMode");
|
|
if (input) input.value = mode;
|
|
|
|
const matrix = document.getElementById("customScopeMatrix");
|
|
if (matrix) {
|
|
matrix.style.display = mode === "custom" ? "block" : "none";
|
|
}
|
|
}
|
|
globalThis.selectMode = selectMode;
|
|
|
|
function handleTargetAppChange(appName) {
|
|
if (appName) {
|
|
const scopeVal = "app:" + appName;
|
|
document.querySelectorAll(".scope-checkbox").forEach((cb) => {
|
|
if (cb.value === scopeVal) cb.checked = true;
|
|
});
|
|
}
|
|
}
|
|
globalThis.handleTargetAppChange = handleTargetAppChange;
|
|
|
|
async function handleDelegateSession(e) {
|
|
e.preventDefault();
|
|
const labelInput = document.getElementById("delegateLabel");
|
|
const label = labelInput ? labelInput.value.trim() : "Delegated Session";
|
|
const hoursInput = document.getElementById("delegateHours");
|
|
const lifespanHours = parseInt(hoursInput ? hoursInput.value : "1", 10) || 1;
|
|
const modeInput = document.getElementById("delegateMode");
|
|
let mode = modeInput ? modeInput.value : "read";
|
|
const targetAppInput = document.getElementById("delegateTargetApp");
|
|
const targetApp = targetAppInput ? targetAppInput.value : "";
|
|
|
|
const customScopes = [];
|
|
if (targetApp) {
|
|
customScopes.push("app:" + targetApp);
|
|
}
|
|
if (mode === "custom" || targetApp) {
|
|
document.querySelectorAll(".scope-checkbox:checked").forEach((cb) => {
|
|
if (!customScopes.includes(cb.value)) {
|
|
customScopes.push(cb.value);
|
|
}
|
|
});
|
|
if (targetApp && mode !== "custom" && mode !== "admin") {
|
|
mode = "custom";
|
|
}
|
|
}
|
|
|
|
const btn = document.getElementById("submitDelegateBtn");
|
|
if (btn) {
|
|
btn.disabled = true;
|
|
btn.textContent = "Minting...";
|
|
}
|
|
|
|
try {
|
|
const res = await fetch("/api/sessions/delegate", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ label, lifespanHours, mode, customScopes }),
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok) {
|
|
_lastMintedToken = data.token;
|
|
const origin = globalThis.location ? globalThis.location.origin : "";
|
|
const magicElem = document.getElementById("handoffMagicLinkText");
|
|
if (magicElem) {
|
|
magicElem.textContent = origin + "/pass?token=" + data.token;
|
|
}
|
|
const cliElem = document.getElementById("handoffCliText");
|
|
if (cliElem) {
|
|
cliElem.textContent = 'export AUTH_YES_TOKEN="' + data.token + '"';
|
|
}
|
|
const curlElem = document.getElementById("handoffCurlText");
|
|
if (curlElem) {
|
|
curlElem.textContent = '-H "Authorization: Bearer ' + data.token + '"';
|
|
}
|
|
const handoffModal = document.getElementById("handoffModal");
|
|
if (handoffModal) handoffModal.style.display = "block";
|
|
showNotice("Delegated session created: " + data.label, false);
|
|
} else {
|
|
showNotice(data.error || "Failed to delegate session", true);
|
|
}
|
|
} catch (_err) {
|
|
showNotice("Network error delegating session", true);
|
|
} finally {
|
|
if (btn) {
|
|
btn.disabled = false;
|
|
btn.textContent = "Mint & Delegate Session";
|
|
}
|
|
}
|
|
}
|
|
globalThis.handleDelegateSession = handleDelegateSession;
|
|
|
|
function copyHandoff(type) {
|
|
let text = "";
|
|
if (type === "cli") {
|
|
const el = document.getElementById("handoffCliText");
|
|
if (el) text = el.textContent;
|
|
}
|
|
if (type === "curl") {
|
|
const el = document.getElementById("handoffCurlText");
|
|
if (el) text = el.textContent;
|
|
}
|
|
if (type === "magic") {
|
|
const el = document.getElementById("handoffMagicLinkText");
|
|
if (el) text = el.textContent;
|
|
}
|
|
if (navigator.clipboard) {
|
|
navigator.clipboard.writeText(text);
|
|
showNotice("Copied to clipboard!", false);
|
|
}
|
|
}
|
|
globalThis.copyHandoff = copyHandoff;
|
|
|
|
function closeHandoffModal() {
|
|
const modal = document.getElementById("handoffModal");
|
|
if (modal) modal.style.display = "none";
|
|
if (globalThis.location) globalThis.location.reload();
|
|
}
|
|
globalThis.closeHandoffModal = closeHandoffModal;
|
|
|
|
function openEditScopesModal(sessionId, label, scopesJson) {
|
|
let scopes = [];
|
|
try {
|
|
scopes = typeof scopesJson === "string"
|
|
? JSON.parse(scopesJson)
|
|
: scopesJson;
|
|
if (typeof scopes === "string") scopes = JSON.parse(scopes);
|
|
} catch (_e) {
|
|
scopes = [];
|
|
}
|
|
const idInput = document.getElementById("editScopesSessionId");
|
|
if (idInput) idInput.value = sessionId;
|
|
const labelEl = document.getElementById("editScopesLabel");
|
|
if (labelEl) labelEl.textContent = label;
|
|
|
|
document.querySelectorAll(".edit-scope-chk").forEach((cb) => {
|
|
cb.checked = Array.isArray(scopes) && scopes.includes(cb.value);
|
|
});
|
|
|
|
const modal = document.getElementById("editScopesModal");
|
|
if (modal) modal.style.display = "flex";
|
|
}
|
|
globalThis.openEditScopesModal = openEditScopesModal;
|
|
|
|
function closeEditScopesModal() {
|
|
const modal = document.getElementById("editScopesModal");
|
|
if (modal) modal.style.display = "none";
|
|
}
|
|
globalThis.closeEditScopesModal = closeEditScopesModal;
|
|
|
|
async function saveUpdatedScopes() {
|
|
const idInput = document.getElementById("editScopesSessionId");
|
|
const sessionId = idInput ? idInput.value : "";
|
|
const customScopes = [];
|
|
document.querySelectorAll(".edit-scope-chk:checked").forEach((cb) => {
|
|
customScopes.push(cb.value);
|
|
});
|
|
|
|
try {
|
|
const res = await fetch("/api/sessions/" + sessionId + "/scopes", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ customScopes }),
|
|
});
|
|
if (res.ok) {
|
|
showNotice("Session scopes updated!", false);
|
|
setTimeout(() => {
|
|
if (globalThis.location) globalThis.location.reload();
|
|
}, 500);
|
|
} else {
|
|
const data = await res.json();
|
|
showNotice(data.error || "Failed to update scopes", true);
|
|
}
|
|
} catch (_err) {
|
|
showNotice("Network error updating scopes", true);
|
|
}
|
|
}
|
|
globalThis.saveUpdatedScopes = saveUpdatedScopes;
|
|
|
|
function copyText(text) {
|
|
if (navigator.clipboard) {
|
|
navigator.clipboard.writeText(text);
|
|
showNotice("Copied to clipboard!", false);
|
|
}
|
|
}
|
|
globalThis.copyText = copyText;
|
|
|
|
// Global escape listener for modals and drawers
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape") {
|
|
closeDelegateDrawer();
|
|
closeEditScopesModal();
|
|
}
|
|
});
|