Files
claudetools/.claude/skills/remediation-tool/references/tenant-consent.html

198 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ComputerGuru — Tenant Admin Consent</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f1117; color: #e2e8f0; min-height: 100vh; padding: 32px 24px; }
h1 { font-size: 1.4rem; font-weight: 600; color: #f8fafc; margin-bottom: 4px; }
.subtitle { font-size: 0.85rem; color: #64748b; margin-bottom: 28px; }
.section-label { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: #475569; margin-bottom: 10px; margin-top: 24px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 8px; }
.card { background: #1e2330; border: 1px solid #2d3548; border-radius: 8px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; transition: border-color 0.15s; }
.card:hover { border-color: #3b82f6; }
.card.done { border-color: #166534; background: #14281f; opacity: 0.7; }
.card.reconsent { border-color: #92400e; background: #1c1a0f; }
.tenant-info { min-width: 0; }
.tenant-name { font-size: 0.9rem; font-weight: 500; color: #e2e8f0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tenant-domain { font-size: 0.75rem; color: #64748b; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.btn { flex-shrink: 0; font-size: 0.78rem; font-weight: 500; padding: 6px 14px; border-radius: 5px; border: none; cursor: pointer; text-decoration: none; display: inline-block; white-space: nowrap; transition: background 0.15s; }
.btn-primary { background: #2563eb; color: #fff; }
.btn-primary:hover { background: #1d4ed8; }
.btn-warn { background: #92400e; color: #fef3c7; }
.btn-warn:hover { background: #b45309; }
.btn-done { background: #166534; color: #bbf7d0; cursor: default; }
.badge { font-size: 0.65rem; font-weight: 600; padding: 2px 7px; border-radius: 3px; display: inline-block; margin-top: 3px; }
.badge-reconsent { background: #451a03; color: #fbbf24; }
.badge-done { background: #052e16; color: #4ade80; }
.stats { display: flex; gap: 20px; margin-bottom: 24px; padding: 16px; background: #1e2330; border: 1px solid #2d3548; border-radius: 8px; }
.stat-val { font-size: 1.4rem; font-weight: 700; }
.stat-label { font-size: 0.72rem; color: #64748b; margin-top: 1px; }
.stat-pending .stat-val { color: #f59e0b; }
.stat-done .stat-val { color: #4ade80; }
.stat-reconsent .stat-val { color: #fb923c; }
.instructions { background: #1e2330; border: 1px solid #2d3548; border-left: 3px solid #3b82f6; border-radius: 8px; padding: 14px 16px; margin-bottom: 24px; font-size: 0.82rem; color: #94a3b8; line-height: 1.6; }
.instructions strong { color: #e2e8f0; }
code { background: #0f1117; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 0.8em; color: #93c5fd; }
</style>
</head>
<body>
<h1>ComputerGuru — Tenant Admin Consent</h1>
<p class="subtitle">Send the consent URL to each customer's Global Admin. After they accept, run <code>onboard-tenant.sh &lt;domain&gt;</code>.</p>
<div class="instructions">
<strong>One-click onboarding flow:</strong> Customer Global Admin clicks their consent link below → logs in → clicks Accept.
Then run: <code>bash scripts/onboard-tenant.sh &lt;domain&gt;</code> — the script will automatically consent all other apps and assign all directory roles.
<br><br>
<strong>Re-consent</strong> (orange) means Tenant Admin was previously consented but needs a refresh to pick up <code>AppRoleAssignment.ReadWrite.All</code>.
</div>
<div class="stats">
<div class="stat stat-pending"><div class="stat-val" id="cnt-pending"></div><div class="stat-label">Pending consent</div></div>
<div class="stat stat-reconsent"><div class="stat-val" id="cnt-reconsent"></div><div class="stat-label">Need re-consent</div></div>
<div class="stat stat-done"><div class="stat-val" id="cnt-done"></div><div class="stat-label">Done</div></div>
</div>
<div class="section-label">Re-consent required</div>
<div class="grid" id="grid-reconsent"></div>
<div class="section-label">Pending initial consent</div>
<div class="grid" id="grid-pending"></div>
<div class="section-label" id="label-done" style="display:none">Fully onboarded</div>
<div class="grid" id="grid-done"></div>
<script>
const BASE = "https://login.microsoftonline.com";
const CLIENT = "709e6eed-0711-4875-9c44-2d3518c47063";
const REDIRECT = "https://azcomputerguru.com";
const TENANTS = [
// status: "done" | "reconsent" | "pending"
{ name: "Marty Ryan", domain: "martylryan.com", id: "48581923-2153-48b9-82b3-6a3587813041", status: "done" },
{ name: "Grabblaw", domain: "grabblaw.com", id: "032b383e-96e4-491b-880d-3fd3295672c3", status: "done" },
{ name: "Andy's Mobile Fuel", domain: "andysmobilefuel.com", id: "806d4728-4545-495e-9eba-f0f96584ea08", status: "done" },
{ name: "Bill Tedards", domain: "tedards.net", id: "4fcbb1f4-fbf9-4548-a93e-7d14a3c091e6", status: "done" },
{ name: "Brian Kahn", domain: "lIGQB0q47JGi8MGBPBAmzBfDHdf.onmicrosoft.com", id: "f5f86b40-4345-406e-94a3-470376d7590b", status: "pending" },
{ name: "cascadestucson.com", domain: "cascadestucson.com", id: "207fa277-e9d8-4eb7-ada1-1064d2221498", status: "done" },
{ name: "cclac.net", domain: "cclac.net", id: "e8a0fafc-21ee-41e8-a5ba-f3a250a8a30e", status: "done" },
{ name: "Cobalt Fine Arts", domain: "cobaltfinearts.com", id: "03c4d4ec-b6d3-4061-a75c-8a4250ba2b29", status: "done" },
{ name: "CUADRO LLC", domain: "cuadro.design", id: "b68c7171-31d6-4b63-8243-7a2cade9caf8", status: "pending" },
{ name: "Curtis Plumbing", domain: "cparizona.onmicrosoft.com", id: "d2d7ea54-9146-42d1-b99e-0da098550bde", status: "pending" },
{ name: "cwconcretellc.com", domain: "NETORGFT11452752.onmicrosoft.com", id: "dfee2224-93cd-4291-9b09-6c6ce9bb8711", status: "pending" },
{ name: "Dataforth Corporation", domain: "dataforth.com", id: "7dfa3ce8-c496-4b51-ab8d-bd3dcd78b584", status: "done" },
{ name: "Feline Ltd Cat Clinic", domain: "felineltd.onmicrosoft.com", id: "1b5f38ef-b6c8-4b6d-9bfb-9250ea7e7994", status: "pending" },
{ name: "Glaz-Tech Industries", domain: "glaztech.com", id: "82931e3c-de7a-4f74-87f7-fe714be1f160", status: "done" },
{ name: "Heieck Sheila", domain: "heieck.org", id: "7462ce7e-071e-49da-88ec-50ec6b46d12e", status: "done" },
{ name: "ICE INC", domain: "iceinc.us.com", id: "ff26952e-970d-4c02-9179-416ed931ec50", status: "pending" },
{ name: "Instrumental Music Ctr", domain: "instrumentalmusic.onmicrosoft.com", id: "65adab75-f1fd-4ef9-b2b4-c24f595393e3", status: "pending" },
{ name: "Jema Enterprises, LLC", domain: "jemaenterprises.com", id: "41268042-9a8e-41c2-9a3c-0775398b86cb", status: "done" },
{ name: "JR Kennedy Company", domain: "jrkco.com", id: "a92594b9-c8ad-4dba-8b40-14fcd32c723c", status: "pending" },
{ name: "Khalsa Montessori", domain: "khalsamontessorischools.onmicrosoft.com", id: "b2950f9d-81f8-40e4-85d9-2854d1d4f31b", status: "pending" },
{ name: "Kittle Design & Const.", domain: "kittlearizona.com", id: "3d073ebe-806a-4a5e-9035-3c7c4a264fc0", status: "pending" },
{ name: "LeeAnn Parkinson", domain: "lamaddux.com", id: "2f0c4c92-c608-4ee0-bdc2-87d5fd8fe929", status: "pending" },
{ name: "MVAN Enterprises", domain: "mvan.onmicrosoft.com", id: "5affaf1e-de89-416b-a655-1b2cf615d5b1", status: "done" },
{ name: "Patient Care Advocates", domain: "pcatucson.com", id: "463b462d-0995-4e51-9e41-82c208015c7f", status: "pending" },
{ name: "Peaceful Spirit Massage", domain: "bestmassageintucson.com", id: "13be285a-374d-4a7c-a7d8-4cb5a98b5c29", status: "done" },
{ name: "Putt Land Surveying", domain: "puttsurveying.com", id: "25008634-91b4-40aa-8113-78ea03826156", status: "pending" },
{ name: "Rednour Law", domain: "rednourlaw.com", id: "4a4ca18a-f516-478b-99da-2e0722c5dc18", status: "done" },
{ name: "Reliant Well Drilling", domain: "reliantpump.services", id: "2b124552-3891-4090-b3ed-2eebad3c4083", status: "done" },
{ name: "Ridgetop Group", domain: "ridgetopgroup.com", id: "ef111bfc-9c90-43c9-a581-f9bbfceb6517", status: "done" },
{ name: "Rincon Vista Vet Ctr", domain: "rinconvistavet.onmicrosoft.com", id: "b8cdcd89-d0f4-4747-bcf3-8bd8a25fd7e1", status: "pending" },
{ name: "Russo Law Firm", domain: "rrs-law.com", id: "bef1b190-f78f-4b1c-aa4b-fab186a30702", status: "pending" },
{ name: "Safe Site Utility Svcs", domain: "safesitellc.com", id: "71b4e637-c802-4137-a812-ae50dbc839e3", status: "done" },
{ name: "SANDTEKO MACHINERY", domain: "SANDTEKOMACHINERY.com", id: "739bb777-cf76-478f-866b-f61c830c8246", status: "done" },
{ name: "Shave, Kevin", domain: "az2son.com", id: "984c05a9-708b-4ec1-9f43-558865cb3c9d", status: "pending" },
{ name: "Sonorangreenllc.com", domain: "sonorangreenllc.com", id: "ededa4fb-f6eb-4398-851d-5eb3e11fab27", status: "done" },
{ name: "Starr Pass Realty", domain: "starrpass.com", id: "222450dd-141f-435f-87b8-cec719aac99e", status: "pending" },
{ name: "The Dumpster Guys", domain: "dumpsterguys.onmicrosoft.com", id: "0b3cd451-2679-4697-b161-07b9ef8d41e9", status: "pending" },
{ name: "The Prairie Schooner", domain: "theprairieschooner.onmicrosoft.com", id: "c941033c-2752-42ef-be22-fbab77e2e587", status: "pending" },
{ name: "Tucson Golden Corral", domain: "tucsongoldencorral.onmicrosoft.com", id: "50e23e94-960f-4f61-8a27-97dbbe001a36", status: "pending" },
{ name: "Tucson Mountain Motors", domain: "tucsonmountainmotors.com", id: "ffdabd05-236b-4666-a7f5-cc40ae9f9122", status: "pending" },
{ name: "Valley Wide Plastering", domain: "valleywideplastering.com", id: "5c53ae9f-7071-4248-b834-8685b646450f", status: "done" },
{ name: "Von's Carstar", domain: "vonscarstar.com", id: "53de51b9-a063-4f46-88ff-7c3468828ed9", status: "pending" },
];
// Load done state from localStorage
const DONE_KEY = "cg_consent_done";
const done = new Set(JSON.parse(localStorage.getItem(DONE_KEY) || "[]"));
function consentUrl(id) {
return `${BASE}/${id}/adminconsent?client_id=${CLIENT}&redirect_uri=${REDIRECT}&prompt=consent`;
}
function markDone(id, btn, card) {
done.add(id);
localStorage.setItem(DONE_KEY, JSON.stringify([...done]));
btn.textContent = "Done";
btn.className = "btn btn-done";
btn.onclick = null;
card.className = "card done";
updateCounts();
}
function renderCard(t) {
const isDone = done.has(t.id);
const card = document.createElement("div");
card.className = "card" + (isDone ? " done" : t.status === "reconsent" ? " reconsent" : "");
const info = document.createElement("div");
info.className = "tenant-info";
info.innerHTML = `<div class="tenant-name">${t.name}</div><div class="tenant-domain">${t.domain}</div>` +
(t.status === "reconsent" && !isDone ? `<span class="badge badge-reconsent">Re-consent</span>` : "") +
(isDone ? `<span class="badge badge-done">Done</span>` : "");
const btn = document.createElement("a");
btn.href = consentUrl(t.id);
btn.target = "_blank";
btn.rel = "noopener";
if (isDone) {
btn.textContent = "Done";
btn.className = "btn btn-done";
btn.onclick = (e) => e.preventDefault();
} else if (t.status === "reconsent") {
btn.textContent = "Re-consent";
btn.className = "btn btn-warn";
btn.onclick = () => setTimeout(() => markDone(t.id, btn, card), 500);
} else {
btn.textContent = "Consent";
btn.className = "btn btn-primary";
btn.onclick = () => setTimeout(() => markDone(t.id, btn, card), 500);
}
card.appendChild(info);
card.appendChild(btn);
return { card, isDone };
}
function updateCounts() {
const total = TENANTS.length;
const doneCount = TENANTS.filter(t => done.has(t.id)).length;
const reconsent = TENANTS.filter(t => t.status === "reconsent" && !done.has(t.id)).length;
const pending = TENANTS.filter(t => t.status === "pending" && !done.has(t.id)).length;
document.getElementById("cnt-pending").textContent = pending;
document.getElementById("cnt-reconsent").textContent = reconsent;
document.getElementById("cnt-done").textContent = doneCount;
const labelDone = document.getElementById("label-done");
labelDone.style.display = doneCount > 0 ? "" : "none";
}
function render() {
const grids = { pending: document.getElementById("grid-pending"), reconsent: document.getElementById("grid-reconsent"), done: document.getElementById("grid-done") };
TENANTS.forEach(t => {
const { card, isDone } = renderCard(t);
if (isDone) grids.done.appendChild(card);
else grids[t.status].appendChild(card);
});
updateCounts();
}
render();
</script>
</body>
</html>