From c93be6a27faf24150de1f764efa34548d3ae9905 Mon Sep 17 00:00:00 2001 From: Tyler Gillispie Date: Fri, 25 Sep 2026 19:08:57 -0700 Subject: [PATCH] feat(admin): complete Slice 2 admin client handlers (#79) - Added missing role and invite UI client handlers to `public/admin-scripts.js` mapped to `globalThis`. - Enforced strict backend contract adherence by changing `spiffe_id` to `spiffeId` in `handleSaveApp`. - Updated unused catch error parameter blocks to adhere to Deno conventions (`_e` or `_err`). Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com> --- public/admin-scripts.js | 146 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/public/admin-scripts.js b/public/admin-scripts.js index b8186f2..3277f0e 100644 --- a/public/admin-scripts.js +++ b/public/admin-scripts.js @@ -321,7 +321,7 @@ function openEditAppDrawer(appJson) { let app; try { app = typeof appJson === "string" ? JSON.parse(appJson) : appJson; - } catch (e) { + } catch (_e) { showNotice("Failed to parse application data.", true); return; } @@ -379,7 +379,7 @@ async function handleSaveApp(e) { const payload = { name, - spiffe_id: spiffeId, + spiffeId, description, domain, is_public: isPublic, @@ -413,7 +413,7 @@ async function handleSaveApp(e) { const data = await res.json(); showNotice(data.error || "Failed to save application", true); } - } catch (err) { + } catch (_err) { showNotice("Network error saving application", true); } } @@ -435,8 +435,146 @@ async function deleteApp(appId, appName) { const data = await res.json(); showNotice(data.error || "Failed to delete application", true); } - } catch (err) { + } catch (_err) { showNotice("Network error deleting application", true); } } globalThis.deleteApp = deleteApp; + +// Slice 2: Roles Handlers +function openCreateRoleDrawer() { + const drawer = document.getElementById("roleDrawer"); + if (!drawer) return; + const title = document.getElementById("roleDrawerTitle"); + if (title) title.textContent = "Create Custom Role"; + + const editId = document.getElementById("roleEditId"); + if (editId) editId.value = ""; + const nameInput = document.getElementById("roleNameInput"); + if (nameInput) nameInput.value = ""; + const appSelect = document.getElementById("roleAppSelect"); + if (appSelect) appSelect.value = ""; + const descInput = document.getElementById("roleDescInput"); + if (descInput) descInput.value = ""; + + drawer.style.display = "block"; +} +globalThis.openCreateRoleDrawer = openCreateRoleDrawer; + +function closeRoleDrawer() { + const drawer = document.getElementById("roleDrawer"); + if (drawer) drawer.style.display = "none"; +} +globalThis.closeRoleDrawer = closeRoleDrawer; + +async function handleSaveRole(e) { + e.preventDefault(); + const id = document.getElementById("roleEditId")?.value; + const name = document.getElementById("roleNameInput")?.value?.trim(); + const app_id = document.getElementById("roleAppSelect")?.value; + const description = document.getElementById("roleDescInput")?.value?.trim(); + + if (!name) { + showNotice("Role name is required", true); + return; + } + + const payload = { id, name, description, app_id: app_id || null }; + + try { + const res = await fetch("/api/admin/roles", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (res.ok) { + showNotice("Role saved successfully!", false); + setTimeout(() => globalThis.location.reload(), 600); + } else { + const data = await res.json(); + showNotice(data.error || "Failed to save role", true); + } + } catch (_err) { + showNotice("Network error saving role", true); + } +} +globalThis.handleSaveRole = handleSaveRole; + +async function deleteRole(roleId, roleName) { + if (!confirm("Permanently delete role '" + roleName + "'?")) return; + try { + const res = await fetch("/api/admin/roles/" + roleId, { method: "DELETE" }); + if (res.ok) { + showNotice("Role deleted successfully", false); + setTimeout(() => globalThis.location.reload(), 600); + } else { + const data = await res.json(); + showNotice(data.error || "Failed to delete role", true); + } + } catch (_err) { + showNotice("Network error deleting role", true); + } +} +globalThis.deleteRole = deleteRole; + +// Slice 2: Invites Handlers +function toggleCreateInviteForm() { + const drawer = document.getElementById("inviteDrawer"); + if (!drawer) return; + drawer.style.display = drawer.style.display === "none" ? "block" : "none"; +} +globalThis.toggleCreateInviteForm = toggleCreateInviteForm; + +async function handleCreateInvite(e) { + e.preventDefault(); + const app_id = document.getElementById("inviteAppSelect")?.value; + const role = document.getElementById("inviteRoleSelect")?.value || "user"; + const max_uses = document.getElementById("inviteMaxUsesInput")?.value; + + const payload = { + app_id: app_id || null, + role, + max_uses: Number(max_uses) || 1, + expires_in_days: 7, + }; + + try { + const res = await fetch("/api/admin/invites", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (res.ok) { + const data = await res.json(); + showNotice("Invite created: " + (data.invite?.code || "Success"), false); + setTimeout(() => globalThis.location.reload(), 600); + } else { + const data = await res.json(); + showNotice(data.error || "Failed to create invite", true); + } + } catch (_err) { + showNotice("Network error creating invite", true); + } +} +globalThis.handleCreateInvite = handleCreateInvite; + +async function revokeInvite(inviteId) { + if (!confirm("Revoke this invite token?")) return; + try { + const res = await fetch("/api/admin/invites/" + inviteId, { + method: "DELETE", + }); + if (res.ok) { + showNotice("Invite revoked successfully", false); + setTimeout(() => globalThis.location.reload(), 600); + } else { + const data = await res.json(); + showNotice(data.error || "Failed to revoke invite", true); + } + } catch (_err) { + showNotice("Network error revoking invite", true); + } +} +globalThis.revokeInvite = revokeInvite;