feat: show owner info in admin pages
All checks were successful
CI / backend-tests (pull_request) Successful in 29s
CI / frontend-tests (pull_request) Successful in 29s

- Add Owner column to AdminRuns.tsx and AdminGenlockes.tsx
- Add owner filter dropdown to both admin pages
- Add owner field to GenlockeListItem schema (resolved from first leg's run)
- Update frontend types for GenlockeListItem

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 13:38:08 +01:00
parent 3bd24fcdb0
commit a3f332f82b
6 changed files with 171 additions and 8 deletions

View File

@@ -13,14 +13,46 @@ export function AdminGenlockes() {
const [deleting, setDeleting] = useState<GenlockeListItem | null>(null)
const [statusFilter, setStatusFilter] = useState('')
const [ownerFilter, setOwnerFilter] = useState('')
const filtered = useMemo(() => {
if (!statusFilter) return genlockes
return genlockes.filter((g) => g.status === statusFilter)
}, [genlockes, statusFilter])
let result = genlockes
if (statusFilter) result = result.filter((g) => g.status === statusFilter)
if (ownerFilter) {
if (ownerFilter === '__none__') {
result = result.filter((g) => !g.owner)
} else {
result = result.filter((g) => g.owner?.id === ownerFilter)
}
}
return result
}, [genlockes, statusFilter, ownerFilter])
const genlockeOwners = useMemo(() => {
const owners = new Map<string, string>()
let hasUnowned = false
for (const g of genlockes) {
if (g.owner) {
owners.set(g.owner.id, g.owner.displayName ?? g.owner.id)
} else {
hasUnowned = true
}
}
const sorted = [...owners.entries()].sort((a, b) => a[1].localeCompare(b[1]))
return { owners: sorted, hasUnowned }
}, [genlockes])
const columns: Column<GenlockeListItem>[] = [
{ header: 'Name', accessor: (g) => g.name, sortKey: (g) => g.name },
{
header: 'Owner',
accessor: (g) => (
<span className={g.owner ? '' : 'text-text-tertiary'}>
{g.owner?.displayName ?? g.owner?.id ?? 'No owner'}
</span>
),
sortKey: (g) => g.owner?.displayName ?? g.owner?.id ?? '',
},
{
header: 'Status',
accessor: (g) => (
@@ -67,9 +99,25 @@ export function AdminGenlockes() {
<option value="completed">Completed</option>
<option value="failed">Failed</option>
</select>
{statusFilter && (
<select
value={ownerFilter}
onChange={(e) => setOwnerFilter(e.target.value)}
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
>
<option value="">All owners</option>
{genlockeOwners.hasUnowned && <option value="__none__">No owner</option>}
{genlockeOwners.owners.map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
{(statusFilter || ownerFilter) && (
<button
onClick={() => setStatusFilter('')}
onClick={() => {
setStatusFilter('')
setOwnerFilter('')
}}
className="text-sm text-text-tertiary hover:text-text-primary"
>
Clear filters