mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-04-10 04:57:44 +03:00
cleanup: improve orphaned repo handling
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Clock,
|
||||
Database,
|
||||
@@ -16,7 +17,8 @@ import {
|
||||
Calendar,
|
||||
Activity,
|
||||
Zap,
|
||||
Info
|
||||
Info,
|
||||
Archive,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -120,13 +122,13 @@ export function AutomationSettings({
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Automatic Syncing Section */}
|
||||
<div className="space-y-4 p-4 border border-border rounded-lg bg-card/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 text-primary" />
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Automatic Syncing Section */}
|
||||
<div className="space-y-4 p-4 border border-border rounded-lg bg-card/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 text-primary" />
|
||||
Automatic Syncing
|
||||
</h3>
|
||||
{isAutoSavingSchedule && (
|
||||
@@ -139,6 +141,7 @@ export function AutomationSettings({
|
||||
<Checkbox
|
||||
id="enable-auto-mirror"
|
||||
checked={scheduleConfig.enabled}
|
||||
className="mt-1.25"
|
||||
onCheckedChange={(checked) =>
|
||||
onScheduleChange({ ...scheduleConfig, enabled: !!checked })
|
||||
}
|
||||
@@ -218,17 +221,17 @@ export function AutomationSettings({
|
||||
Enable automatic syncing to schedule periodic repository updates
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Database Cleanup Section */}
|
||||
<div className="space-y-4 p-4 border border-border rounded-lg bg-card/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||
<Database className="h-4 w-4 text-primary" />
|
||||
Database Maintenance
|
||||
</h3>
|
||||
{/* Database Cleanup Section */}
|
||||
<div className="space-y-4 p-4 border border-border rounded-lg bg-card/50">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||
<Database className="h-4 w-4 text-primary" />
|
||||
Database Maintenance
|
||||
</h3>
|
||||
{isAutoSavingCleanup && (
|
||||
<Activity className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
@@ -239,6 +242,7 @@ export function AutomationSettings({
|
||||
<Checkbox
|
||||
id="enable-auto-cleanup"
|
||||
checked={cleanupConfig.enabled}
|
||||
className="mt-1.25"
|
||||
onCheckedChange={(checked) =>
|
||||
onCleanupChange({ ...cleanupConfig, enabled: !!checked })
|
||||
}
|
||||
@@ -257,8 +261,8 @@ export function AutomationSettings({
|
||||
</div>
|
||||
|
||||
{cleanupConfig.enabled && (
|
||||
<div className="ml-6 space-y-3">
|
||||
<div>
|
||||
<div className="ml-6 space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="retention-period" className="text-sm flex items-center gap-2">
|
||||
Data retention period
|
||||
<TooltipProvider>
|
||||
@@ -304,6 +308,7 @@ export function AutomationSettings({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -334,13 +339,108 @@ export function AutomationSettings({
|
||||
) : (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Enable automatic cleanup to optimize database storage
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
|
||||
{/* Repository Cleanup Section */}
|
||||
<div className="space-y-4 p-4 border border-border rounded-lg bg-card/50 md:col-span-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center gap-2">
|
||||
<Archive className="h-4 w-4 text-primary" />
|
||||
Repository Cleanup (orphaned mirrors)
|
||||
</h3>
|
||||
{isAutoSavingCleanup && (
|
||||
<Activity className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Checkbox
|
||||
id="cleanup-handle-orphans"
|
||||
checked={Boolean(cleanupConfig.deleteIfNotInGitHub)}
|
||||
className="mt-1.25"
|
||||
onCheckedChange={(checked) =>
|
||||
onCleanupChange({
|
||||
...cleanupConfig,
|
||||
deleteIfNotInGitHub: Boolean(checked),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label
|
||||
htmlFor="cleanup-handle-orphans"
|
||||
className="text-sm font-normal cursor-pointer"
|
||||
>
|
||||
Handle orphaned repositories automatically
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Keep your Gitea backups when GitHub repos disappear. Archive is the safest option—it preserves data and disables automatic syncs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{cleanupConfig.deleteIfNotInGitHub && (
|
||||
<div className="space-y-3 ml-6">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="cleanup-orphaned-action" className="text-sm font-medium">
|
||||
Action for orphaned repositories
|
||||
</Label>
|
||||
<Select
|
||||
value={cleanupConfig.orphanedRepoAction ?? "archive"}
|
||||
onValueChange={(value) =>
|
||||
onCleanupChange({
|
||||
...cleanupConfig,
|
||||
orphanedRepoAction: value as DatabaseCleanupConfig["orphanedRepoAction"],
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="cleanup-orphaned-action">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="archive">Archive (preserve data)</SelectItem>
|
||||
<SelectItem value="skip">Skip (leave as-is)</SelectItem>
|
||||
<SelectItem value="delete">Delete from Gitea</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Archive renames mirror backups with an <code>archived-</code> prefix and disables automatic syncs—use Manual Sync when you want to refresh.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label
|
||||
htmlFor="cleanup-dry-run"
|
||||
className="text-sm font-normal cursor-pointer"
|
||||
>
|
||||
Dry run (log only)
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground max-w-xl">
|
||||
When enabled, cleanup logs the planned action without modifying repositories.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="cleanup-dry-run"
|
||||
checked={Boolean(cleanupConfig.dryRun)}
|
||||
onCheckedChange={(checked) =>
|
||||
onCleanupChange({
|
||||
...cleanupConfig,
|
||||
dryRun: Boolean(checked),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ export function ConfigTabs() {
|
||||
cleanupConfig: {
|
||||
enabled: false, // Don't set defaults here - will be loaded from API
|
||||
retentionDays: 0, // Will be replaced with actual value from API
|
||||
deleteIfNotInGitHub: true,
|
||||
orphanedRepoAction: "archive",
|
||||
dryRun: false,
|
||||
deleteFromGitea: false,
|
||||
protectedRepos: [],
|
||||
},
|
||||
mirrorOptions: {
|
||||
mirrorReleases: false,
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Checkbox } from "../ui/checkbox";
|
||||
import type { DatabaseCleanupConfig } from "@/types/config";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import { RefreshCw, Database } from "lucide-react";
|
||||
|
||||
interface DatabaseCleanupConfigFormProps {
|
||||
config: DatabaseCleanupConfig;
|
||||
setConfig: React.Dispatch<React.SetStateAction<DatabaseCleanupConfig>>;
|
||||
onAutoSave?: (config: DatabaseCleanupConfig) => void;
|
||||
isAutoSaving?: boolean;
|
||||
}
|
||||
|
||||
|
||||
// Helper to calculate cleanup interval in hours (should match backend logic)
|
||||
function calculateCleanupInterval(retentionSeconds: number): number {
|
||||
const retentionDays = retentionSeconds / (24 * 60 * 60);
|
||||
if (retentionDays <= 1) {
|
||||
return 6;
|
||||
} else if (retentionDays <= 3) {
|
||||
return 12;
|
||||
} else if (retentionDays <= 7) {
|
||||
return 24;
|
||||
} else if (retentionDays <= 30) {
|
||||
return 48;
|
||||
} else {
|
||||
return 168;
|
||||
}
|
||||
}
|
||||
|
||||
export function DatabaseCleanupConfigForm({
|
||||
config,
|
||||
setConfig,
|
||||
onAutoSave,
|
||||
isAutoSaving = false,
|
||||
}: DatabaseCleanupConfigFormProps) {
|
||||
// Optimistically update nextRun when enabled or retention changes
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||
) => {
|
||||
const { name, value, type } = e.target;
|
||||
let newConfig = {
|
||||
...config,
|
||||
[name]: type === "checkbox" ? (e.target as HTMLInputElement).checked : value,
|
||||
};
|
||||
|
||||
// If enabling or changing retention, recalculate nextRun
|
||||
if (
|
||||
(name === "enabled" && (e.target as HTMLInputElement).checked) ||
|
||||
(name === "retentionDays" && config.enabled)
|
||||
) {
|
||||
const now = new Date();
|
||||
const retentionSeconds =
|
||||
name === "retentionDays"
|
||||
? Number(value)
|
||||
: Number(newConfig.retentionDays);
|
||||
const intervalHours = calculateCleanupInterval(retentionSeconds);
|
||||
const nextRun = new Date(now.getTime() + intervalHours * 60 * 60 * 1000);
|
||||
newConfig = {
|
||||
...newConfig,
|
||||
nextRun,
|
||||
};
|
||||
}
|
||||
// If disabling, clear nextRun
|
||||
if (name === "enabled" && !(e.target as HTMLInputElement).checked) {
|
||||
newConfig = {
|
||||
...newConfig,
|
||||
nextRun: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
setConfig(newConfig);
|
||||
if (onAutoSave) {
|
||||
onAutoSave(newConfig);
|
||||
}
|
||||
};
|
||||
|
||||
// Predefined retention periods (in seconds, like schedule intervals)
|
||||
const retentionOptions: { value: number; label: string }[] = [
|
||||
{ value: 86400, label: "1 day" }, // 24 * 60 * 60
|
||||
{ value: 259200, label: "3 days" }, // 3 * 24 * 60 * 60
|
||||
{ value: 604800, label: "7 days" }, // 7 * 24 * 60 * 60
|
||||
{ value: 1209600, label: "14 days" }, // 14 * 24 * 60 * 60
|
||||
{ value: 2592000, label: "30 days" }, // 30 * 24 * 60 * 60
|
||||
{ value: 5184000, label: "60 days" }, // 60 * 24 * 60 * 60
|
||||
{ value: 7776000, label: "90 days" }, // 90 * 24 * 60 * 60
|
||||
];
|
||||
|
||||
return (
|
||||
<Card className="self-start">
|
||||
<CardContent className="pt-6 relative">
|
||||
{isAutoSaving && (
|
||||
<div className="absolute top-4 right-4 flex items-center text-sm text-muted-foreground">
|
||||
<RefreshCw className="h-3 w-3 animate-spin mr-1" />
|
||||
<span className="text-xs">Auto-saving...</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id="cleanup-enabled"
|
||||
name="enabled"
|
||||
checked={config.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
handleChange({
|
||||
target: {
|
||||
name: "enabled",
|
||||
type: "checkbox",
|
||||
checked: Boolean(checked),
|
||||
value: "",
|
||||
},
|
||||
} as React.ChangeEvent<HTMLInputElement>)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="cleanup-enabled"
|
||||
className="select-none ml-2 block text-sm font-medium"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-4 w-4" />
|
||||
Enable Automatic Database Cleanup
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{config.enabled && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">
|
||||
Data Retention Period
|
||||
</label>
|
||||
|
||||
<Select
|
||||
name="retentionDays"
|
||||
value={String(config.retentionDays)}
|
||||
onValueChange={(value) =>
|
||||
handleChange({
|
||||
target: { name: "retentionDays", value },
|
||||
} as React.ChangeEvent<HTMLInputElement>)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full border border-input dark:bg-background dark:hover:bg-background">
|
||||
<SelectValue placeholder="Select retention period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-background text-foreground border border-input shadow-sm">
|
||||
{retentionOptions.map((option) => (
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value.toString()}
|
||||
className="cursor-pointer text-sm px-3 py-2 hover:bg-accent focus:bg-accent focus:text-accent-foreground"
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Activities and events older than this period will be automatically deleted.
|
||||
</p>
|
||||
<div className="mt-2 p-2 bg-muted/50 rounded-md">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>Cleanup Frequency:</strong> The cleanup process runs automatically at optimal intervals:
|
||||
shorter retention periods trigger more frequent cleanups, longer periods trigger less frequent cleanups.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-x-4">
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium mb-1">Last Cleanup</label>
|
||||
<div className="text-sm">
|
||||
{config.lastRun ? formatDate(config.lastRun) : "Never"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{config.enabled && (
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium mb-1">Next Cleanup</label>
|
||||
<div className="text-sm">
|
||||
{config.nextRun
|
||||
? formatDate(config.nextRun)
|
||||
: config.enabled
|
||||
? "Calculating..."
|
||||
: "Never"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ScheduleConfigForm } from './ScheduleConfigForm';
|
||||
import { DatabaseCleanupConfigForm } from './DatabaseCleanupConfigForm';
|
||||
import { Separator } from '../ui/separator';
|
||||
import type { ScheduleConfig, DatabaseCleanupConfig } from '@/types/config';
|
||||
|
||||
interface ScheduleAndCleanupFormProps {
|
||||
scheduleConfig: ScheduleConfig;
|
||||
cleanupConfig: DatabaseCleanupConfig;
|
||||
setScheduleConfig: (update: ScheduleConfig | ((prev: ScheduleConfig) => ScheduleConfig)) => void;
|
||||
setCleanupConfig: (update: DatabaseCleanupConfig | ((prev: DatabaseCleanupConfig) => DatabaseCleanupConfig)) => void;
|
||||
onAutoSaveSchedule?: (config: ScheduleConfig) => Promise<void>;
|
||||
onAutoSaveCleanup?: (config: DatabaseCleanupConfig) => Promise<void>;
|
||||
isAutoSavingSchedule?: boolean;
|
||||
isAutoSavingCleanup?: boolean;
|
||||
}
|
||||
|
||||
export function ScheduleAndCleanupForm({
|
||||
scheduleConfig,
|
||||
cleanupConfig,
|
||||
setScheduleConfig,
|
||||
setCleanupConfig,
|
||||
onAutoSaveSchedule,
|
||||
onAutoSaveCleanup,
|
||||
isAutoSavingSchedule,
|
||||
isAutoSavingCleanup,
|
||||
}: ScheduleAndCleanupFormProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<ScheduleConfigForm
|
||||
config={scheduleConfig}
|
||||
setConfig={setScheduleConfig}
|
||||
onAutoSave={onAutoSaveSchedule}
|
||||
isAutoSaving={isAutoSavingSchedule}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
<DatabaseCleanupConfigForm
|
||||
config={cleanupConfig}
|
||||
setConfig={setCleanupConfig}
|
||||
onAutoSave={onAutoSaveCleanup}
|
||||
isAutoSaving={isAutoSavingCleanup}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user