feat(settings): otomatik docker image temizliği ekle

Docker image temizliği için yapılandırılabilir zamanlayıcı ve manuel
tetikleme özelliği eklenmiştir. Kullanıcılar saat, gün veya hafta bazlı
periyotlar belirleyebilir ve anlık temizlik yapabilir.
This commit is contained in:
2026-02-03 09:34:37 +00:00
parent b04ac03739
commit 1f90ce54d4
6 changed files with 198 additions and 9 deletions

View File

@@ -3,6 +3,8 @@ import { apiClient } from "./client";
export interface SettingsResponse {
webhookToken: string;
webhookSecret: string;
cleanupIntervalValue?: number;
cleanupIntervalUnit?: "saat" | "gün" | "hafta";
updatedAt: string;
}
@@ -20,3 +22,13 @@ export async function rotateWebhookSecret(): Promise<SettingsResponse> {
const { data } = await apiClient.post("/settings/secret/rotate");
return data as SettingsResponse;
}
export async function saveCleanupInterval(value: number, unit: "saat" | "gün" | "hafta") {
const { data } = await apiClient.post("/settings/cleanup-interval", { value, unit });
return data as SettingsResponse;
}
export async function cleanupImages(): Promise<{ success: boolean }> {
const { data } = await apiClient.post("/settings/cleanup-images");
return data as { success: boolean };
}

View File

@@ -1,10 +1,20 @@
import { useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCopy, faEye, faEyeSlash, faRotate } from "@fortawesome/free-solid-svg-icons";
import { faBroom, faCopy, faEye, faEyeSlash, faFloppyDisk, faRotate } from "@fortawesome/free-solid-svg-icons";
import { toast } from "sonner";
import { Button } from "../components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
import { fetchSettings, rotateWebhookSecret, rotateWebhookToken, SettingsResponse } from "../api/settings";
import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
import {
cleanupImages,
fetchSettings,
rotateWebhookSecret,
rotateWebhookToken,
saveCleanupInterval,
SettingsResponse
} from "../api/settings";
export function SettingsPage() {
const [settings, setSettings] = useState<SettingsResponse | null>(null);
@@ -13,10 +23,22 @@ export function SettingsPage() {
const [rotatingSecret, setRotatingSecret] = useState(false);
const [showToken, setShowToken] = useState(false);
const [showSecret, setShowSecret] = useState(false);
const [cleanupValue, setCleanupValue] = useState<string>("1");
const [cleanupUnit, setCleanupUnit] = useState<"saat" | "gün" | "hafta">("hafta");
const [savingCleanup, setSavingCleanup] = useState(false);
const [cleaning, setCleaning] = useState(false);
useEffect(() => {
fetchSettings()
.then((data) => setSettings(data))
.then((data) => {
setSettings(data);
if (data.cleanupIntervalValue) {
setCleanupValue(String(data.cleanupIntervalValue));
}
if (data.cleanupIntervalUnit) {
setCleanupUnit(data.cleanupIntervalUnit);
}
})
.catch(() => toast.error("Settings yüklenemedi"))
.finally(() => setLoading(false));
}, []);
@@ -77,6 +99,36 @@ export function SettingsPage() {
}
};
const handleSaveCleanup = async () => {
const value = Number(cleanupValue);
if (!value || Number.isNaN(value) || value < 1) {
toast.error("Geçerli bir periyot girin");
return;
}
setSavingCleanup(true);
try {
const data = await saveCleanupInterval(value, cleanupUnit);
setSettings((prev) => (prev ? { ...prev, ...data } : data));
toast.success("Temizlik periyodu kaydedildi");
} catch {
toast.error("Periyot kaydedilemedi");
} finally {
setSavingCleanup(false);
}
};
const handleCleanupImages = async () => {
setCleaning(true);
try {
await cleanupImages();
toast.success("Kullanılmayan image'lar temizlendi");
} catch {
toast.error("Temizlik başarısız");
} finally {
setCleaning(false);
}
};
if (loading) {
return (
<div className="rounded-md border border-border bg-muted/30 px-4 py-6 text-sm text-muted-foreground">
@@ -138,6 +190,58 @@ export function SettingsPage() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Image Temizliği</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 sm:grid-cols-[1fr_auto] sm:items-end">
<div className="space-y-2">
<Label htmlFor="cleanupValue">Temizlik Periyodu</Label>
<div className="flex items-center gap-2">
<Input
id="cleanupValue"
type="number"
min="1"
value={cleanupValue}
onChange={(e) => setCleanupValue(e.target.value)}
className="bg-white"
/>
<Select value={cleanupUnit} onValueChange={(value) => setCleanupUnit(value as typeof cleanupUnit)}>
<SelectTrigger className="w-32">
<SelectValue placeholder="Birim" />
</SelectTrigger>
<SelectContent>
<SelectItem value="saat">saat</SelectItem>
<SelectItem value="gün">gün</SelectItem>
<SelectItem value="hafta">hafta</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-wrap items-center gap-2 sm:justify-end">
<Button
variant="outline"
onClick={handleSaveCleanup}
disabled={savingCleanup}
className="bg-white"
>
<FontAwesomeIcon icon={faFloppyDisk} className="mr-2 h-4 w-4" />
Kaydet
</Button>
<Button
onClick={handleCleanupImages}
disabled={cleaning}
className="bg-rose-100 text-rose-700 hover:bg-rose-200"
>
<FontAwesomeIcon icon={faBroom} className="mr-2 h-4 w-4" />
Clean Cache Images
</Button>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Webhook Secret</CardTitle>