Add auto-update feature to LDAP query builder. This allows users to automatically generate and update LDAP filters based on changes made in the builder.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/780f41b5-94d1-4167-880d-b8a8a3fce389.jpg
This commit is contained in:
alphaeusmote
2025-04-09 03:07:53 +00:00
+241 -311
View File
@@ -4,6 +4,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
@@ -73,9 +74,9 @@ const LdapQueryBuilderPage = () => {
const [showTestResultsDialog, setShowTestResultsDialog] = useState(false); const [showTestResultsDialog, setShowTestResultsDialog] = useState(false);
const [testResults, setTestResults] = useState<any[]>([]); const [testResults, setTestResults] = useState<any[]>([]);
const [isActive, setIsActive] = useState(true); const [isActive, setIsActive] = useState(true);
const [autoUpdateEnabled, setAutoUpdateEnabled] = useState(false);
const [filterBuilder, setFilterBuilder] = useState<any>({ const [filterBuilder, setFilterBuilder] = useState<any>({
operator: "and", operator: "and",
conditions: [{ attribute: "", operator: "equals", value: "" }],
groups: [] groups: []
}); });
@@ -240,7 +241,6 @@ const LdapQueryBuilderPage = () => {
// Reset filter builder to default state // Reset filter builder to default state
setFilterBuilder({ setFilterBuilder({
operator: "and", operator: "and",
conditions: [{ attribute: "", operator: "equals", value: "" }],
groups: [] groups: []
}); });
}; };
@@ -272,6 +272,62 @@ const LdapQueryBuilderPage = () => {
} }
}; };
// Generate LDAP filter from the builder
const generateLdapFilter = () => {
const mainOperatorSymbol = filterBuilder.operator === 'and' ? '&' : '|';
// Generate group conditions
const groupsLdap = filterBuilder.groups
.map((group: any) => {
const groupOperatorSymbol = group.operator === 'and' ? '&' : '|';
const groupConditions = group.conditions
.filter((c: any) => c.attribute.trim() !== '')
.map((c: any) => {
switch (c.operator) {
case 'equals':
return `(${c.attribute}=${c.value})`;
case 'contains':
return `(${c.attribute}=*${c.value}*)`;
case 'startsWith':
return `(${c.attribute}=${c.value}*)`;
case 'endsWith':
return `(${c.attribute}=*${c.value})`;
case 'present':
return `(${c.attribute}=*)`;
case 'notEquals':
return `(!(${c.attribute}=${c.value}))`;
default:
return `(${c.attribute}=${c.value})`;
}
});
if (groupConditions.length === 0) {
return '';
} else if (groupConditions.length === 1) {
return groupConditions[0];
} else {
return `(${groupOperatorSymbol}${groupConditions.join('')})`;
}
})
.filter((ldap: string) => ldap !== '');
if (groupsLdap.length === 0) {
setFilterQuery('');
} else if (groupsLdap.length === 1) {
setFilterQuery(groupsLdap[0]);
} else {
setFilterQuery(`(${mainOperatorSymbol}${groupsLdap.join('')})`);
}
};
// Auto-update the LDAP filter when changes are made (if enabled)
useEffect(() => {
if (autoUpdateEnabled) {
generateLdapFilter();
}
}, [filterBuilder, autoUpdateEnabled]);
// Save filter // Save filter
const handleSaveFilter = () => { const handleSaveFilter = () => {
if (!filterName || !filterQuery) { if (!filterName || !filterQuery) {
@@ -644,102 +700,6 @@ const LdapQueryBuilderPage = () => {
</div> </div>
</div> </div>
{/* Main conditions section */}
<div className="border rounded-md p-4 space-y-4 bg-background">
{filterBuilder.conditions.map((condition: any, index: number) => (
<div key={index} className="grid grid-cols-12 gap-2 items-center">
<div className="col-span-4">
<Input
placeholder="Attribute"
value={condition.attribute}
onChange={(e) => {
const updatedConditions = [...filterBuilder.conditions];
updatedConditions[index].attribute = e.target.value;
setFilterBuilder({
...filterBuilder,
conditions: updatedConditions
});
}}
/>
</div>
<div className="col-span-3">
<Select
value={condition.operator}
onValueChange={(value) => {
const updatedConditions = [...filterBuilder.conditions];
updatedConditions[index].operator = value;
setFilterBuilder({
...filterBuilder,
conditions: updatedConditions
});
}}
>
<SelectTrigger>
<SelectValue placeholder="Operator" />
</SelectTrigger>
<SelectContent>
<SelectItem value="equals">Equals</SelectItem>
<SelectItem value="contains">Contains</SelectItem>
<SelectItem value="startsWith">Starts With</SelectItem>
<SelectItem value="endsWith">Ends With</SelectItem>
<SelectItem value="present">Is Present</SelectItem>
<SelectItem value="notEquals">Not Equals</SelectItem>
</SelectContent>
</Select>
</div>
<div className="col-span-4">
<Input
placeholder="Value"
value={condition.value}
disabled={condition.operator === 'present'}
onChange={(e) => {
const updatedConditions = [...filterBuilder.conditions];
updatedConditions[index].value = e.target.value;
setFilterBuilder({
...filterBuilder,
conditions: updatedConditions
});
}}
/>
</div>
<div className="col-span-1 flex justify-end">
<Button
variant="ghost"
size="icon"
onClick={() => {
const updatedConditions = [...filterBuilder.conditions];
updatedConditions.splice(index, 1);
setFilterBuilder({
...filterBuilder,
conditions: updatedConditions
});
}}
disabled={filterBuilder.conditions.length <= 1}
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
))}
<Button
variant="outline"
size="sm"
onClick={() => {
setFilterBuilder({
...filterBuilder,
conditions: [
...filterBuilder.conditions,
{ attribute: "", operator: "equals", value: "" }
]
});
}}
>
<Plus className="h-4 w-4 mr-2" />
Add Condition
</Button>
</div>
{/* Condition groups section */} {/* Condition groups section */}
<div className="mt-4"> <div className="mt-4">
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
@@ -767,231 +727,201 @@ const LdapQueryBuilderPage = () => {
</Button> </Button>
</div> </div>
{filterBuilder.groups && filterBuilder.groups.map((group: any, groupIndex: number) => ( {filterBuilder.groups && filterBuilder.groups.length > 0 ? (
<div key={groupIndex} className="border rounded-md p-4 mb-4 bg-background"> filterBuilder.groups.map((group: any, groupIndex: number) => (
<div className="flex justify-between items-center mb-3"> <div key={groupIndex} className="border rounded-md p-4 mb-4 bg-background">
<div className="flex items-center"> <div className="flex justify-between items-center mb-3">
<Label className="mr-2">Group Operator:</Label> <div className="flex items-center">
<Select <Label className="mr-2">Group Operator:</Label>
value={group.operator} <Select
onValueChange={(value) => { value={group.operator}
onValueChange={(value) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].operator = value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<SelectTrigger className="w-24">
<SelectValue placeholder="Operator" />
</SelectTrigger>
<SelectContent>
<SelectItem value="and">AND</SelectItem>
<SelectItem value="or">OR</SelectItem>
</SelectContent>
</Select>
</div>
<Button
variant="ghost"
size="icon"
onClick={() => {
const updatedGroups = [...filterBuilder.groups]; const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].operator = value; updatedGroups.splice(groupIndex, 1);
setFilterBuilder({ setFilterBuilder({
...filterBuilder, ...filterBuilder,
groups: updatedGroups groups: updatedGroups
}); });
}} }}
> >
<SelectTrigger className="w-24"> <X className="h-4 w-4" />
<SelectValue placeholder="Operator" /> </Button>
</SelectTrigger> </div>
<SelectContent>
<SelectItem value="and">AND</SelectItem> <div className="space-y-3">
<SelectItem value="or">OR</SelectItem> {group.conditions.map((condition: any, condIndex: number) => (
</SelectContent> <div key={condIndex} className="grid grid-cols-12 gap-2 items-center">
</Select> <div className="col-span-4">
<Input
placeholder="Attribute"
value={condition.attribute}
onChange={(e) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions[condIndex].attribute = e.target.value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
/>
</div>
<div className="col-span-3">
<Select
value={condition.operator}
onValueChange={(value) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions[condIndex].operator = value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<SelectTrigger>
<SelectValue placeholder="Operator" />
</SelectTrigger>
<SelectContent>
<SelectItem value="equals">Equals</SelectItem>
<SelectItem value="contains">Contains</SelectItem>
<SelectItem value="startsWith">Starts With</SelectItem>
<SelectItem value="endsWith">Ends With</SelectItem>
<SelectItem value="present">Is Present</SelectItem>
<SelectItem value="notEquals">Not Equals</SelectItem>
</SelectContent>
</Select>
</div>
<div className="col-span-4">
<Input
placeholder="Value"
value={condition.value}
disabled={condition.operator === 'present'}
onChange={(e) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions[condIndex].value = e.target.value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
/>
</div>
<div className="col-span-1 flex justify-end">
<Button
variant="ghost"
size="icon"
onClick={() => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions.splice(condIndex, 1);
if (updatedGroups[groupIndex].conditions.length === 0) {
updatedGroups.splice(groupIndex, 1);
}
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
disabled={group.conditions.length <= 1}
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
))}
<Button
variant="outline"
size="sm"
onClick={() => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions.push(
{ attribute: "", operator: "equals", value: "" }
);
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<Plus className="h-4 w-4 mr-2" />
Add Condition to Group
</Button>
</div> </div>
<Button
variant="ghost"
size="icon"
onClick={() => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups.splice(groupIndex, 1);
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<X className="h-4 w-4" />
</Button>
</div> </div>
))
<div className="space-y-3"> ) : (
{group.conditions.map((condition: any, condIndex: number) => ( <div className="p-8 text-center border rounded-md bg-background">
<div key={condIndex} className="grid grid-cols-12 gap-2 items-center"> <p className="text-muted-foreground mb-4">No condition groups added yet</p>
<div className="col-span-4"> <Button
<Input variant="outline"
placeholder="Attribute" size="sm"
value={condition.attribute} onClick={() => {
onChange={(e) => { setFilterBuilder({
const updatedGroups = [...filterBuilder.groups]; ...filterBuilder,
updatedGroups[groupIndex].conditions[condIndex].attribute = e.target.value; groups: [
setFilterBuilder({ ...filterBuilder.groups,
...filterBuilder, {
groups: updatedGroups operator: "and",
}); conditions: [
}} { attribute: "", operator: "equals", value: "" }
/> ]
</div>
<div className="col-span-3">
<Select
value={condition.operator}
onValueChange={(value) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions[condIndex].operator = value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<SelectTrigger>
<SelectValue placeholder="Operator" />
</SelectTrigger>
<SelectContent>
<SelectItem value="equals">Equals</SelectItem>
<SelectItem value="contains">Contains</SelectItem>
<SelectItem value="startsWith">Starts With</SelectItem>
<SelectItem value="endsWith">Ends With</SelectItem>
<SelectItem value="present">Is Present</SelectItem>
<SelectItem value="notEquals">Not Equals</SelectItem>
</SelectContent>
</Select>
</div>
<div className="col-span-4">
<Input
placeholder="Value"
value={condition.value}
disabled={condition.operator === 'present'}
onChange={(e) => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions[condIndex].value = e.target.value;
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
/>
</div>
<div className="col-span-1 flex justify-end">
<Button
variant="ghost"
size="icon"
onClick={() => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions.splice(condIndex, 1);
if (updatedGroups[groupIndex].conditions.length === 0) {
updatedGroups.splice(groupIndex, 1);
}
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
disabled={group.conditions.length <= 1}
>
<Trash className="h-4 w-4" />
</Button>
</div>
</div>
))}
<Button
variant="outline"
size="sm"
onClick={() => {
const updatedGroups = [...filterBuilder.groups];
updatedGroups[groupIndex].conditions.push(
{ attribute: "", operator: "equals", value: "" }
);
setFilterBuilder({
...filterBuilder,
groups: updatedGroups
});
}}
>
<Plus className="h-4 w-4 mr-2" />
Add Condition to Group
</Button>
</div>
</div>
))}
</div>
<div className="mt-4">
<Button
variant="secondary"
size="sm"
onClick={() => {
// Generate LDAP filter from visual builder
const mainOperatorSymbol = filterBuilder.operator === 'and' ? '&' : '|';
const mainConditions = filterBuilder.conditions
.filter((c: any) => c.attribute.trim() !== '')
.map((c: any) => {
switch (c.operator) {
case 'equals':
return `(${c.attribute}=${c.value})`;
case 'contains':
return `(${c.attribute}=*${c.value}*)`;
case 'startsWith':
return `(${c.attribute}=${c.value}*)`;
case 'endsWith':
return `(${c.attribute}=*${c.value})`;
case 'present':
return `(${c.attribute}=*)`;
case 'notEquals':
return `(!(${c.attribute}=${c.value}))`;
default:
return `(${c.attribute}=${c.value})`;
}
});
// Generate group conditions
const groupsLdap = filterBuilder.groups
.map((group: any) => {
const groupOperatorSymbol = group.operator === 'and' ? '&' : '|';
const groupConditions = group.conditions
.filter((c: any) => c.attribute.trim() !== '')
.map((c: any) => {
switch (c.operator) {
case 'equals':
return `(${c.attribute}=${c.value})`;
case 'contains':
return `(${c.attribute}=*${c.value}*)`;
case 'startsWith':
return `(${c.attribute}=${c.value}*)`;
case 'endsWith':
return `(${c.attribute}=*${c.value})`;
case 'present':
return `(${c.attribute}=*)`;
case 'notEquals':
return `(!(${c.attribute}=${c.value}))`;
default:
return `(${c.attribute}=${c.value})`;
} }
}); ]
});
if (groupConditions.length === 0) { }}
return ''; >
} else if (groupConditions.length === 1) { <FolderPlus className="h-4 w-4 mr-2" />
return groupConditions[0]; Add First Group
} else { </Button>
return `(${groupOperatorSymbol}${groupConditions.join('')})`; </div>
} )}
})
.filter((ldap: string) => ldap !== '');
// Combine main conditions and group conditions
const allConditions = [...mainConditions, ...groupsLdap];
if (allConditions.length === 0) {
setFilterQuery('');
} else if (allConditions.length === 1) {
setFilterQuery(allConditions[0]);
} else {
setFilterQuery(`(${mainOperatorSymbol}${allConditions.join('')})`);
}
}}
>
<ArrowLeftRight className="h-4 w-4 mr-2" />
Generate LDAP Filter
</Button>
</div> </div>
{/* Additional feature 1: Auto-generate filter on changes */}
<div className="mt-4 flex items-center space-x-2">
<Switch
id="autoUpdate"
checked={autoUpdateEnabled}
onCheckedChange={setAutoUpdateEnabled}
/>
<Label htmlFor="autoUpdate">Auto-generate LDAP filter on changes</Label>
</div>
{/* Generate button (if not auto-update) */}
{!autoUpdateEnabled && (
<div className="mt-4">
<Button
variant="secondary"
size="sm"
onClick={generateLdapFilter}
>
<ArrowLeftRight className="h-4 w-4 mr-2" />
Generate LDAP Filter
</Button>
</div>
)}
{/* Display LDAP Filter */}
{filterQuery && ( {filterQuery && (
<div className="mt-4 p-3 border rounded-md bg-muted"> <div className="mt-4 p-3 border rounded-md bg-muted">
<Label className="text-xs mb-1 block">Generated LDAP Filter:</Label> <Label className="text-xs mb-1 block">Generated LDAP Filter:</Label>