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:
alphaeusmote
2025-04-10 03:43:08 +00:00
4 changed files with 806 additions and 0 deletions
@@ -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;
+214
View File
@@ -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';
}
};
+209
View File
@@ -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;
+8
View File
@@ -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