feat(ldap): search box for the discovered groups/OUs list
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Successful in 40s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m25s

Real directories can return many groups/OUs, making the checkbox list
tedious to scroll through. Adds a client-side search input above the
list that filters by name or DN substring (case-insensitive) as you
type, so picking the right groups for the import filter is faster.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 16:06:10 +02:00
parent 246dc89a98
commit aaa29226c9
3 changed files with 48 additions and 20 deletions
+44 -20
View File
@@ -81,6 +81,15 @@ export default function AdminLdapPage() {
const [discovering, setDiscovering] = useState(false);
const [manualDn, setManualDn] = useState('');
const [savingFilter, setSavingFilter] = useState(false);
const [discoverSearch, setDiscoverSearch] = useState('');
const filteredDiscovered = discovered?.filter((entry) => {
const q = discoverSearch.trim().toLowerCase();
if (!q) return true;
return (
entry.name.toLowerCase().includes(q) || entry.dn.toLowerCase().includes(q)
);
});
const hasAccess =
currentUser?.role === 'ADMIN' || currentUser?.role === 'SUPER_ADMIN';
@@ -555,26 +564,41 @@ export default function AdminLdapPage() {
</div>
{discovered && discovered.length > 0 && (
<div className="mb-4 max-h-64 overflow-y-auto rounded-md border border-border divide-y divide-border">
{discovered.map((entry) => (
<label
key={entry.dn}
className="flex items-center gap-3 px-4 py-2 text-sm hover:bg-muted/30 cursor-pointer"
>
<input
type="checkbox"
checked={groupFilterDns.includes(entry.dn)}
onChange={() => toggleGroupFilterDn(entry.dn)}
/>
<span className="rounded bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground">
{entry.type === 'ou' ? t('groupFilter.typeOu') : t('groupFilter.typeGroup')}
</span>
<span className="font-medium text-foreground">{entry.name}</span>
<span className="font-mono text-xs text-muted-foreground truncate">
{entry.dn}
</span>
</label>
))}
<div className="mb-4 space-y-2">
<input
type="text"
value={discoverSearch}
onChange={(e) => setDiscoverSearch(e.target.value)}
placeholder={t('groupFilter.searchPlaceholder')}
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm"
/>
<div className="max-h-64 overflow-y-auto rounded-md border border-border divide-y divide-border">
{filteredDiscovered && filteredDiscovered.length > 0 ? (
filteredDiscovered.map((entry) => (
<label
key={entry.dn}
className="flex items-center gap-3 px-4 py-2 text-sm hover:bg-muted/30 cursor-pointer"
>
<input
type="checkbox"
checked={groupFilterDns.includes(entry.dn)}
onChange={() => toggleGroupFilterDn(entry.dn)}
/>
<span className="rounded bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground">
{entry.type === 'ou' ? t('groupFilter.typeOu') : t('groupFilter.typeGroup')}
</span>
<span className="font-medium text-foreground">{entry.name}</span>
<span className="font-mono text-xs text-muted-foreground truncate">
{entry.dn}
</span>
</label>
))
) : (
<p className="px-4 py-3 text-sm text-muted-foreground">
{t('groupFilter.noMatches')}
</p>
)}
</div>
</div>
)}
+2
View File
@@ -318,6 +318,8 @@
"typeOu": "OU",
"typeGroup": "Gruppe",
"noneFound": "Keine Gruppen oder OUs gefunden.",
"searchPlaceholder": "Gruppen/OUs durchsuchen...",
"noMatches": "Keine Treffer fuer diese Suche.",
"manualDn": "DN manuell hinzufuegen",
"addDn": "Hinzufuegen",
"selected": "Ausgewaehlt",
+2
View File
@@ -318,6 +318,8 @@
"typeOu": "OU",
"typeGroup": "Group",
"noneFound": "No groups or OUs found.",
"searchPlaceholder": "Search groups/OUs...",
"noMatches": "No matches for this search.",
"manualDn": "Add DN manually",
"addDn": "Add",
"selected": "Selected",