Add bulk enable invite access to user directory
Magent CI/CD / verify (push) Successful in 10m27s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Skipped

This commit is contained in:
2026-09-07 16:19:12 +12:00
parent 4034a8f72a
commit edca300d27
+40 -1
View File
@@ -15,6 +15,7 @@ type AdminUser = {
lastLoginAt?: string | null lastLoginAt?: string | null
isBlocked?: boolean isBlocked?: boolean
autoSearchEnabled?: boolean autoSearchEnabled?: boolean
inviteManagementEnabled?: boolean
profileId?: number | null profileId?: number | null
expiresAt?: string | null expiresAt?: string | null
isExpired?: boolean isExpired?: boolean
@@ -88,6 +89,8 @@ export default function UsersPage() {
const [jellyseerrSyncBusy, setJellyseerrSyncBusy] = useState(false) const [jellyseerrSyncBusy, setJellyseerrSyncBusy] = useState(false)
const [jellyseerrResyncBusy, setJellyseerrResyncBusy] = useState(false) const [jellyseerrResyncBusy, setJellyseerrResyncBusy] = useState(false)
const [bulkAutoSearchBusy, setBulkAutoSearchBusy] = useState(false) const [bulkAutoSearchBusy, setBulkAutoSearchBusy] = useState(false)
const [bulkInvitesBusy, setBulkInvitesBusy] = useState(false)
const [inviteStatus, setInviteStatus] = useState<string | null>(null)
const loadUsers = async () => { const loadUsers = async () => {
try { try {
@@ -116,6 +119,7 @@ export default function UsersPage() {
lastLoginAt: user.last_login_at ?? null, lastLoginAt: user.last_login_at ?? null,
isBlocked: Boolean(user.is_blocked), isBlocked: Boolean(user.is_blocked),
autoSearchEnabled: Boolean(user.auto_search_enabled ?? true), autoSearchEnabled: Boolean(user.auto_search_enabled ?? true),
inviteManagementEnabled: Boolean(user.invite_management_enabled),
profileId: profileId:
user.profile_id == null || Number.isNaN(Number(user.profile_id)) user.profile_id == null || Number.isNaN(Number(user.profile_id))
? null ? null
@@ -192,6 +196,25 @@ export default function UsersPage() {
} }
} }
const enableInvitesForEveryone = async () => {
if (bulkInvitesBusy || !window.confirm('Enable invite access for all existing non-admin users, including users outside the current search? Existing invite limits, account blocks and expiry dates will not change.')) return
setBulkInvitesBusy(true)
setInviteStatus(null)
try {
const response = await authFetch(`${getApiBase()}/admin/users/invite-access/bulk`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: true }),
})
if (!response.ok) throw new Error('Invite access update failed')
const data = await response.json()
setInviteStatus(`Invite access enabled for ${data.updated ?? 0} non-admin accounts. Existing limits are unchanged.`)
await loadUsers()
} catch {
setInviteStatus('Could not enable invites. Please reload the list to check the current permissions, then try again.')
} finally { setBulkInvitesBusy(false) }
}
const bulkUpdateAutoSearch = async (enabled: boolean) => { const bulkUpdateAutoSearch = async (enabled: boolean) => {
setBulkAutoSearchBusy(true) setBulkAutoSearchBusy(true)
setJellyseerrSyncStatus(null) setJellyseerrSyncStatus(null)
@@ -233,6 +256,7 @@ export default function UsersPage() {
const nonAdminUsers = users.filter((user) => user.role !== 'admin') const nonAdminUsers = users.filter((user) => user.role !== 'admin')
const autoSearchEnabledCount = nonAdminUsers.filter((user) => user.autoSearchEnabled !== false).length const autoSearchEnabledCount = nonAdminUsers.filter((user) => user.autoSearchEnabled !== false).length
const inviteEnabledCount = nonAdminUsers.filter((user) => user.inviteManagementEnabled).length
const blockedCount = users.filter((user) => user.isBlocked).length const blockedCount = users.filter((user) => user.isBlocked).length
const expiredCount = users.filter((user) => user.isExpired).length const expiredCount = users.filter((user) => user.isExpired).length
const adminCount = users.filter((user) => user.role === 'admin').length const adminCount = users.filter((user) => user.role === 'admin').length
@@ -348,7 +372,7 @@ export default function UsersPage() {
<div> <div>
<h2>Bulk controls</h2> <h2>Bulk controls</h2>
<p className="lede"> <p className="lede">
Auto search/download can be enabled or disabled for all non-admin users. Manage permissions for all existing non-admin users, not just the current search results.
</p> </p>
</div> </div>
</div> </div>
@@ -378,6 +402,21 @@ export default function UsersPage() {
</div> </div>
</div> </div>
</div> </div>
<div className="admin-panel user-directory-bulk-panel">
<div className="user-bulk-toolbar">
<div className="user-bulk-summary">
<strong>Invite access</strong>
<span>{inviteEnabledCount} of {nonAdminUsers.length} non-admin users enabled</span>
<span>Admins already have access. Existing invite limits, blocked accounts and expiry dates stay unchanged.</span>
</div>
<div className="user-bulk-actions">
<button type="button" onClick={enableInvitesForEveryone} disabled={bulkInvitesBusy || nonAdminUsers.length === 0 || inviteEnabledCount === nonAdminUsers.length}>
{bulkInvitesBusy ? 'Enabling invites...' : inviteEnabledCount === nonAdminUsers.length && nonAdminUsers.length > 0 ? 'Invites enabled for everyone' : 'Enable invites for all users'}
</button>
</div>
</div>
{inviteStatus && <p role="status">{inviteStatus}</p>}
</div>
<div className="admin-panel user-directory-search-panel"> <div className="admin-panel user-directory-search-panel">
<div className="user-directory-panel-header"> <div className="user-directory-panel-header">
<div> <div>