mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-12 19:58:26 +00:00
Enhance dynamic group rule editor with variable selection.
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/d4482634-dce5-40ce-a61e-aaaf3f0b2047.jpg
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
|||||||
import { DynamicGroupRule, LdapConnection } from '@shared/schema';
|
import { DynamicGroupRule, LdapConnection } from '@shared/schema';
|
||||||
import CronJobBuilder, { ScheduleItem } from './cron-job-builder';
|
import CronJobBuilder, { ScheduleItem } from './cron-job-builder';
|
||||||
import ConditionBuilder from './condition-builder';
|
import ConditionBuilder from './condition-builder';
|
||||||
|
import VariableSelector from './variable-selector';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { queryClient, apiRequest } from '@/lib/queryClient';
|
import { queryClient, apiRequest } from '@/lib/queryClient';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
@@ -349,17 +350,30 @@ export const RuleEditor: React.FC<RuleEditorProps> = ({ rule, onSave, onCancel }
|
|||||||
<Label htmlFor="targetGroupName">Group Name <span className="text-destructive">*</span></Label>
|
<Label htmlFor="targetGroupName">Group Name <span className="text-destructive">*</span></Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<span className="py-2 px-3 bg-muted text-muted-foreground rounded-l-md border">CN=</span>
|
<span className="py-2 px-3 bg-muted text-muted-foreground rounded-l-md border">CN=</span>
|
||||||
<Input
|
<div className="relative flex-1">
|
||||||
id="targetGroupName"
|
<Input
|
||||||
name="targetGroupName"
|
id="targetGroupName"
|
||||||
value={formData.targetGroupName}
|
name="targetGroupName"
|
||||||
onChange={handleInputChange}
|
value={formData.targetGroupName}
|
||||||
className="rounded-l-none flex-1"
|
onChange={handleInputChange}
|
||||||
placeholder="GroupName"
|
className="rounded-l-none pr-24"
|
||||||
/>
|
placeholder="GroupName"
|
||||||
|
/>
|
||||||
|
<div className="absolute right-1 top-1">
|
||||||
|
<VariableSelector
|
||||||
|
onSelectVariable={(variable) => {
|
||||||
|
const variableText = `{${variable}}`;
|
||||||
|
const currentValue = formData.targetGroupName || '';
|
||||||
|
const newValue = currentValue + variableText;
|
||||||
|
setFormData({ ...formData, targetGroupName: newValue });
|
||||||
|
}}
|
||||||
|
connections={formData.connections}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Name of the group (can include variables)
|
Name of the group (can include variables like {'{attributeName}'})
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -387,51 +401,6 @@ export const RuleEditor: React.FC<RuleEditorProps> = ({ rule, onSave, onCancel }
|
|||||||
<Label htmlFor="createGroupIfNotExists">Create group if it doesn't exist</Label>
|
<Label htmlFor="createGroupIfNotExists">Create group if it doesn't exist</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<Textarea
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
value={formData.description}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="Enter rule description"
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>LDAP Connections <span className="text-destructive">*</span></Label>
|
|
||||||
<div className="border rounded-md p-4 space-y-2">
|
|
||||||
{ldapConnections.length === 0 ? (
|
|
||||||
<p className="text-sm text-muted-foreground">No LDAP connections available. Please create one first.</p>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{ldapConnections.map((connection) => (
|
|
||||||
<div key={connection.id} className="flex items-center space-x-2">
|
|
||||||
<Switch
|
|
||||||
id={`connection-${connection.id}`}
|
|
||||||
checked={formData.connections.includes(connection.id)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (checked) {
|
|
||||||
handleConnectionChange([...formData.connections, connection.id]);
|
|
||||||
} else {
|
|
||||||
handleConnectionChange(formData.connections.filter(id => id !== connection.id));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor={`connection-${connection.id}`}>
|
|
||||||
{connection.name} ({connection.server})
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Select one or more LDAP connections to search for objects
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="variablePattern">Variable Pattern</Label>
|
<Label htmlFor="variablePattern">Variable Pattern</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -519,8 +488,14 @@ export const RuleEditor: React.FC<RuleEditorProps> = ({ rule, onSave, onCancel }
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm font-medium text-muted-foreground">Target Group:</dt>
|
<dt className="text-sm font-medium text-muted-foreground">Target Group:</dt>
|
||||||
<dd className="text-sm max-w-[250px] truncate" title={formData.targetGroup}>
|
<dd className="text-sm max-w-[250px] truncate" title={getTargetGroupDN()}>
|
||||||
{formData.targetGroup || 'Not set'}
|
{getTargetGroupDN() || 'Not set'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<dt className="text-sm font-medium text-muted-foreground">Create if not exists:</dt>
|
||||||
|
<dd className="text-sm">
|
||||||
|
{formData.createGroupIfNotExists ? 'Yes' : 'No'}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { PlusCircle, Database, Tag } from "lucide-react";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
|
||||||
|
interface VariableSelectorProps {
|
||||||
|
onSelectVariable: (variable: string) => void;
|
||||||
|
connections: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common LDAP attributes that might be useful as variables
|
||||||
|
const COMMON_ATTRIBUTES = [
|
||||||
|
{ value: 'sAMAccountName', label: 'sAMAccountName', description: 'Account name (username)' },
|
||||||
|
{ value: 'givenName', label: 'givenName', description: 'First name' },
|
||||||
|
{ value: 'sn', label: 'sn', description: 'Last name (surname)' },
|
||||||
|
{ value: 'displayName', label: 'displayName', description: 'Display name' },
|
||||||
|
{ value: 'mail', label: 'mail', description: 'Email address' },
|
||||||
|
{ value: 'department', label: 'department', description: 'Department' },
|
||||||
|
{ value: 'company', label: 'company', description: 'Company' },
|
||||||
|
{ value: 'title', label: 'title', description: 'Job title' },
|
||||||
|
{ value: 'manager', label: 'manager', description: 'Manager DN' },
|
||||||
|
{ value: 'employeeID', label: 'employeeID', description: 'Employee ID' },
|
||||||
|
{ value: 'memberOf', label: 'memberOf', description: 'Group memberships' },
|
||||||
|
{ value: 'objectSid', label: 'objectSid', description: 'Security identifier' },
|
||||||
|
{ value: 'whenCreated', label: 'whenCreated', description: 'Creation date' },
|
||||||
|
{ value: 'physicalDeliveryOfficeName', label: 'physicalDeliveryOfficeName', description: 'Office' },
|
||||||
|
{ value: 'streetAddress', label: 'streetAddress', description: 'Street address' },
|
||||||
|
{ value: 'l', label: 'l', description: 'City' },
|
||||||
|
{ value: 'st', label: 'st', description: 'State/province' },
|
||||||
|
{ value: 'postalCode', label: 'postalCode', description: 'Postal code' },
|
||||||
|
{ value: 'c', label: 'c', description: 'Country code' },
|
||||||
|
{ value: 'telephoneNumber', label: 'telephoneNumber', description: 'Phone number' },
|
||||||
|
{ value: 'mobile', label: 'mobile', description: 'Mobile number' },
|
||||||
|
{ value: 'ipPhone', label: 'ipPhone', description: 'IP phone' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const VariableSelector: React.FC<VariableSelectorProps> = ({ onSelectVariable, connections }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [customAttribute, setCustomAttribute] = useState('');
|
||||||
|
const { toast } = useToast();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleSelect = (attribute: string) => {
|
||||||
|
onSelectVariable(attribute);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCustomAttribute = () => {
|
||||||
|
if (!customAttribute.trim()) {
|
||||||
|
toast({
|
||||||
|
title: "Invalid attribute",
|
||||||
|
description: "Please enter an attribute name",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectVariable(customAttribute.trim());
|
||||||
|
setCustomAttribute('');
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
className="ml-2 h-8 gap-1"
|
||||||
|
>
|
||||||
|
<PlusCircle className="h-3.5 w-3.5" />
|
||||||
|
<span>Add Variable</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-64 p-0" align="end">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search attributes..." />
|
||||||
|
|
||||||
|
<CommandList className="max-h-[300px]">
|
||||||
|
<CommandEmpty>No attributes found</CommandEmpty>
|
||||||
|
|
||||||
|
<CommandGroup heading="Custom Attribute">
|
||||||
|
<div className="flex items-center px-2 py-1.5">
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
value={customAttribute}
|
||||||
|
onChange={(e) => setCustomAttribute(e.target.value)}
|
||||||
|
placeholder="Enter attribute name"
|
||||||
|
className="flex-1 h-8"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleAddCustomAttribute}
|
||||||
|
className="ml-1"
|
||||||
|
>
|
||||||
|
<PlusCircle className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CommandGroup>
|
||||||
|
|
||||||
|
<CommandGroup heading="Common Attributes">
|
||||||
|
{COMMON_ATTRIBUTES.map((attribute) => (
|
||||||
|
<CommandItem
|
||||||
|
key={attribute.value}
|
||||||
|
value={attribute.value}
|
||||||
|
onSelect={() => handleSelect(attribute.value)}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Tag className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm">{attribute.label}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">{attribute.description}</p>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VariableSelector;
|
||||||
Reference in New Issue
Block a user