604 lines
29 KiB
HTML
604 lines
29 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Clearview | Permission Deviations</title>
|
||
<meta name="description" content="Clearview scans Microsoft 365 SharePoint sites and Exchange Online mailboxes for permission deviations.">
|
||
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="styles.css">
|
||
</head>
|
||
<body class="app-shell">
|
||
<div class="bg-orb orb-one" aria-hidden="true"></div>
|
||
<div class="bg-orb orb-two" aria-hidden="true"></div>
|
||
|
||
<aside class="sidebar">
|
||
<div class="sidebar-brand">
|
||
<img src="assets/clearview-logo-dark.svg" alt="Clearview" class="brand-logo">
|
||
</div>
|
||
<nav class="sidebar-nav">
|
||
<a href="#/dashboard" class="nav-link" data-route="dashboard">Dashboard</a>
|
||
<a href="#/jobs" class="nav-link" data-route="jobs">Scan Jobs</a>
|
||
|
||
<div class="nav-section">SharePoint</div>
|
||
<a href="#/scan/sharepoint" class="nav-link" data-route="scan-sharepoint">New SP Scan</a>
|
||
|
||
<div class="nav-section">Mailboxes</div>
|
||
<a href="#/scan/mailbox" class="nav-link" data-route="scan-mailbox">New Mailbox Scan</a>
|
||
|
||
<div class="nav-section">Entra</div>
|
||
<a href="#/scan/entra" class="nav-link" data-route="scan-entra">New Entra Scan</a>
|
||
|
||
<div class="nav-spacer"></div>
|
||
<a href="#/tenants" class="nav-link" data-route="tenants">Tenants</a>
|
||
<a href="#/settings" class="nav-link" data-route="settings">Settings</a>
|
||
</nav>
|
||
<div class="sidebar-foot">
|
||
<span class="sidebar-version" id="appVersion" title="Running Clearview build">…</span>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="content">
|
||
<header class="content-topbar">
|
||
<div class="content-title" id="contentTitle">Dashboard</div>
|
||
<div class="content-actions">
|
||
<button id="refreshJobsBtn" class="btn btn-outline" type="button">Refresh</button>
|
||
<div class="header-user" id="userBadge"></div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Dashboard -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="dashboard">
|
||
<div class="hero fade-up">
|
||
<p class="eyebrow">Permission Drift Detection</p>
|
||
<h1>Monitor Microsoft 365 permissions across all customers</h1>
|
||
<p class="lede">
|
||
Scan SharePoint sites for deviations from root permissions, and Exchange Online
|
||
mailboxes for delegated access (Full Access, Send As, Send on Behalf, folder delegations).
|
||
</p>
|
||
<div class="hero-stats">
|
||
<article>
|
||
<span class="kpi" id="statTenants">0</span>
|
||
<span class="label">Tenants</span>
|
||
</article>
|
||
<article>
|
||
<span class="kpi" id="statJobs">0</span>
|
||
<span class="label">Jobs</span>
|
||
</article>
|
||
<article>
|
||
<span class="kpi" id="statRunning">0</span>
|
||
<span class="label">Active Jobs</span>
|
||
</article>
|
||
<article>
|
||
<span class="kpi" id="statErrors">0</span>
|
||
<span class="label">With errors</span>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>Recent jobs</h2>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Job ID</th>
|
||
<th>Type</th>
|
||
<th>Tenant</th>
|
||
<th>Status</th>
|
||
<th>Targets</th>
|
||
<th>Updated</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="dashRecentJobs">
|
||
<tr><td colspan="6">No jobs yet.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Tenants -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="tenants" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>Tenants</h2>
|
||
<button id="addTenantBtn" class="btn btn-outline" type="button">Add Tenant</button>
|
||
</div>
|
||
|
||
<div id="addTenantForm" class="scan-form" hidden>
|
||
<h3>New Tenant</h3>
|
||
|
||
<div id="tenantSetupAutomated" class="setup-note" hidden>
|
||
<h3>Azure App Setup (automated)</h3>
|
||
<p>Connect to the customer's Microsoft tenant, then create a dedicated scan app automatically.</p>
|
||
<ul>
|
||
<li>Click <strong>Connect Microsoft</strong> and approve admin consent.</li>
|
||
<li>Created scan app receives SharePoint <code>Sites.FullControl.All</code> with admin consent.</li>
|
||
<li>For mailbox scanning, the <strong>Exchange.ManageAsApp</strong> permission and <strong>Exchange Administrator</strong> Entra role must be added manually after creation — see the <em>Enable mailbox scanning</em> section below.</li>
|
||
</ul>
|
||
<form id="onboardingForm" class="onboarding-form" action="#" method="post">
|
||
<div class="onboarding-grid">
|
||
<div class="onboarding-wide">
|
||
<button id="connectMicrosoftBtn" class="btn btn-outline" type="button">Connect Microsoft</button>
|
||
</div>
|
||
<label class="onboarding-wide">
|
||
Connected Tenant ID
|
||
<input id="connectedTenantId" type="text" placeholder="Connect first to populate tenant id">
|
||
</label>
|
||
<label class="onboarding-wide">
|
||
New Scan App Display Name
|
||
<input id="scanAppDisplayName" type="text" value="Clearview Scan App">
|
||
</label>
|
||
</div>
|
||
<button class="btn btn-outline" type="submit">Create Scan App Automatically</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="tenantSetupManual" class="setup-note" hidden>
|
||
<h3>Azure App Setup (manual)</h3>
|
||
<p>Create a dedicated Azure app registration in the customer's tenant.</p>
|
||
<ol class="setup-steps">
|
||
<li>Open <strong>Azure Portal</strong> → <strong>Entra ID → App registrations → New registration</strong>.</li>
|
||
<li>Pick a name (e.g. <em>Clearview Scan App</em>), select <strong>Single tenant</strong>, click <strong>Register</strong>.</li>
|
||
<li>Copy <strong>Directory (tenant) ID</strong> and <strong>Application (client) ID</strong>.</li>
|
||
<li>For SharePoint: <strong>API permissions → Add a permission → SharePoint → Application permissions</strong>, select <code>Sites.FullControl.All</code>, then click <strong>Grant admin consent</strong>.</li>
|
||
<li>For group resolution (recommended): also add <strong>Microsoft Graph → Application permissions → <code>Group.Read.All</code></strong> and grant admin consent. This lets Clearview expand Microsoft 365 / Azure AD security groups to their members and owners during the <em>Resolve groups</em> action. Without it, M365 group entries are kept as a single line.</li>
|
||
<li>The primary domain is the tenant's default Microsoft 365 domain — typically <code><tenantname>.onmicrosoft.com</code>. Find it in <strong>Microsoft 365 admin center → Settings → Domains</strong> (the <em>Default</em> entry).</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div id="tenantSetupMailbox" class="setup-note">
|
||
<h3>Enable mailbox scanning (Exchange Online)</h3>
|
||
<p>Mailbox scanning needs additional permissions on the scan app, on top of the SharePoint setup. Skip this section if the tenant only needs SharePoint scans.</p>
|
||
<ol class="setup-steps">
|
||
<li><strong>Add the API permission.</strong> Azure Portal → <strong>Entra ID → App registrations → [your scan app] → API permissions → Add a permission → APIs my organization uses</strong>. Search for <em>Office 365 Exchange Online</em>, choose <strong>Application permissions</strong> and tick <code>Exchange.ManageAsApp</code>. Click <strong>Add permissions</strong>.</li>
|
||
<li><strong>Grant admin consent.</strong> Still on the API permissions page, click <strong>Grant admin consent for <tenant></strong>. Verify the status column shows <em>Granted for <tenant></em>.</li>
|
||
<li><strong>Assign the Exchange Administrator role.</strong> Entra ID → <strong>Roles and administrators</strong> → search <em>Exchange Administrator</em> → click the role → <strong>Add assignments</strong> → search the scan app by name (you'll need to switch the picker to include <em>Service principals / Apps</em>) → select it and confirm. This role grants the app the right to read mailbox permissions; it cannot be granted via Microsoft Graph and must be done in the portal.</li>
|
||
<li><strong>Generate a certificate.</strong> Save the tenant first (this section's form), then use the <strong>Certificate</strong> button in the Tenants table to generate a self-signed RSA-2048 key. The public PEM appears in a panel — click <strong>Download .cer</strong>.</li>
|
||
<li><strong>Upload the certificate to Azure.</strong> Back in the scan app, go to <strong>Certificates & secrets → Certificates → Upload certificate</strong>, pick the downloaded <code>.cer</code> file, and confirm. Azure shows the SHA-1 thumbprint — it must match the one shown in the Tenants table.</li>
|
||
<li><strong>Fill in the Primary Domain field</strong> on the tenant form (e.g. <code>contoso.onmicrosoft.com</code>). Clearview uses this for <code>Connect-ExchangeOnline -Organization</code> and to auto-fill the Mailbox scan form.</li>
|
||
<li><strong>Test the connection.</strong> Run a <em>Scan all mailboxes</em> job for this tenant; preflight on the first target validates that authentication works end-to-end.</li>
|
||
</ol>
|
||
<p class="setup-hint">Exchange Online does <strong>not</strong> support client-secret app-only authentication. Mailbox scans require a certificate. The same certificate is reused for SharePoint scans, so generating it once is enough.</p>
|
||
</div>
|
||
|
||
<div class="auth-grid">
|
||
<label class="onboarding-wide">
|
||
Tenant Name (label for your reference)
|
||
<input id="newTenantName" type="text" placeholder="Contoso">
|
||
</label>
|
||
<label>
|
||
Tenant ID
|
||
<input id="newTenantTenantId" type="text" placeholder="00000000-0000-0000-0000-000000000000">
|
||
</label>
|
||
<label>
|
||
Primary Domain <span style="font-weight:400;font-size:0.82rem">(used by mailbox scanning, e.g. contoso.onmicrosoft.com)</span>
|
||
<input id="newTenantPrimaryDomain" type="text" placeholder="contoso.onmicrosoft.com">
|
||
</label>
|
||
<label>
|
||
Client ID
|
||
<input id="newTenantClientId" type="text" placeholder="00000000-0000-0000-0000-000000000000">
|
||
</label>
|
||
<label class="auth-secret">
|
||
Client Secret <span style="font-weight:400;font-size:0.82rem">(optional — not needed when using a certificate; not supported for mailbox scans)</span>
|
||
<input id="newTenantClientSecret" type="password" placeholder="Leave empty if you will generate a certificate">
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button id="saveTenantBtn" class="btn btn-solid" type="button">Save Tenant</button>
|
||
<button id="cancelTenantBtn" class="btn btn-outline" type="button">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>Tenant ID</th>
|
||
<th>Client ID</th>
|
||
<th>Auth</th>
|
||
<th>Added</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tenantsTableBody">
|
||
<tr><td colspan="6">No tenants configured yet.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="tenantFeedback" class="feedback" aria-live="polite"></div>
|
||
|
||
<div id="certBlock" class="cert-block" hidden>
|
||
<h3>Public Certificate</h3>
|
||
<p>Upload this certificate in <strong>Azure Portal → App registrations → [your app] → Certificates & secrets → Certificates → Upload certificate</strong>.</p>
|
||
<textarea id="certPem" class="cert-pem" rows="10" readonly></textarea>
|
||
<div class="form-actions">
|
||
<button id="downloadCertBtn" class="btn btn-solid" type="button">Download .cer</button>
|
||
<button id="copyCertBtn" class="btn btn-outline" type="button">Copy to clipboard</button>
|
||
<button id="closeCertBtn" class="btn btn-outline" type="button">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Scan SharePoint -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="scan-sharepoint" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>New SharePoint Scan</h2>
|
||
<span class="badge">SharePoint</span>
|
||
</div>
|
||
|
||
<div class="scan-form auth-block">
|
||
<h3>Scan mode</h3>
|
||
<label>
|
||
What to collect
|
||
<select id="sharepointScanMode">
|
||
<option value="sharepoint">Deviations from root (libraries, folders, files)</option>
|
||
<option value="sharepoint_root">Root permissions only (site-level role assignments)</option>
|
||
</select>
|
||
</label>
|
||
<p class="setup-hint">
|
||
<strong>Deviations from root</strong> traverses every document library and reports only permissions that
|
||
differ from the site root baseline. <strong>Root permissions only</strong> lists the role assignments
|
||
on the site root itself — much faster, useful for an inventory of who has site-level access.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="scan-form auth-block">
|
||
<h3>Tenant</h3>
|
||
<label>
|
||
Select Tenant Profile
|
||
<select id="scanTenantSelect" data-shared-tenant-select>
|
||
<option value="">-- Select a tenant --</option>
|
||
<option value="__manual__">Manual credentials...</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<div id="manualCredentialsBlock" class="scan-form auth-block" hidden>
|
||
<h3>Microsoft App Credentials</h3>
|
||
<div class="auth-grid">
|
||
<label>
|
||
Tenant ID
|
||
<input id="tenantId" type="text" placeholder="00000000-0000-0000-0000-000000000000">
|
||
</label>
|
||
<label>
|
||
Client ID
|
||
<input id="clientId" type="text" placeholder="00000000-0000-0000-0000-000000000000">
|
||
</label>
|
||
<label class="auth-secret">
|
||
Client Secret
|
||
<input id="clientSecret" type="password" placeholder="Client secret">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<form id="manualScanForm" class="scan-form" action="#" method="post">
|
||
<h3>Manual URLs</h3>
|
||
<label>
|
||
Site URLs (one per line)
|
||
<textarea id="manualUrls" rows="6" placeholder="https://contoso.sharepoint.com/sites/finance https://contoso.sharepoint.com/sites/hr"></textarea>
|
||
</label>
|
||
<label class="checkline">
|
||
<input id="manualSkipDefaults" type="checkbox" checked>
|
||
<span>Skip default sites (tenant root, app catalog)</span>
|
||
</label>
|
||
<button class="btn btn-solid" type="submit">Queue manual scan</button>
|
||
</form>
|
||
|
||
<form id="csvScanForm" class="scan-form" action="#" method="post" enctype="multipart/form-data">
|
||
<h3>CSV Import</h3>
|
||
<label>
|
||
Microsoft Sites export (CSV)
|
||
<input id="csvFile" type="file" accept=".csv,text/csv">
|
||
</label>
|
||
<label class="checkline">
|
||
<input id="csvSkipDefaults" type="checkbox" checked>
|
||
<span>Skip default sites (tenant root, app catalog)</span>
|
||
</label>
|
||
<button class="btn btn-solid" type="submit">Queue CSV scan</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="submitFeedback" class="feedback" aria-live="polite"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Scan Mailbox -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="scan-mailbox" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>New Mailbox Scan</h2>
|
||
<span class="badge">Exchange Online</span>
|
||
</div>
|
||
|
||
<div class="scan-form auth-block">
|
||
<h3>Tenant</h3>
|
||
<label>
|
||
Select Tenant Profile
|
||
<select id="mailboxScanTenantSelect" data-shared-tenant-select>
|
||
<option value="">-- Select a tenant --</option>
|
||
</select>
|
||
</label>
|
||
<p class="setup-hint">
|
||
Mailbox scanning requires a certificate on the tenant profile and the
|
||
<code>Exchange.ManageAsApp</code> permission with the Exchange Administrator role.
|
||
Client-secret authentication is not supported for Exchange Online.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<form id="manualMailboxForm" class="scan-form" action="#" method="post">
|
||
<h3>Manual UPNs</h3>
|
||
<label>
|
||
User Principal Names (one per line)
|
||
<textarea id="manualMailboxes" rows="6" placeholder="alice@contoso.com bob@contoso.com"></textarea>
|
||
</label>
|
||
<button class="btn btn-solid" type="submit">Queue mailbox scan</button>
|
||
</form>
|
||
|
||
<form id="csvMailboxForm" class="scan-form" action="#" method="post" enctype="multipart/form-data">
|
||
<h3>CSV Import</h3>
|
||
<label>
|
||
CSV with <code>UserPrincipalName</code> / <code>Email</code> column
|
||
<input id="csvMailboxFile" type="file" accept=".csv,text/csv">
|
||
</label>
|
||
<button class="btn btn-solid" type="submit">Queue CSV scan</button>
|
||
</form>
|
||
|
||
<form id="allMailboxesForm" class="scan-form" action="#" method="post">
|
||
<h3>All mailboxes in tenant</h3>
|
||
<label>
|
||
Organization (primary tenant domain)
|
||
<input id="allMailboxesOrg" type="text" placeholder="contoso.onmicrosoft.com">
|
||
</label>
|
||
<p class="setup-hint">
|
||
Clearview enumerates every mailbox in the tenant via <code>Get-EXOMailbox -ResultSize Unlimited</code>
|
||
and queues one target per mailbox. Can take 10–60 seconds for large tenants.
|
||
</p>
|
||
<button class="btn btn-solid" type="submit">Queue scan for all mailboxes</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="mailboxSubmitFeedback" class="feedback" aria-live="polite"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Scan Entra Groups -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="scan-entra" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>New Entra Group Scan</h2>
|
||
<span class="badge">Microsoft Graph</span>
|
||
</div>
|
||
|
||
<div class="scan-form auth-block">
|
||
<h3>Tenant</h3>
|
||
<label>
|
||
Select Tenant Profile
|
||
<select id="entraScanTenantSelect">
|
||
<option value="">-- Select a tenant --</option>
|
||
</select>
|
||
</label>
|
||
<p class="setup-hint">
|
||
Entra group scans use the <strong>Microsoft Graph</strong> API. The scan app needs the
|
||
Application permission <code>Group.Read.All</code> with admin consent. Authentication
|
||
uses the same tenant certificate as SharePoint and Mailbox scans.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<form id="manualEntraForm" class="scan-form" action="#" method="post">
|
||
<h3>Manual Object IDs</h3>
|
||
<label>
|
||
Group identifiers (one per line — Object ID, mail address, or display name)
|
||
<textarea id="manualEntraIds" rows="6" placeholder="00000000-0000-0000-0000-000000000000 Pharmacology@contoso.onmicrosoft.com"></textarea>
|
||
</label>
|
||
<button class="btn btn-solid" type="submit">Queue Entra scan</button>
|
||
</form>
|
||
|
||
<form id="csvEntraForm" class="scan-form" action="#" method="post" enctype="multipart/form-data">
|
||
<h3>CSV Import (Entra export)</h3>
|
||
<label>
|
||
CSV with <code>Object ID</code> column (Entra "Groups" export)
|
||
<input id="csvEntraFile" type="file" accept=".csv,text/csv">
|
||
</label>
|
||
<p class="setup-hint">
|
||
Export from Entra portal → Groups → All groups → Download. Clearview reads the
|
||
<code>Object ID</code> / <code>id</code> column; other columns are ignored.
|
||
</p>
|
||
<button class="btn btn-solid" type="submit">Queue CSV scan</button>
|
||
</form>
|
||
|
||
<form id="allEntraForm" class="scan-form" action="#" method="post">
|
||
<h3>All groups in tenant</h3>
|
||
<p class="setup-hint">
|
||
Enumerates every group in the tenant (any type) via Microsoft Graph and queues one
|
||
target per group. Can take 30–120 seconds for large tenants.
|
||
</p>
|
||
<button class="btn btn-solid" type="submit">Queue scan for all groups</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="entraSubmitFeedback" class="feedback" aria-live="polite"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Jobs (list + selected job details) -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="jobs" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>Scan Jobs</h2>
|
||
<div class="panel-header-right">
|
||
<select id="jobTypeFilter" class="filter-select">
|
||
<option value="">All types</option>
|
||
<option value="sharepoint">SharePoint deviations</option>
|
||
<option value="sharepoint_root">SharePoint root</option>
|
||
<option value="mailbox">Mailbox</option>
|
||
<option value="entra_groups">Entra groups</option>
|
||
</select>
|
||
<select id="jobTenantFilter" class="filter-select">
|
||
<option value="">All tenants</option>
|
||
</select>
|
||
<span id="jobAutoRefresh" class="badge">Auto refresh: on</span>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Job ID</th>
|
||
<th>Type</th>
|
||
<th>Tenant</th>
|
||
<th>Source</th>
|
||
<th>Status</th>
|
||
<th>Targets</th>
|
||
<th>Items</th>
|
||
<th>Updated</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="jobsTableBody">
|
||
<tr><td colspan="9">No jobs yet.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>Selected Job Details</h2>
|
||
<div class="panel-header-right">
|
||
<select id="jobSiteFilter" class="filter-select">
|
||
<option value="">All targets</option>
|
||
</select>
|
||
<button id="exportJobBtn" class="btn btn-outline" type="button" hidden>Export Excel</button>
|
||
<span id="selectedJobId" class="badge">No selection</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="jobSummary" class="job-summary">Select a job to inspect targets and deviations.</div>
|
||
<div id="jobActivity" class="job-activity" hidden></div>
|
||
|
||
<h3 class="subheading" id="targetsHeading">Targets</h3>
|
||
<div class="table-wrap compact-wrap">
|
||
<table>
|
||
<thead id="targetsTableHead">
|
||
<tr>
|
||
<th>URL</th>
|
||
<th>Status</th>
|
||
<th>Attempts</th>
|
||
<th>Error</th>
|
||
<th>Connection test</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="targetsTableBody">
|
||
<tr><td colspan="6">No job selected.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="sharingLinksResolveBlock" hidden>
|
||
<h3 class="subheading">Resolve Sharing Links</h3>
|
||
<p class="resolve-hint">Fetch the actual recipients for the selected link types. Anonymous links have no resolvable members.</p>
|
||
<div id="sharingLinksTypes" class="sharing-link-types"></div>
|
||
<div class="form-actions" style="margin-top:0.6rem">
|
||
<button id="resolveSharingLinksBtn" class="btn btn-outline" type="button">Resolve</button>
|
||
</div>
|
||
<div id="resolveFeedback" class="feedback" aria-live="polite"></div>
|
||
</div>
|
||
|
||
<div id="resolveGroupsBlock" hidden>
|
||
<h3 class="subheading">Resolve SharePoint Groups</h3>
|
||
<p class="resolve-hint">
|
||
Expand SharePoint groups (Owners / Members / Visitors / custom site groups) to the underlying
|
||
user list. When a member is itself a Microsoft 365 / Azure AD group, Clearview recursively
|
||
expands it via Microsoft Graph (members + owners, depth 3) — requires
|
||
<code>Group.Read.All</code> on Microsoft Graph for that tenant. Without that permission the
|
||
M365 group lines stay collapsed. Members are written to the deviation rows and Excel export.
|
||
</p>
|
||
<div class="form-actions" style="margin-top:0.6rem">
|
||
<button id="resolveGroupsBtn" class="btn btn-outline" type="button">Resolve groups</button>
|
||
</div>
|
||
<div id="resolveGroupsFeedback" class="feedback" aria-live="polite"></div>
|
||
</div>
|
||
|
||
<h3 class="subheading">Permission Deviations</h3>
|
||
<div class="table-wrap deviations-wrap">
|
||
<table>
|
||
<thead id="deviationsTableHead">
|
||
<tr>
|
||
<th>Site</th>
|
||
<th>Object</th>
|
||
<th>Type</th>
|
||
<th>Principal</th>
|
||
<th>Role</th>
|
||
<th>Delta</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="deviationsTableBody">
|
||
<tr><td colspan="6">No deviation data yet.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- =================================================================== -->
|
||
<!-- Route: Settings (placeholder) -->
|
||
<!-- =================================================================== -->
|
||
<section class="route-page" data-route-page="settings" hidden>
|
||
<div class="panel">
|
||
<div class="panel-header split">
|
||
<h2>Settings</h2>
|
||
</div>
|
||
<nav class="settings-tabs" role="tablist">
|
||
<a href="#/settings/general" class="settings-tab" data-settings-tab="general" role="tab">General</a>
|
||
<a href="#/settings/users" class="settings-tab" data-settings-tab="users" data-admin-only role="tab">Users</a>
|
||
<a href="#/settings/audit" class="settings-tab" data-settings-tab="audit" data-admin-only role="tab">Audit log</a>
|
||
</nav>
|
||
|
||
<div class="settings-pane" data-settings-pane="general">
|
||
<p class="setup-hint">Runtime configuration is currently controlled via environment variables in <code>stack/.env</code>. See the <strong>TECHNICAL.md</strong> document for the full list (timeouts, retries, scan caps, onboarding).</p>
|
||
</div>
|
||
|
||
<div class="settings-pane" data-settings-pane="users" hidden>
|
||
<div class="users-toolbar"><button id="newUserBtn" class="btn btn-primary" type="button">New user</button></div>
|
||
<div id="usersTable"></div>
|
||
</div>
|
||
|
||
<div class="settings-pane" data-settings-pane="audit" hidden>
|
||
<div id="auditTable"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|