From e8d48376a09a4f9fce5dffe1127a32c810d85f0c Mon Sep 17 00:00:00 2001 From: Arunavo Ray Date: Sun, 15 Jun 2025 12:56:25 +0530 Subject: [PATCH] Updated Automation & Maintainence --- src/components/config/AutomationSettings.tsx | 335 +++++++++++++++++++ src/components/config/ConfigTabs.tsx | 210 +++++------- 2 files changed, 413 insertions(+), 132 deletions(-) create mode 100644 src/components/config/AutomationSettings.tsx diff --git a/src/components/config/AutomationSettings.tsx b/src/components/config/AutomationSettings.tsx new file mode 100644 index 0000000..f2f1efd --- /dev/null +++ b/src/components/config/AutomationSettings.tsx @@ -0,0 +1,335 @@ +import React, { useEffect } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { Badge } from "@/components/ui/badge"; +import { + Clock, + Database, + RefreshCw, + Calendar, + Activity, + Zap, + Info +} from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import type { ScheduleConfig, DatabaseCleanupConfig } from "@/types/config"; +import { formatDate } from "@/lib/utils"; +import { cn } from "@/lib/utils"; + +interface AutomationSettingsProps { + scheduleConfig: ScheduleConfig; + cleanupConfig: DatabaseCleanupConfig; + onScheduleChange: (config: ScheduleConfig) => void; + onCleanupChange: (config: DatabaseCleanupConfig) => void; + isAutoSavingSchedule?: boolean; + isAutoSavingCleanup?: boolean; +} + +const scheduleIntervals = [ + { label: "Every hour", value: 3600 }, + { label: "Every 2 hours", value: 7200 }, + { label: "Every 4 hours", value: 14400 }, + { label: "Every 8 hours", value: 28800 }, + { label: "Every 12 hours", value: 43200 }, + { label: "Daily", value: 86400 }, + { label: "Every 2 days", value: 172800 }, + { label: "Weekly", value: 604800 }, +]; + +const retentionPeriods = [ + { label: "1 day", value: 86400 }, + { label: "3 days", value: 259200 }, + { label: "1 week", value: 604800 }, + { label: "2 weeks", value: 1209600 }, + { label: "1 month", value: 2592000 }, + { label: "2 months", value: 5184000 }, + { label: "3 months", value: 7776000 }, +]; + +function getCleanupInterval(retentionSeconds: number): number { + const days = retentionSeconds / 86400; + if (days <= 1) return 21600; // 6 hours + if (days <= 3) return 43200; // 12 hours + if (days <= 7) return 86400; // 24 hours + if (days <= 30) return 172800; // 48 hours + return 604800; // 1 week +} + +function getCleanupFrequencyText(retentionSeconds: number): string { + const days = retentionSeconds / 86400; + if (days <= 1) return "every 6 hours"; + if (days <= 3) return "every 12 hours"; + if (days <= 7) return "daily"; + if (days <= 30) return "every 2 days"; + return "weekly"; +} + +export function AutomationSettings({ + scheduleConfig, + cleanupConfig, + onScheduleChange, + onCleanupChange, + isAutoSavingSchedule, + isAutoSavingCleanup, +}: AutomationSettingsProps) { + // Update nextRun for cleanup when settings change + useEffect(() => { + if (cleanupConfig.enabled && !cleanupConfig.nextRun) { + const cleanupInterval = getCleanupInterval(cleanupConfig.retentionDays); + const nextRun = new Date(Date.now() + cleanupInterval * 1000); + onCleanupChange({ ...cleanupConfig, nextRun }); + } + }, [cleanupConfig.enabled, cleanupConfig.retentionDays]); + + return ( + + + + + Automation & Maintenance + + + + +
+ {/* Automatic Mirroring Section */} +
+
+

+ + Automatic Mirroring +

+ {isAutoSavingSchedule && ( + + )} +
+ +
+
+ + onScheduleChange({ ...scheduleConfig, enabled: !!checked }) + } + /> +
+ +

+ Periodically check GitHub for changes and mirror them to Gitea +

+
+
+ + {scheduleConfig.enabled && ( +
+
+ + +
+
+ )} + +
+
+ + + Last sync + + + {scheduleConfig.lastRun + ? formatDate(scheduleConfig.lastRun) + : "Never"} + +
+ {scheduleConfig.enabled && scheduleConfig.nextRun && ( +
+ + + Next sync + + + {formatDate(scheduleConfig.nextRun)} + +
+ )} +
+
+
+ + {/* Database Cleanup Section */} +
+
+

+ + Database Maintenance +

+ {isAutoSavingCleanup && ( + + )} +
+ +
+
+ + onCleanupChange({ ...cleanupConfig, enabled: !!checked }) + } + /> +
+ +

+ Remove old activity logs and events to optimize storage +

+
+
+ + {cleanupConfig.enabled && ( +
+
+ + + {cleanupConfig.enabled && ( +

+ Cleanup runs {getCleanupFrequencyText(cleanupConfig.retentionDays)} +

+ )} +
+
+ )} + +
+
+ + + Last cleanup + + + {cleanupConfig.lastRun + ? formatDate(cleanupConfig.lastRun) + : "Never"} + +
+ {cleanupConfig.enabled && cleanupConfig.nextRun && ( +
+ + + Next cleanup + + + {cleanupConfig.nextRun + ? formatDate(cleanupConfig.nextRun) + : "Calculating..."} + +
+ )} +
+
+
+
+ +
+
+ +
+

+ Background Operations +

+

+ These automated tasks run in the background to keep your mirrors up-to-date and maintain optimal database performance. + Choose intervals that match your workflow and repository update frequency. +

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/config/ConfigTabs.tsx b/src/components/config/ConfigTabs.tsx index 1205c69..2218dcb 100644 --- a/src/components/config/ConfigTabs.tsx +++ b/src/components/config/ConfigTabs.tsx @@ -1,8 +1,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'; import { GitHubConfigForm } from './GitHubConfigForm'; import { GiteaConfigForm } from './GiteaConfigForm'; -import { ScheduleConfigForm } from './ScheduleConfigForm'; -import { DatabaseCleanupConfigForm } from './DatabaseCleanupConfigForm'; +import { AutomationSettings } from './AutomationSettings'; import type { ConfigApiResponse, GiteaConfig, @@ -80,14 +79,10 @@ export function ConfigTabs() { const [isAutoSavingCleanup, setIsAutoSavingCleanup] = useState(false); const [isAutoSavingGitHub, setIsAutoSavingGitHub] = useState(false); const [isAutoSavingGitea, setIsAutoSavingGitea] = useState(false); - const [isAutoSavingMirrorOptions, setIsAutoSavingMirrorOptions] = useState(false); - const [isAutoSavingAdvancedOptions, setIsAutoSavingAdvancedOptions] = useState(false); const autoSaveScheduleTimeoutRef = useRef(null); const autoSaveCleanupTimeoutRef = useRef(null); const autoSaveGitHubTimeoutRef = useRef(null); const autoSaveGiteaTimeoutRef = useRef(null); - const autoSaveMirrorOptionsTimeoutRef = useRef(null); - const autoSaveAdvancedOptionsTimeoutRef = useRef(null); const isConfigFormValid = (): boolean => { const { githubConfig, giteaConfig } = config; @@ -362,102 +357,74 @@ export function ConfigTabs() { }, 500); // 500ms debounce }, [user?.id, config.githubConfig, config.scheduleConfig, config.cleanupConfig]); - // Auto-save function specifically for mirror options changes + // Auto-save function for mirror options (handled within GitHub config) const autoSaveMirrorOptions = useCallback(async (mirrorOptions: MirrorOptions) => { if (!user?.id) return; - // Clear any existing timeout - if (autoSaveMirrorOptionsTimeoutRef.current) { - clearTimeout(autoSaveMirrorOptionsTimeoutRef.current); - } + const reqPayload: SaveConfigApiRequest = { + userId: user.id!, + githubConfig: config.githubConfig, + giteaConfig: config.giteaConfig, + scheduleConfig: config.scheduleConfig, + cleanupConfig: config.cleanupConfig, + mirrorOptions: mirrorOptions, + advancedOptions: config.advancedOptions, + }; - // Debounce the auto-save to prevent excessive API calls - autoSaveMirrorOptionsTimeoutRef.current = setTimeout(async () => { - setIsAutoSavingMirrorOptions(true); + try { + const response = await fetch('/api/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reqPayload), + }); + const result: SaveConfigApiResponse = await response.json(); - const reqPayload: SaveConfigApiRequest = { - userId: user.id!, - githubConfig: config.githubConfig, - giteaConfig: config.giteaConfig, - scheduleConfig: config.scheduleConfig, - cleanupConfig: config.cleanupConfig, - mirrorOptions: mirrorOptions, - advancedOptions: config.advancedOptions, - }; - - try { - const response = await fetch('/api/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(reqPayload), - }); - const result: SaveConfigApiResponse = await response.json(); - - if (result.success) { - // Silent success - no toast for auto-save - // Invalidate config cache so other components get fresh data - invalidateConfigCache(); - } else { - showErrorToast( - `Auto-save failed: ${result.message || 'Unknown error'}`, - toast - ); - } - } catch (error) { - showErrorToast(error, toast); - } finally { - setIsAutoSavingMirrorOptions(false); + if (result.success) { + invalidateConfigCache(); + } else { + showErrorToast( + `Auto-save failed: ${result.message || 'Unknown error'}`, + toast + ); } - }, 500); // 500ms debounce + } catch (error) { + showErrorToast(error, toast); + } }, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.advancedOptions]); - // Auto-save function specifically for advanced options changes + // Auto-save function for advanced options (handled within GitHub config) const autoSaveAdvancedOptions = useCallback(async (advancedOptions: AdvancedOptions) => { if (!user?.id) return; - // Clear any existing timeout - if (autoSaveAdvancedOptionsTimeoutRef.current) { - clearTimeout(autoSaveAdvancedOptionsTimeoutRef.current); - } + const reqPayload: SaveConfigApiRequest = { + userId: user.id!, + githubConfig: config.githubConfig, + giteaConfig: config.giteaConfig, + scheduleConfig: config.scheduleConfig, + cleanupConfig: config.cleanupConfig, + mirrorOptions: config.mirrorOptions, + advancedOptions: advancedOptions, + }; - // Debounce the auto-save to prevent excessive API calls - autoSaveAdvancedOptionsTimeoutRef.current = setTimeout(async () => { - setIsAutoSavingAdvancedOptions(true); + try { + const response = await fetch('/api/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reqPayload), + }); + const result: SaveConfigApiResponse = await response.json(); - const reqPayload: SaveConfigApiRequest = { - userId: user.id!, - githubConfig: config.githubConfig, - giteaConfig: config.giteaConfig, - scheduleConfig: config.scheduleConfig, - cleanupConfig: config.cleanupConfig, - mirrorOptions: config.mirrorOptions, - advancedOptions: advancedOptions, - }; - - try { - const response = await fetch('/api/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(reqPayload), - }); - const result: SaveConfigApiResponse = await response.json(); - - if (result.success) { - // Silent success - no toast for auto-save - // Invalidate config cache so other components get fresh data - invalidateConfigCache(); - } else { - showErrorToast( - `Auto-save failed: ${result.message || 'Unknown error'}`, - toast - ); - } - } catch (error) { - showErrorToast(error, toast); - } finally { - setIsAutoSavingAdvancedOptions(false); + if (result.success) { + invalidateConfigCache(); + } else { + showErrorToast( + `Auto-save failed: ${result.message || 'Unknown error'}`, + toast + ); } - }, 500); // 500ms debounce + } catch (error) { + showErrorToast(error, toast); + } }, [user?.id, config.githubConfig, config.giteaConfig, config.scheduleConfig, config.cleanupConfig, config.mirrorOptions]); // Cleanup timeouts on unmount @@ -475,12 +442,6 @@ export function ConfigTabs() { if (autoSaveGiteaTimeoutRef.current) { clearTimeout(autoSaveGiteaTimeoutRef.current); } - if (autoSaveMirrorOptionsTimeoutRef.current) { - clearTimeout(autoSaveMirrorOptionsTimeoutRef.current); - } - if (autoSaveAdvancedOptionsTimeoutRef.current) { - clearTimeout(autoSaveAdvancedOptionsTimeoutRef.current); - } }; }, []); @@ -569,20 +530,19 @@ export function ConfigTabs() { - {/* Schedule & Database Cleanup - Side by side */} -
-
+ {/* Automation & Maintenance - Full width */} +
+ +
- + - +
-
-
- + - +
@@ -692,35 +652,21 @@ export function ConfigTabs() { />
- {/* Schedule & Database Cleanup - Side by side */} -
- - setConfig(prev => ({ - ...prev, - scheduleConfig: - typeof update === 'function' - ? update(prev.scheduleConfig) - : update, - })) - } - onAutoSave={autoSaveScheduleConfig} - isAutoSaving={isAutoSavingSchedule} - /> - - setConfig(prev => ({ - ...prev, - cleanupConfig: - typeof update === 'function' - ? update(prev.cleanupConfig) - : update, - })) - } - onAutoSave={autoSaveCleanupConfig} - isAutoSaving={isAutoSavingCleanup} + {/* Automation & Maintenance - Full width */} +
+ { + setConfig(prev => ({ ...prev, scheduleConfig: newConfig })); + autoSaveScheduleConfig(newConfig); + }} + onCleanupChange={(newConfig) => { + setConfig(prev => ({ ...prev, cleanupConfig: newConfig })); + autoSaveCleanupConfig(newConfig); + }} + isAutoSavingSchedule={isAutoSavingSchedule} + isAutoSavingCleanup={isAutoSavingCleanup} />