mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-30 11:26:24 +02:00
feat: added periodic tasks in backend db and frontend hooks
- TODO: Add celery redbeat and create tasks dinamically in our redis
This commit is contained in:
parent
70808eb08b
commit
182f815bb7
8 changed files with 484 additions and 31 deletions
|
|
@ -1,7 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { Calendar as CalendarIcon, Edit, Plus, RefreshCw, Trash2 } from "lucide-react";
|
||||
import {
|
||||
Calendar as CalendarIcon,
|
||||
Clock,
|
||||
Edit,
|
||||
Loader2,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
|
@ -28,8 +36,17 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -64,7 +81,7 @@ export default function ConnectorsPage() {
|
|||
const searchSpaceId = params.search_space_id as string;
|
||||
const today = new Date();
|
||||
|
||||
const { connectors, isLoading, error, deleteConnector, indexConnector } =
|
||||
const { connectors, isLoading, error, deleteConnector, indexConnector, updateConnector } =
|
||||
useSearchSourceConnectors(false, parseInt(searchSpaceId));
|
||||
const [connectorToDelete, setConnectorToDelete] = useState<number | null>(null);
|
||||
const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(null);
|
||||
|
|
@ -75,6 +92,16 @@ export default function ConnectorsPage() {
|
|||
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
|
||||
|
||||
// Periodic indexing state
|
||||
const [periodicDialogOpen, setPeriodicDialogOpen] = useState(false);
|
||||
const [selectedConnectorForPeriodic, setSelectedConnectorForPeriodic] = useState<number | null>(
|
||||
null
|
||||
);
|
||||
const [periodicEnabled, setPeriodicEnabled] = useState(false);
|
||||
const [frequencyMinutes, setFrequencyMinutes] = useState<string>("1440");
|
||||
const [customFrequency, setCustomFrequency] = useState<string>("");
|
||||
const [isSavingPeriodic, setIsSavingPeriodic] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
toast.error("Failed to load connectors");
|
||||
|
|
@ -141,6 +168,84 @@ export default function ConnectorsPage() {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle opening periodic indexing dialog
|
||||
const handleOpenPeriodicDialog = (connectorId: number) => {
|
||||
const connector = connectors.find((c) => c.id === connectorId);
|
||||
if (!connector) return;
|
||||
|
||||
setSelectedConnectorForPeriodic(connectorId);
|
||||
setPeriodicEnabled(connector.periodic_indexing_enabled);
|
||||
|
||||
if (connector.indexing_frequency_minutes) {
|
||||
// Check if it's a preset value
|
||||
const presetValues = ["15", "60", "360", "720", "1440", "10080"];
|
||||
if (presetValues.includes(connector.indexing_frequency_minutes.toString())) {
|
||||
setFrequencyMinutes(connector.indexing_frequency_minutes.toString());
|
||||
setCustomFrequency("");
|
||||
} else {
|
||||
setFrequencyMinutes("custom");
|
||||
setCustomFrequency(connector.indexing_frequency_minutes.toString());
|
||||
}
|
||||
} else {
|
||||
setFrequencyMinutes("1440");
|
||||
setCustomFrequency("");
|
||||
}
|
||||
|
||||
setPeriodicDialogOpen(true);
|
||||
};
|
||||
|
||||
// Handle saving periodic indexing configuration
|
||||
const handleSavePeriodicIndexing = async () => {
|
||||
if (selectedConnectorForPeriodic === null) return;
|
||||
|
||||
const connector = connectors.find((c) => c.id === selectedConnectorForPeriodic);
|
||||
if (!connector) return;
|
||||
|
||||
setIsSavingPeriodic(true);
|
||||
try {
|
||||
// Determine the frequency value
|
||||
let frequency: number | null = null;
|
||||
if (periodicEnabled) {
|
||||
if (frequencyMinutes === "custom") {
|
||||
frequency = parseInt(customFrequency, 10);
|
||||
if (isNaN(frequency) || frequency <= 0) {
|
||||
toast.error("Please enter a valid frequency in minutes");
|
||||
setIsSavingPeriodic(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
frequency = parseInt(frequencyMinutes, 10);
|
||||
}
|
||||
}
|
||||
|
||||
await updateConnector(selectedConnectorForPeriodic, {
|
||||
periodic_indexing_enabled: periodicEnabled,
|
||||
indexing_frequency_minutes: frequency,
|
||||
});
|
||||
|
||||
toast.success(
|
||||
periodicEnabled
|
||||
? "Periodic indexing enabled successfully"
|
||||
: "Periodic indexing disabled successfully"
|
||||
);
|
||||
setPeriodicDialogOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating periodic indexing:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to update periodic indexing");
|
||||
} finally {
|
||||
setIsSavingPeriodic(false);
|
||||
setSelectedConnectorForPeriodic(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Format frequency for display
|
||||
const formatFrequency = (minutes: number): string => {
|
||||
if (minutes < 60) return `${minutes}m`;
|
||||
if (minutes < 1440) return `${Math.floor(minutes / 60)}h`;
|
||||
if (minutes < 10080) return `${Math.floor(minutes / 1440)}d`;
|
||||
return `${Math.floor(minutes / 10080)}w`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8 max-w-6xl">
|
||||
<motion.div
|
||||
|
|
@ -193,6 +298,7 @@ export default function ConnectorsPage() {
|
|||
<TableHead>Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Last Indexed</TableHead>
|
||||
<TableHead>Periodic</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
|
@ -206,6 +312,41 @@ export default function ConnectorsPage() {
|
|||
? formatDateTime(connector.last_indexed_at)
|
||||
: "Not indexable"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{connector.is_indexable ? (
|
||||
connector.periodic_indexing_enabled ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1 text-green-600 dark:text-green-400">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
{connector.indexing_frequency_minutes
|
||||
? formatFrequency(connector.indexing_frequency_minutes)
|
||||
: "Enabled"}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Runs every {connector.indexing_frequency_minutes} minutes
|
||||
{connector.next_scheduled_at && (
|
||||
<>
|
||||
<br />
|
||||
Next: {formatDateTime(connector.next_scheduled_at)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">Disabled</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
{connector.is_indexable && (
|
||||
|
|
@ -256,6 +397,25 @@ export default function ConnectorsPage() {
|
|||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
{connector.is_indexable && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleOpenPeriodicDialog(connector.id)}
|
||||
>
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="sr-only">Configure Periodic Indexing</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Configure Periodic Indexing</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
|
@ -424,6 +584,110 @@ export default function ConnectorsPage() {
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Periodic Indexing Configuration Dialog */}
|
||||
<Dialog open={periodicDialogOpen} onOpenChange={setPeriodicDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure Periodic Indexing</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set up automatic indexing at regular intervals for this connector.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-6 py-4">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="periodic-enabled" className="text-base">
|
||||
Enable Periodic Indexing
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automatically index this connector at regular intervals
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="periodic-enabled"
|
||||
checked={periodicEnabled}
|
||||
onCheckedChange={setPeriodicEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{periodicEnabled && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="frequency">Indexing Frequency</Label>
|
||||
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes}>
|
||||
<SelectTrigger id="frequency">
|
||||
<SelectValue placeholder="Select frequency" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="15">Every 15 minutes</SelectItem>
|
||||
<SelectItem value="60">Every hour</SelectItem>
|
||||
<SelectItem value="360">Every 6 hours</SelectItem>
|
||||
<SelectItem value="720">Every 12 hours</SelectItem>
|
||||
<SelectItem value="1440">Daily (24 hours)</SelectItem>
|
||||
<SelectItem value="10080">Weekly (7 days)</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{frequencyMinutes === "custom" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="custom-frequency">Custom Frequency (minutes)</Label>
|
||||
<Input
|
||||
id="custom-frequency"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="Enter minutes"
|
||||
value={customFrequency}
|
||||
onChange={(e) => setCustomFrequency(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Enter the number of minutes between each indexing run
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-lg bg-muted p-3 text-sm">
|
||||
<p className="font-medium mb-1">Preview:</p>
|
||||
<p className="text-muted-foreground">
|
||||
{frequencyMinutes === "custom" && customFrequency
|
||||
? `Will run every ${customFrequency} minutes`
|
||||
: frequencyMinutes === "15"
|
||||
? "Will run every 15 minutes"
|
||||
: frequencyMinutes === "60"
|
||||
? "Will run every hour"
|
||||
: frequencyMinutes === "360"
|
||||
? "Will run every 6 hours"
|
||||
: frequencyMinutes === "720"
|
||||
? "Will run every 12 hours"
|
||||
: frequencyMinutes === "1440"
|
||||
? "Will run daily (every 24 hours)"
|
||||
: frequencyMinutes === "10080"
|
||||
? "Will run weekly (every 7 days)"
|
||||
: "Select a frequency above"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPeriodicDialogOpen(false);
|
||||
setSelectedConnectorForPeriodic(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSavePeriodicIndexing} disabled={isSavingPeriodic}>
|
||||
{isSavingPeriodic && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Save Configuration
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue