From 67c6891f23791540f418ab153382771415c4d839 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 20:11:55 -0500 Subject: [PATCH] Add quick actions to Domain Health for user/domain management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "Add to Org" button for misaligned users (Users Who Should Join Their Company) - Modified misaligned_users query to include target_org_id and target_org_name - Shows which org the user should be added to based on their email domain - One-click to add all users from a domain to the company org - Add "Link Domain" button for Organizations Without Verified Domains - Suggests first corporate domain found from user emails - Filters out personal email domains (gmail, yahoo, etc.) - One-click to link domain to the organization - New API endpoint: POST /api/admin/organizations/:orgId/add-users - Accepts array of user IDs - Validates org exists and is not personal workspace - Adds membership records for users - Includes input validation and rate limiting (max 100 users) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/funny-sheep-remain.md | 2 + server/public/admin-domain-health.html | 111 ++++++++++++++++++- server/src/routes/admin/domains.ts | 18 ++- server/src/routes/admin/organizations.ts | 133 +++++++++++++++++++++++ 4 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 .changeset/funny-sheep-remain.md diff --git a/.changeset/funny-sheep-remain.md b/.changeset/funny-sheep-remain.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/funny-sheep-remain.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/public/admin-domain-health.html b/server/public/admin-domain-health.html index ddaa49d77d..6e89af7ddf 100644 --- a/server/public/admin-domain-health.html +++ b/server/public/admin-domain-health.html @@ -636,6 +636,64 @@

Domain Health

} } + async function addUsersToOrg(button, orgId, orgName, userIds) { + // Validate inputs + if (!isValidOrgId(orgId)) { + alert('Invalid organization ID format'); + return; + } + + if (!confirm(`Add ${userIds.length} user(s) to "${orgName}"?\n\nThis will add them to the company organization. They will keep access to their personal workspace.`)) { + return; + } + + const originalText = button.textContent; + button.disabled = true; + button.textContent = 'Adding...'; + + try { + const response = await fetch(`/api/admin/organizations/${encodeURIComponent(orgId)}/add-users`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ user_ids: userIds }) + }); + + if (!response.ok) { + if (response.status === 401) { + window.AdminSidebar?.redirectToLogin(); + return; + } + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || errorData.error || 'Failed to add users'); + } + + const result = await response.json(); + + // Success + button.classList.remove('btn-primary'); + button.classList.add('btn-success'); + button.textContent = `Added ${result.added_count || userIds.length}!`; + + // Reload after brief delay + setTimeout(() => { + loadDomainHealth(); + }, 1500); + } catch (error) { + button.classList.remove('btn-primary'); + button.classList.add('btn-error'); + button.textContent = 'Failed'; + button.title = error.message; + + setTimeout(() => { + button.disabled = false; + button.classList.remove('btn-error'); + button.classList.add('btn-primary'); + button.textContent = originalText; + button.title = ''; + }, 3000); + } + } + function renderSummary() { const { summary } = healthData; const totalIssues = summary.issues.orphan_domains + summary.issues.misaligned_users + @@ -795,7 +853,10 @@

Domain Health

${misaligned_users.map(d => ` - ${escapeHtml(d.domain)} + + ${escapeHtml(d.domain)} + ${d.target_org_name ? `
→ ${escapeHtml(d.target_org_name)}` : ''} + ${d.user_count}