import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Textarea } from '@/components/ui/textarea'; import { Switch } from '@/components/ui/switch'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Separator } from '@/components/ui/separator'; import { Info, Clock, Settings, ListFilter, EyeIcon, CalendarDays, RefreshCw, AlertTriangle } from 'lucide-react'; import { DynamicGroupRule, LdapConnection } from '@shared/schema'; import CronJobBuilder, { ScheduleItem } from './cron-job-builder'; import ConditionBuilder from './condition-builder'; import { useToast } from '@/hooks/use-toast'; import { queryClient, apiRequest } from '@/lib/queryClient'; import { useMutation, useQuery } from '@tanstack/react-query'; export interface Condition { id?: number; ruleId?: number; parentId?: number | null; attribute: string; customAttribute?: string; operator: string; value: string; logicalOperator?: string | null; isGroup?: boolean; } interface RuleEditorProps { rule?: DynamicGroupRule; onSave: (rule: DynamicGroupRule, schedules: ScheduleItem[]) => void; onCancel: () => void; } export const RuleEditor: React.FC = ({ rule, onSave, onCancel }) => { const { toast } = useToast(); const [activeTab, setActiveTab] = useState('general'); const [conditions, setConditions] = useState([]); const [schedules, setSchedules] = useState([]); const [formData, setFormData] = useState({ name: rule?.name || '', description: rule?.description || '', targetGroup: rule?.targetGroup || '', enabled: rule?.enabled ?? true, variablePattern: rule?.variablePattern || '', connections: rule?.connectionIds || [] }); // Load LDAP connections const { data: ldapConnections = [] } = useQuery({ queryKey: ['/api/ldap-connections'], staleTime: 60000, }); // Load conditions for the rule if editing const { data: conditionsData } = useQuery({ queryKey: ['/api/dynamic-group-rules', rule?.id, 'conditions'], enabled: !!rule?.id, staleTime: 60000, }); // Load schedules for the rule if editing const { data: schedulesData } = useQuery({ queryKey: ['/api/dynamic-group-rules', rule?.id, 'schedules'], enabled: !!rule?.id, staleTime: 60000, }); // Initialize conditions and schedules when data loads useEffect(() => { if (conditionsData) { setConditions(conditionsData); } }, [conditionsData]); useEffect(() => { if (schedulesData) { setSchedules(schedulesData); } }, [schedulesData]); // Handle form input changes const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; // Handle toggle changes const handleToggleChange = (field: string, value: boolean) => { setFormData(prev => ({ ...prev, [field]: value })); }; // Handle connection selection const handleConnectionChange = (connectionIds: number[]) => { setFormData(prev => ({ ...prev, connections: connectionIds })); }; // Handle form submission const handleSubmit = () => { if (!formData.name) { toast({ title: "Missing Information", description: "Please provide a name for the rule", variant: "destructive" }); setActiveTab('general'); return; } if (!formData.targetGroup) { toast({ title: "Missing Information", description: "Please specify a target AD group", variant: "destructive" }); setActiveTab('general'); return; } if (formData.connections.length === 0) { toast({ title: "Missing Information", description: "Please select at least one LDAP connection", variant: "destructive" }); setActiveTab('general'); return; } if (conditions.length === 0) { toast({ title: "Missing Information", description: "Please define at least one condition", variant: "destructive" }); setActiveTab('conditions'); return; } if (schedules.length === 0) { toast({ title: "Missing Information", description: "Please define at least one schedule", variant: "destructive" }); setActiveTab('schedules'); return; } const ruleData: DynamicGroupRule = { id: rule?.id, name: formData.name, description: formData.description || null, targetGroup: formData.targetGroup, enabled: formData.enabled, createdAt: rule?.createdAt || new Date(), updatedAt: new Date(), lastRun: rule?.lastRun || null, lastRunStatus: rule?.lastRunStatus || null, variablePattern: formData.variablePattern || null, connectionIds: formData.connections }; onSave(ruleData, schedules); }; // Test the rule const testRuleMutation = useMutation({ mutationFn: async () => { const res = await apiRequest('POST', '/api/dynamic-group-rules/test', { rule: { name: formData.name, targetGroup: formData.targetGroup, connectionIds: formData.connections, variablePattern: formData.variablePattern || null }, conditions }); return await res.json(); }, onSuccess: (data) => { toast({ title: "Test Results", description: `The rule would match ${data.matchCount} objects`, }); }, onError: (error: Error) => { toast({ title: "Test Failed", description: error.message, variant: "destructive" }); } }); const handleTest = () => { if (formData.connections.length === 0) { toast({ title: "Missing Information", description: "Please select at least one LDAP connection to test", variant: "destructive" }); return; } if (conditions.length === 0) { toast({ title: "Missing Information", description: "Please define at least one condition to test", variant: "destructive" }); return; } testRuleMutation.mutate(); }; return (
General Conditions Schedules Preview {/* General Settings Tab */} Rule Settings Configure the basic settings for your dynamic group rule

Full DN of the group to update (e.g., CN=Marketing,OU=Groups,DC=example,DC=com)