bafe799d00
CI / checks (pull_request) Failing after 12m24s
The Cognito panel gains a test sign-in that shows the decoded ID and access token claims, a groups section, and a per-user panel to enable or disable, confirm, sign out everywhere, reset or set a password and change group membership. Delete confirmations (row and bulk) now say what else goes, driven by the schema's deleteImpact, and the inspector follows list refreshes instead of the click-time snapshot.
65 lines
3.8 KiB
TypeScript
65 lines
3.8 KiB
TypeScript
import {Trash2} from 'lucide-react'
|
|
import {useMutation, useQueryClient, type UseQueryResult} from '@tanstack/react-query'
|
|
import {useState} from 'react'
|
|
import {createUserPoolGroup, deleteUserPoolGroup} from '@/api/cloudProxyClient'
|
|
import type {UserPoolGroup} from '@/types/resource'
|
|
|
|
type Props = {
|
|
poolId: string
|
|
groupsQuery: UseQueryResult<UserPoolGroup[]>
|
|
groupsKey: unknown[]
|
|
runtimeReachable: boolean
|
|
}
|
|
|
|
export function CognitoGroupsSection({poolId, groupsQuery, groupsKey, runtimeReachable}: Props) {
|
|
const queryClient = useQueryClient()
|
|
const [name, setName] = useState('')
|
|
const [description, setDescription] = useState('')
|
|
const [precedence, setPrecedence] = useState('')
|
|
const refresh = () => queryClient.invalidateQueries({queryKey: groupsKey})
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: () => createUserPoolGroup('aws', poolId, {
|
|
name: name.trim(),
|
|
...(description.trim() ? {description: description.trim()} : {}),
|
|
...(precedence.trim() ? {precedence: Number(precedence)} : {}),
|
|
}),
|
|
onSuccess: () => {
|
|
setName('')
|
|
setDescription('')
|
|
setPrecedence('')
|
|
return refresh()
|
|
},
|
|
})
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (groupName: string) => deleteUserPoolGroup('aws', poolId, groupName),
|
|
onSuccess: refresh,
|
|
})
|
|
|
|
const groups = groupsQuery.data ?? []
|
|
const pending = createMutation.isPending || deleteMutation.isPending
|
|
const error = groupsQuery.error ?? createMutation.error ?? deleteMutation.error
|
|
|
|
return <>
|
|
<div className="input-row resource-table-bar">
|
|
<div><p className="eyebrow">Groups</p><span className="muted">Group names appear in the <code>cognito:groups</code> token claim, so apps can check roles</span></div>
|
|
</div>
|
|
<form className="dynamic-form" aria-label="Create group" onSubmit={(event) => { event.preventDefault(); if (name.trim()) createMutation.mutate() }}>
|
|
<label className="dynamic-field"><span>Group name</span><input className="input" value={name} onChange={(event) => setName(event.target.value)} placeholder="admins" disabled={pending} required/></label>
|
|
<label className="dynamic-field"><span>Description</span><input className="input" value={description} onChange={(event) => setDescription(event.target.value)} disabled={pending}/></label>
|
|
<label className="dynamic-field"><span>Precedence</span><input className="input" type="number" min={0} step={1} value={precedence} onChange={(event) => setPrecedence(event.target.value)} disabled={pending}/><small className="muted">Lower wins when a user is in several groups.</small></label>
|
|
<div style={{alignSelf: 'end'}}><button className="button primary" type="submit" disabled={!runtimeReachable || !name.trim() || pending}>{createMutation.isPending ? 'Creating' : 'Create group'}</button></div>
|
|
</form>
|
|
{error instanceof Error && <p className="error-text compact-text" role="alert">{error.message}</p>}
|
|
{groupsQuery.isSuccess && groups.length === 0 && <p className="muted padded">No groups yet.</p>}
|
|
{groups.length > 0 && <div className="cognito-table"><table className="table"><thead><tr><th>Name</th><th>Description</th><th>Precedence</th><th/></tr></thead><tbody>{groups.map((group) => (
|
|
<tr key={group.name}>
|
|
<td><code>{group.name}</code></td>
|
|
<td>{group.description || '—'}</td>
|
|
<td>{group.precedence ?? '—'}</td>
|
|
<td><button className="icon-btn danger" type="button" title="Delete group" aria-label={`Delete group ${group.name}`} disabled={!runtimeReachable || pending} onClick={() => deleteMutation.mutate(group.name)}><Trash2 size={14}/></button></td>
|
|
</tr>
|
|
))}</tbody></table></div>}
|
|
</>
|
|
}
|