mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-07-26 11:59:14 +00:00
Add dynamic group rule management API endpoints and UI components.
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/4e0af531-eaa1-4a7b-a243-4f9e551aef27.jpg
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Trash, Plus, Calendar, Clock, RotateCcw } from 'lucide-react';
|
||||
import {
|
||||
ScheduleFrequency,
|
||||
Weekday,
|
||||
scheduleFrequencies,
|
||||
weekdays,
|
||||
InsertScheduleRule
|
||||
} from '@shared/schema';
|
||||
|
||||
interface CronJobBuilderProps {
|
||||
schedules: ScheduleItem[];
|
||||
onSchedulesChange: (schedules: ScheduleItem[]) => void;
|
||||
ruleId?: number;
|
||||
}
|
||||
|
||||
export interface ScheduleItem {
|
||||
id?: number;
|
||||
ruleId?: number;
|
||||
name: string;
|
||||
description: string;
|
||||
frequency: ScheduleFrequency;
|
||||
cronExpression: string;
|
||||
dayOfWeek?: Weekday | null;
|
||||
dayOfMonth?: number | null;
|
||||
hour: number;
|
||||
minute: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const initialSchedule: ScheduleItem = {
|
||||
name: 'Default Schedule',
|
||||
description: 'Runs daily at midnight',
|
||||
frequency: 'daily',
|
||||
cronExpression: '0 0 * * *',
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
||||
schedules = [],
|
||||
onSchedulesChange,
|
||||
ruleId
|
||||
}) => {
|
||||
const [activeSchedules, setActiveSchedules] = useState<ScheduleItem[]>(
|
||||
schedules.length > 0 ? schedules : [initialSchedule]
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState<string>('0');
|
||||
|
||||
useEffect(() => {
|
||||
if (schedules.length > 0 && activeSchedules.length === 0) {
|
||||
setActiveSchedules(schedules);
|
||||
}
|
||||
}, [schedules]);
|
||||
|
||||
const updateSchedule = (index: number, updatedSchedule: Partial<ScheduleItem>) => {
|
||||
const newSchedules = [...activeSchedules];
|
||||
newSchedules[index] = { ...newSchedules[index], ...updatedSchedule };
|
||||
|
||||
// Update cron expression
|
||||
const schedule = newSchedules[index];
|
||||
let cronExpression = '';
|
||||
|
||||
switch (schedule.frequency) {
|
||||
case 'minutely':
|
||||
cronExpression = '* * * * *';
|
||||
break;
|
||||
case 'hourly':
|
||||
cronExpression = `${schedule.minute} * * * *`;
|
||||
break;
|
||||
case 'daily':
|
||||
cronExpression = `${schedule.minute} ${schedule.hour} * * *`;
|
||||
break;
|
||||
case 'weekly':
|
||||
const dayNumber = schedule.dayOfWeek ? weekdays.indexOf(schedule.dayOfWeek) : 0;
|
||||
cronExpression = `${schedule.minute} ${schedule.hour} * * ${dayNumber}`;
|
||||
break;
|
||||
case 'monthly':
|
||||
const day = schedule.dayOfMonth || 1;
|
||||
cronExpression = `${schedule.minute} ${schedule.hour} ${day} * *`;
|
||||
break;
|
||||
}
|
||||
|
||||
newSchedules[index].cronExpression = cronExpression;
|
||||
|
||||
setActiveSchedules(newSchedules);
|
||||
onSchedulesChange(newSchedules);
|
||||
};
|
||||
|
||||
const addNewSchedule = () => {
|
||||
const newSchedule: ScheduleItem = {
|
||||
...initialSchedule,
|
||||
name: `Schedule ${activeSchedules.length + 1}`,
|
||||
ruleId
|
||||
};
|
||||
|
||||
const newSchedules = [...activeSchedules, newSchedule];
|
||||
setActiveSchedules(newSchedules);
|
||||
setActiveTab(String(newSchedules.length - 1));
|
||||
onSchedulesChange(newSchedules);
|
||||
};
|
||||
|
||||
const removeSchedule = (index: number) => {
|
||||
if (activeSchedules.length <= 1) {
|
||||
return; // Don't remove the last schedule
|
||||
}
|
||||
|
||||
const newSchedules = activeSchedules.filter((_, i) => i !== index);
|
||||
setActiveSchedules(newSchedules);
|
||||
|
||||
// Set active tab to the previous one or the first one
|
||||
if (Number(activeTab) >= newSchedules.length) {
|
||||
setActiveTab(String(Math.max(0, newSchedules.length - 1)));
|
||||
}
|
||||
|
||||
onSchedulesChange(newSchedules);
|
||||
};
|
||||
|
||||
const toggleScheduleStatus = (index: number) => {
|
||||
const newSchedules = [...activeSchedules];
|
||||
newSchedules[index].enabled = !newSchedules[index].enabled;
|
||||
setActiveSchedules(newSchedules);
|
||||
onSchedulesChange(newSchedules);
|
||||
};
|
||||
|
||||
// Function to generate a human-readable description of the schedule
|
||||
const getScheduleDescription = (schedule: ScheduleItem): string => {
|
||||
switch (schedule.frequency) {
|
||||
case 'minutely':
|
||||
return 'Runs every minute';
|
||||
case 'hourly':
|
||||
return `Runs every hour at ${schedule.minute} minutes past the hour`;
|
||||
case 'daily':
|
||||
return `Runs daily at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
case 'weekly':
|
||||
return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
case 'monthly':
|
||||
return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
default:
|
||||
return 'Custom schedule';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to format time in 12-hour format
|
||||
const formatTime = (hour: number, minute: number): string => {
|
||||
const period = hour >= 12 ? 'PM' : 'AM';
|
||||
const displayHour = hour % 12 || 12;
|
||||
const displayMinute = minute.toString().padStart(2, '0');
|
||||
return `${displayHour}:${displayMinute} ${period}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Schedule Configuration</CardTitle>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addNewSchedule}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
Add Schedule
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="flex items-center space-x-2 mb-4 overflow-x-auto pb-2">
|
||||
<TabsList>
|
||||
{activeSchedules.map((schedule, index) => (
|
||||
<TabsTrigger
|
||||
key={index}
|
||||
value={String(index)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{schedule.name}
|
||||
{!schedule.enabled && (
|
||||
<Badge variant="outline" className="ml-1 text-xs">Disabled</Badge>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
{activeSchedules.map((schedule, index) => (
|
||||
<TabsContent key={index} value={String(index)} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-name-${index}`}>Schedule Name</Label>
|
||||
<Input
|
||||
id={`schedule-name-${index}`}
|
||||
value={schedule.name}
|
||||
onChange={(e) => updateSchedule(index, { name: e.target.value })}
|
||||
placeholder="Schedule Name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-frequency-${index}`}>Frequency</Label>
|
||||
<Select
|
||||
value={schedule.frequency}
|
||||
onValueChange={(value) => updateSchedule(index, { frequency: value as ScheduleFrequency })}
|
||||
>
|
||||
<SelectTrigger id={`schedule-frequency-${index}`}>
|
||||
<SelectValue placeholder="Select frequency" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{scheduleFrequencies.map((freq) => (
|
||||
<SelectItem key={freq} value={freq}>
|
||||
{freq.charAt(0).toUpperCase() + freq.slice(1)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{(schedule.frequency === 'weekly') && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-day-week-${index}`}>Day of Week</Label>
|
||||
<Select
|
||||
value={schedule.dayOfWeek || 'Monday'}
|
||||
onValueChange={(value) => updateSchedule(index, { dayOfWeek: value as Weekday })}
|
||||
>
|
||||
<SelectTrigger id={`schedule-day-week-${index}`}>
|
||||
<SelectValue placeholder="Select day" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{weekdays.map((day) => (
|
||||
<SelectItem key={day} value={day}>
|
||||
{day}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(schedule.frequency === 'monthly') && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-day-month-${index}`}>Day of Month</Label>
|
||||
<Select
|
||||
value={String(schedule.dayOfMonth || 1)}
|
||||
onValueChange={(value) => updateSchedule(index, { dayOfMonth: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id={`schedule-day-month-${index}`}>
|
||||
<SelectValue placeholder="Select day" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 31 }, (_, i) => i + 1).map((day) => (
|
||||
<SelectItem key={day} value={String(day)}>
|
||||
{day}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(schedule.frequency !== 'minutely') && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(schedule.frequency !== 'hourly') && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-hour-${index}`}>Hour</Label>
|
||||
<Select
|
||||
value={String(schedule.hour)}
|
||||
onValueChange={(value) => updateSchedule(index, { hour: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id={`schedule-hour-${index}`}>
|
||||
<SelectValue placeholder="Select hour" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 24 }, (_, i) => i).map((hour) => (
|
||||
<SelectItem key={hour} value={String(hour)}>
|
||||
{hour.toString().padStart(2, '0')}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-minute-${index}`}>Minute</Label>
|
||||
<Select
|
||||
value={String(schedule.minute)}
|
||||
onValueChange={(value) => updateSchedule(index, { minute: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger id={`schedule-minute-${index}`}>
|
||||
<SelectValue placeholder="Select minute" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: 60 }, (_, i) => i).map((minute) => (
|
||||
<SelectItem key={minute} value={String(minute)}>
|
||||
{minute.toString().padStart(2, '0')}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`schedule-description-${index}`}>Description</Label>
|
||||
<Input
|
||||
id={`schedule-description-${index}`}
|
||||
value={schedule.description}
|
||||
onChange={(e) => updateSchedule(index, { description: e.target.value })}
|
||||
placeholder="Schedule Description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="block mb-2">Cron Expression</Label>
|
||||
<div className="flex items-center p-2 rounded-md bg-muted/50">
|
||||
<code className="text-sm font-mono">{schedule.cronExpression}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<div className="bg-muted/30 p-3 rounded-md">
|
||||
<p className="text-sm font-medium mb-1">Schedule Summary:</p>
|
||||
<p className="text-sm">{getScheduleDescription(schedule)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id={`schedule-enabled-${index}`}
|
||||
checked={schedule.enabled}
|
||||
onCheckedChange={() => toggleScheduleStatus(index)}
|
||||
/>
|
||||
<Label htmlFor={`schedule-enabled-${index}`}>
|
||||
{schedule.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{activeSchedules.length > 1 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => removeSchedule(index)}
|
||||
>
|
||||
<Trash className="h-4 w-4 mr-1" />
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CronJobBuilder;
|
||||
@@ -0,0 +1,214 @@
|
||||
import { ScheduleFrequency, Weekday, weekdays } from '@shared/schema';
|
||||
import { ScheduleItem } from '@/components/dynamic-groups/cron-job-builder';
|
||||
|
||||
/**
|
||||
* Parse a cron expression into its components
|
||||
* @param cronExpression - The cron expression to parse
|
||||
* @returns An object with the cron expression components
|
||||
*/
|
||||
export const parseCronExpression = (cronExpression: string): {
|
||||
minute: number;
|
||||
hour: number;
|
||||
dayOfMonth: number | null;
|
||||
month: number | null;
|
||||
dayOfWeek: number | null;
|
||||
} => {
|
||||
const parts = cronExpression.split(' ');
|
||||
|
||||
// Default to all parts being null/0
|
||||
const result = {
|
||||
minute: 0,
|
||||
hour: 0,
|
||||
dayOfMonth: null as number | null,
|
||||
month: null as number | null,
|
||||
dayOfWeek: null as number | null
|
||||
};
|
||||
|
||||
// Handle minute (first part)
|
||||
if (parts.length > 0 && parts[0] !== '*') {
|
||||
result.minute = parseInt(parts[0]);
|
||||
}
|
||||
|
||||
// Handle hour (second part)
|
||||
if (parts.length > 1 && parts[1] !== '*') {
|
||||
result.hour = parseInt(parts[1]);
|
||||
}
|
||||
|
||||
// Handle day of month (third part)
|
||||
if (parts.length > 2 && parts[2] !== '*') {
|
||||
result.dayOfMonth = parseInt(parts[2]);
|
||||
}
|
||||
|
||||
// Handle month (fourth part)
|
||||
if (parts.length > 3 && parts[3] !== '*') {
|
||||
result.month = parseInt(parts[3]);
|
||||
}
|
||||
|
||||
// Handle day of week (fifth part)
|
||||
if (parts.length > 4 && parts[4] !== '*') {
|
||||
result.dayOfWeek = parseInt(parts[4]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect the frequency of a schedule from its cron expression
|
||||
* @param cronExpression - The cron expression to analyze
|
||||
* @returns The detected frequency
|
||||
*/
|
||||
export const detectFrequency = (cronExpression: string): ScheduleFrequency => {
|
||||
if (!cronExpression || cronExpression.trim() === '') {
|
||||
return 'daily'; // Default to daily
|
||||
}
|
||||
|
||||
const parts = cronExpression.split(' ');
|
||||
|
||||
// * * * * * - Every minute
|
||||
if (parts[0] === '*' && parts[1] === '*') {
|
||||
return 'minutely';
|
||||
}
|
||||
|
||||
// MM * * * * - Every hour at minute MM
|
||||
if (parts[0] !== '*' && parts[1] === '*') {
|
||||
return 'hourly';
|
||||
}
|
||||
|
||||
// MM HH * * d - Weekly on day d at HH:MM
|
||||
if (parts[0] !== '*' && parts[1] !== '*' && parts[2] === '*' && parts[3] === '*' && parts[4] !== '*') {
|
||||
return 'weekly';
|
||||
}
|
||||
|
||||
// MM HH DD * * - Monthly on day DD at HH:MM
|
||||
if (parts[0] !== '*' && parts[1] !== '*' && parts[2] !== '*' && parts[3] === '*' && parts[4] === '*') {
|
||||
return 'monthly';
|
||||
}
|
||||
|
||||
// MM HH * * * - Daily at HH:MM
|
||||
if (parts[0] !== '*' && parts[1] !== '*' && parts[2] === '*' && parts[3] === '*' && parts[4] === '*') {
|
||||
return 'daily';
|
||||
}
|
||||
|
||||
// Default to daily for any other patterns
|
||||
return 'daily';
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a cron expression to a schedule item
|
||||
* @param cronExpression - The cron expression to convert
|
||||
* @param name - Optional name for the schedule
|
||||
* @param description - Optional description for the schedule
|
||||
* @returns A schedule item object
|
||||
*/
|
||||
export const cronToScheduleItem = (
|
||||
cronExpression: string,
|
||||
name = 'Schedule',
|
||||
description = '',
|
||||
enabled = true,
|
||||
id?: number,
|
||||
ruleId?: number
|
||||
): ScheduleItem => {
|
||||
const frequency = detectFrequency(cronExpression);
|
||||
const { minute, hour, dayOfMonth, dayOfWeek } = parseCronExpression(cronExpression);
|
||||
|
||||
// Generate description if not provided
|
||||
let autoDescription = description;
|
||||
if (!autoDescription) {
|
||||
autoDescription = getScheduleDescription({
|
||||
frequency,
|
||||
minute,
|
||||
hour,
|
||||
dayOfMonth,
|
||||
dayOfWeek: dayOfWeek !== null ? weekdays[dayOfWeek] : null,
|
||||
cronExpression,
|
||||
name,
|
||||
description: '',
|
||||
enabled
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
ruleId,
|
||||
name,
|
||||
description: autoDescription,
|
||||
frequency,
|
||||
cronExpression,
|
||||
minute,
|
||||
hour,
|
||||
dayOfMonth: dayOfMonth,
|
||||
dayOfWeek: dayOfWeek !== null ? weekdays[dayOfWeek] as Weekday : null,
|
||||
enabled
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a schedule item to a cron expression
|
||||
* @param schedule - The schedule item to convert
|
||||
* @returns A cron expression string
|
||||
*/
|
||||
export const scheduleToCronExpression = (schedule: Partial<ScheduleItem>): string => {
|
||||
let cronExpression = '';
|
||||
|
||||
switch (schedule.frequency) {
|
||||
case 'minutely':
|
||||
cronExpression = '* * * * *';
|
||||
break;
|
||||
case 'hourly':
|
||||
cronExpression = `${schedule.minute || 0} * * * *`;
|
||||
break;
|
||||
case 'daily':
|
||||
cronExpression = `${schedule.minute || 0} ${schedule.hour || 0} * * *`;
|
||||
break;
|
||||
case 'weekly':
|
||||
const dayNumber = schedule.dayOfWeek ? weekdays.indexOf(schedule.dayOfWeek) : 0;
|
||||
cronExpression = `${schedule.minute || 0} ${schedule.hour || 0} * * ${dayNumber}`;
|
||||
break;
|
||||
case 'monthly':
|
||||
const day = schedule.dayOfMonth || 1;
|
||||
cronExpression = `${schedule.minute || 0} ${schedule.hour || 0} ${day} * *`;
|
||||
break;
|
||||
default:
|
||||
cronExpression = '0 0 * * *'; // Default to midnight every day
|
||||
}
|
||||
|
||||
return cronExpression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a human-readable description of a schedule
|
||||
* @param schedule - The schedule to describe
|
||||
* @returns A human-readable description
|
||||
*/
|
||||
export const getScheduleDescription = (schedule: {
|
||||
frequency: ScheduleFrequency;
|
||||
minute: number;
|
||||
hour: number;
|
||||
dayOfMonth: number | null;
|
||||
dayOfWeek: Weekday | null;
|
||||
cronExpression: string;
|
||||
enabled: boolean;
|
||||
}): string => {
|
||||
// Format time in 12-hour format
|
||||
const formatTime = (hour: number, minute: number): string => {
|
||||
const period = hour >= 12 ? 'PM' : 'AM';
|
||||
const displayHour = hour % 12 || 12;
|
||||
const displayMinute = minute.toString().padStart(2, '0');
|
||||
return `${displayHour}:${displayMinute} ${period}`;
|
||||
};
|
||||
|
||||
switch (schedule.frequency) {
|
||||
case 'minutely':
|
||||
return 'Runs every minute';
|
||||
case 'hourly':
|
||||
return `Runs every hour at ${schedule.minute} minutes past the hour`;
|
||||
case 'daily':
|
||||
return `Runs daily at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
case 'weekly':
|
||||
return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
case 'monthly':
|
||||
return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||
default:
|
||||
return 'Custom schedule';
|
||||
}
|
||||
};
|
||||
@@ -5293,6 +5293,215 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic Group Rules routes
|
||||
app.get("/api/dynamic-group-rules", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const rules = await storage.listDynamicGroupRules();
|
||||
res.json(rules);
|
||||
} catch (error) {
|
||||
console.error('Error fetching dynamic group rules:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch dynamic group rules' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/dynamic-group-rules/:id", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const rule = await storage.getDynamicGroupRule(id);
|
||||
|
||||
if (!rule) {
|
||||
return res.status(404).json({ error: 'Dynamic group rule not found' });
|
||||
}
|
||||
|
||||
res.json(rule);
|
||||
} catch (error) {
|
||||
console.error('Error fetching dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch dynamic group rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/dynamic-group-rules", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const rule = await storage.createDynamicGroupRule(req.body);
|
||||
res.status(201).json(rule);
|
||||
} catch (error) {
|
||||
console.error('Error creating dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to create dynamic group rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.put("/api/dynamic-group-rules/:id", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const updatedRule = await storage.updateDynamicGroupRule(id, req.body);
|
||||
|
||||
if (!updatedRule) {
|
||||
return res.status(404).json({ error: 'Dynamic group rule not found' });
|
||||
}
|
||||
|
||||
res.json(updatedRule);
|
||||
} catch (error) {
|
||||
console.error('Error updating dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to update dynamic group rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/dynamic-group-rules/:id", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const deleted = await storage.deleteDynamicGroupRule(id);
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Dynamic group rule not found' });
|
||||
}
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('Error deleting dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to delete dynamic group rule' });
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic Group Conditions routes
|
||||
app.get("/api/dynamic-group-rules/:ruleId/conditions", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const conditions = await storage.getConditionsByRuleId(ruleId);
|
||||
res.json(conditions);
|
||||
} catch (error) {
|
||||
console.error('Error fetching conditions for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch conditions' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/dynamic-group-rules/:ruleId/conditions", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const conditionData = { ...req.body, ruleId };
|
||||
const condition = await storage.createDynamicGroupCondition(conditionData);
|
||||
res.status(201).json(condition);
|
||||
} catch (error) {
|
||||
console.error('Error creating condition for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to create condition' });
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic Group Rule Connections routes
|
||||
app.get("/api/dynamic-group-rules/:ruleId/connections", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const connections = await storage.getRuleConnectionsByRuleId(ruleId);
|
||||
res.json(connections);
|
||||
} catch (error) {
|
||||
console.error('Error fetching connections for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch connections' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/dynamic-group-rules/:ruleId/connections", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const connectionData = { ...req.body, ruleId };
|
||||
const connection = await storage.createDynamicGroupRuleConnection(connectionData);
|
||||
res.status(201).json(connection);
|
||||
} catch (error) {
|
||||
console.error('Error creating connection for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to create connection' });
|
||||
}
|
||||
});
|
||||
|
||||
// Schedule Rules routes
|
||||
app.get("/api/schedule-rules", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const scheduleRules = await storage.getAllScheduleRules();
|
||||
res.json(scheduleRules);
|
||||
} catch (error) {
|
||||
console.error('Error fetching schedule rules:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch schedule rules' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/schedule-rules/:id", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const scheduleRule = await storage.getScheduleRule(id);
|
||||
|
||||
if (!scheduleRule) {
|
||||
return res.status(404).json({ error: 'Schedule rule not found' });
|
||||
}
|
||||
|
||||
res.json(scheduleRule);
|
||||
} catch (error) {
|
||||
console.error('Error fetching schedule rule:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch schedule rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/dynamic-group-rules/:ruleId/schedules", requirePermission(PERMISSIONS.VIEW_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const schedules = await storage.getScheduleRulesByRuleId(ruleId);
|
||||
res.json(schedules);
|
||||
} catch (error) {
|
||||
console.error('Error fetching schedules for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch schedules' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/schedule-rules", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const scheduleRule = await storage.createScheduleRule(req.body);
|
||||
res.status(201).json(scheduleRule);
|
||||
} catch (error) {
|
||||
console.error('Error creating schedule rule:', error);
|
||||
res.status(500).json({ error: 'Failed to create schedule rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/dynamic-group-rules/:ruleId/schedules", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const ruleId = Number(req.params.ruleId);
|
||||
const scheduleData = { ...req.body, ruleId };
|
||||
const schedule = await storage.createScheduleRule(scheduleData);
|
||||
res.status(201).json(schedule);
|
||||
} catch (error) {
|
||||
console.error('Error creating schedule for dynamic group rule:', error);
|
||||
res.status(500).json({ error: 'Failed to create schedule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.put("/api/schedule-rules/:id", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const updatedRule = await storage.updateScheduleRule(id, req.body);
|
||||
|
||||
if (!updatedRule) {
|
||||
return res.status(404).json({ error: 'Schedule rule not found' });
|
||||
}
|
||||
|
||||
res.json(updatedRule);
|
||||
} catch (error) {
|
||||
console.error('Error updating schedule rule:', error);
|
||||
res.status(500).json({ error: 'Failed to update schedule rule' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/schedule-rules/:id", requirePermission(PERMISSIONS.MANAGE_DYNAMIC_GROUPS, { allowApiToken: true }), async (req, res) => {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const deleted = await storage.deleteScheduleRule(id);
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Schedule rule not found' });
|
||||
}
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('Error deleting schedule rule:', error);
|
||||
res.status(500).json({ error: 'Failed to delete schedule rule' });
|
||||
}
|
||||
});
|
||||
|
||||
const httpServer = createServer(app);
|
||||
|
||||
return httpServer;
|
||||
|
||||
@@ -507,6 +507,14 @@ export type MoveComputer = z.infer<typeof moveComputerSchema>;
|
||||
export type MoveUser = z.infer<typeof moveUserSchema>;
|
||||
export type AddToGroup = z.infer<typeof addToGroupSchema>;
|
||||
export type RemoveFromGroup = z.infer<typeof removeFromGroupSchema>;
|
||||
export type DynamicGroupRule = typeof dynamicGroupRules.$inferSelect;
|
||||
export type InsertDynamicGroupRule = z.infer<typeof insertDynamicGroupRuleSchema>;
|
||||
export type DynamicGroupCondition = typeof dynamicGroupConditions.$inferSelect;
|
||||
export type InsertDynamicGroupCondition = z.infer<typeof insertDynamicGroupConditionSchema>;
|
||||
export type ScheduleRule = typeof scheduleRules.$inferSelect;
|
||||
export type InsertScheduleRule = z.infer<typeof insertScheduleRuleSchema>;
|
||||
export type ScheduleFrequency = typeof scheduleFrequencies[number];
|
||||
export type Weekday = typeof weekdays[number];
|
||||
|
||||
|
||||
// LDAP Query Builder schemas
|
||||
|
||||
Reference in New Issue
Block a user