diff --git a/application/src/components/dashboard/Sidebar.tsx b/application/src/components/dashboard/Sidebar.tsx
index 6fe582c..24b4c6d 100644
--- a/application/src/components/dashboard/Sidebar.tsx
+++ b/application/src/components/dashboard/Sidebar.tsx
@@ -117,10 +117,10 @@ export const Sidebar = ({
{t("alertsTemplates")}
-
+ handleSettingsItemClick('data-retention')}>
{t("dataRetention")}
-
+
handleSettingsItemClick('about')}>
{t("aboutSystem")}
diff --git a/application/src/components/settings/data-retention/DataRetentionSettings.tsx b/application/src/components/settings/data-retention/DataRetentionSettings.tsx
new file mode 100644
index 0000000..15c1d58
--- /dev/null
+++ b/application/src/components/settings/data-retention/DataRetentionSettings.tsx
@@ -0,0 +1,316 @@
+
+import React, { useState, useEffect } from 'react';
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Loader2, Database, Trash2, AlertTriangle, Globe, Server } from "lucide-react";
+import { useToast } from "@/hooks/use-toast";
+import { useLanguage } from "@/contexts/LanguageContext";
+import { authService } from "@/services/authService";
+import { dataRetentionService } from "@/services/dataRetentionService";
+
+interface RetentionSettings {
+ uptimeRetentionDays: number;
+ serverRetentionDays: number;
+}
+
+const DataRetentionSettings = () => {
+ const { t } = useLanguage();
+ const { toast } = useToast();
+ const [settings, setSettings] = useState({
+ uptimeRetentionDays: 30,
+ serverRetentionDays: 30
+ });
+ const [isLoading, setIsLoading] = useState(true);
+ const [isSaving, setIsSaving] = useState(false);
+ const [isUptimeShrinking, setIsUptimeShrinking] = useState(false);
+ const [isServerShrinking, setIsServerShrinking] = useState(false);
+ const [isFullShrinking, setIsFullShrinking] = useState(false);
+ const [lastCleanup, setLastCleanup] = useState(null);
+
+ // Check if user is super admin
+ const currentUser = authService.getCurrentUser();
+ const isSuperAdmin = currentUser?.role === "superadmin";
+
+ useEffect(() => {
+ if (isSuperAdmin) {
+ loadSettings();
+ }
+ }, [isSuperAdmin]);
+
+ const loadSettings = async () => {
+ try {
+ setIsLoading(true);
+ const result = await dataRetentionService.getRetentionSettings();
+ if (result) {
+ setSettings({
+ uptimeRetentionDays: result.uptimeRetentionDays || 30,
+ serverRetentionDays: result.serverRetentionDays || 30
+ });
+ setLastCleanup(result.lastCleanup);
+ }
+ } catch (error) {
+ console.error("Error loading retention settings:", error);
+ toast({
+ title: "Error",
+ description: "Failed to load retention settings",
+ variant: "destructive",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSave = async () => {
+ try {
+ setIsSaving(true);
+ await dataRetentionService.updateRetentionSettings(settings);
+ toast({
+ title: "Settings saved",
+ description: "Data retention settings have been updated",
+ });
+ } catch (error) {
+ console.error("Error saving retention settings:", error);
+ toast({
+ title: "Error",
+ description: "Failed to save retention settings",
+ variant: "destructive",
+ });
+ } finally {
+ setIsSaving(false);
+ }
+ };
+
+ const handleUptimeShrink = async () => {
+ try {
+ setIsUptimeShrinking(true);
+ const result = await dataRetentionService.manualUptimeCleanup();
+
+ toast({
+ title: "Uptime cleanup completed",
+ description: `Deleted ${result.deletedRecords} old uptime records`,
+ });
+
+ // Reload settings to get updated last cleanup time
+ await loadSettings();
+ } catch (error) {
+ console.error("Error during uptime cleanup:", error);
+ toast({
+ title: "Error",
+ description: "Failed to perform uptime data cleanup",
+ variant: "destructive",
+ });
+ } finally {
+ setIsUptimeShrinking(false);
+ }
+ };
+
+ const handleServerShrink = async () => {
+ try {
+ setIsServerShrinking(true);
+ const result = await dataRetentionService.manualServerCleanup();
+
+ toast({
+ title: "Server cleanup completed",
+ description: `Deleted ${result.deletedRecords} old server records`,
+ });
+
+ // Reload settings to get updated last cleanup time
+ await loadSettings();
+ } catch (error) {
+ console.error("Error during server cleanup:", error);
+ toast({
+ title: "Error",
+ description: "Failed to perform server data cleanup",
+ variant: "destructive",
+ });
+ } finally {
+ setIsServerShrinking(false);
+ }
+ };
+
+ const handleFullShrink = async () => {
+ try {
+ setIsFullShrinking(true);
+ const result = await dataRetentionService.manualCleanup();
+
+ toast({
+ title: "Database cleanup completed",
+ description: `Deleted ${result.deletedRecords} old records`,
+ });
+
+ // Reload settings to get updated last cleanup time
+ await loadSettings();
+ } catch (error) {
+ console.error("Error during manual cleanup:", error);
+ toast({
+ title: "Error",
+ description: "Failed to perform database cleanup",
+ variant: "destructive",
+ });
+ } finally {
+ setIsFullShrinking(false);
+ }
+ };
+
+ // Show permission notice for admin users
+ if (!isSuperAdmin) {
+ return (
+
+
+
+
+
+ {t("dataRetention", "settings")}
+
+
+
+
+
+
+ Permission Notice: As an admin user, you do not have access to data retention settings. These settings can only be accessed and modified by Super Admins.
+
+
+
+
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+
+ Loading retention settings...
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ Data Retention Settings
+
+
+ Configure how long monitoring data is kept in the system
+
+
+
+
+
+
Uptime Monitoring Retention (days)
+
setSettings(prev => ({
+ ...prev,
+ uptimeRetentionDays: parseInt(e.target.value) || 30
+ }))}
+ className="mt-1"
+ />
+
+ Service uptime and incident data older than this will be automatically deleted
+
+
+
+
+
Server Monitoring Retention (days)
+
setSettings(prev => ({
+ ...prev,
+ serverRetentionDays: parseInt(e.target.value) || 30
+ }))}
+ className="mt-1"
+ />
+
+ Server metrics and process data older than this will be automatically deleted
+
+
+
+
+ {lastCleanup && (
+
+
+
+ Last automatic cleanup: {new Date(lastCleanup).toLocaleString()}
+
+
+ )}
+
+
+
+
+ {isUptimeShrinking ? (
+
+ ) : (
+
+ )}
+ Shrink Uptime Data
+
+
+
+ {isServerShrinking ? (
+
+ ) : (
+
+ )}
+ Shrink Server Data
+
+
+
+ {isFullShrinking ? (
+
+ ) : (
+
+ )}
+ Full Database Shrink
+
+
+
+
+
+ {isSaving ? (
+
+ ) : null}
+ Save Changes
+
+
+
+
+
+ );
+};
+
+export default DataRetentionSettings;
\ No newline at end of file
diff --git a/application/src/components/settings/data-retention/index.ts b/application/src/components/settings/data-retention/index.ts
new file mode 100644
index 0000000..bc25d28
--- /dev/null
+++ b/application/src/components/settings/data-retention/index.ts
@@ -0,0 +1,5 @@
+
+import DataRetentionSettings from './DataRetentionSettings';
+
+export default DataRetentionSettings;
+export { DataRetentionSettings };
\ No newline at end of file
diff --git a/application/src/pages/Settings.tsx b/application/src/pages/Settings.tsx
index 5c23f96..6dd3516 100644
--- a/application/src/pages/Settings.tsx
+++ b/application/src/pages/Settings.tsx
@@ -9,6 +9,7 @@ import UserManagement from "@/components/settings/user-management";
import { NotificationSettings } from "@/components/settings/notification-settings";
import { AlertsTemplates } from "@/components/settings/alerts-templates";
import { AboutSystem } from "@/components/settings/about-system";
+import DataRetentionSettings from "@/components/settings/data-retention/DataRetentionSettings";
const Settings = () => {
// State for sidebar collapse functionality
@@ -58,6 +59,7 @@ const Settings = () => {
{activePanel === "users" && }
{activePanel === "notifications" && }
{activePanel === "templates" && }
+ {activePanel === "data-retention" && }
{activePanel === "about" && }
@@ -65,4 +67,4 @@ const Settings = () => {
);
};
-export default Settings;
+export default Settings;
\ No newline at end of file
diff --git a/application/src/services/dataRetentionService.ts b/application/src/services/dataRetentionService.ts
new file mode 100644
index 0000000..05c7e76
--- /dev/null
+++ b/application/src/services/dataRetentionService.ts
@@ -0,0 +1,293 @@
+
+import { pb } from '@/lib/pocketbase';
+
+interface RetentionSettings {
+ uptimeRetentionDays: number;
+ serverRetentionDays: number;
+ lastCleanup?: string;
+}
+
+interface CleanupResult {
+ deletedRecords: number;
+ collections: string[];
+}
+
+export const dataRetentionService = {
+ async getRetentionSettings(): Promise {
+ try {
+ // Try to get existing settings from data_settings collection
+ const records = await pb.collection('data_settings').getFullList({
+ sort: '-created'
+ });
+
+ if (records.length > 0) {
+ const settings = records[0];
+ return {
+ uptimeRetentionDays: settings.uptime_retention_days || 30,
+ serverRetentionDays: settings.server_retention_days || 30,
+ lastCleanup: settings.last_cleanup
+ };
+ }
+
+ // Return default settings if none exist
+ return {
+ uptimeRetentionDays: 30,
+ serverRetentionDays: 30
+ };
+ } catch (error) {
+ console.error("Error fetching retention settings:", error);
+ // Return default settings on error
+ return {
+ uptimeRetentionDays: 30,
+ serverRetentionDays: 30
+ };
+ }
+ },
+
+ async updateRetentionSettings(settings: RetentionSettings): Promise {
+ try {
+ // Check if settings already exist
+ const existingRecords = await pb.collection('data_settings').getFullList({
+ sort: '-created'
+ });
+
+ const data = {
+ uptime_retention_days: settings.uptimeRetentionDays,
+ server_retention_days: settings.serverRetentionDays,
+ retention_days: Math.max(settings.uptimeRetentionDays, settings.serverRetentionDays), // General retention days
+ backup: "auto", // Default backup setting
+ updated: new Date().toISOString()
+ };
+
+ if (existingRecords.length > 0) {
+ // Update existing record
+ await pb.collection('data_settings').update(existingRecords[0].id, data);
+ } else {
+ // Create new record
+ await pb.collection('data_settings').create({
+ ...data,
+ created: new Date().toISOString()
+ });
+ }
+
+ console.log("Retention settings updated successfully");
+ } catch (error) {
+ console.error("Error updating retention settings:", error);
+ throw new Error("Failed to update retention settings");
+ }
+ },
+
+ async manualUptimeCleanup(): Promise {
+ try {
+ const settings = await this.getRetentionSettings();
+ if (!settings) {
+ throw new Error("Could not load retention settings");
+ }
+
+ let totalDeleted = 0;
+ const cleanedCollections: string[] = [];
+
+ // Calculate cutoff date for uptime data
+ const uptimeCutoffDate = new Date();
+ uptimeCutoffDate.setDate(uptimeCutoffDate.getDate() - settings.uptimeRetentionDays);
+
+ console.log(`Starting uptime cleanup - Cutoff: ${uptimeCutoffDate.toISOString()}`);
+
+ // Clean uptime_data collection
+ try {
+ const uptimeRecords = await pb.collection('uptime_data').getFullList({
+ filter: `created < "${uptimeCutoffDate.toISOString()}"`
+ });
+
+ console.log(`Found ${uptimeRecords.length} uptime records to delete`);
+
+ for (const record of uptimeRecords) {
+ await pb.collection('uptime_data').delete(record.id);
+ totalDeleted++;
+ }
+
+ if (uptimeRecords.length > 0) {
+ cleanedCollections.push('uptime_data');
+ }
+ } catch (error) {
+ console.error("Error cleaning uptime_data:", error);
+ }
+
+ console.log(`Uptime cleanup completed. Deleted ${totalDeleted} records`);
+
+ return {
+ deletedRecords: totalDeleted,
+ collections: cleanedCollections
+ };
+ } catch (error) {
+ console.error("Error during uptime cleanup:", error);
+ throw new Error("Failed to perform uptime data cleanup");
+ }
+ },
+
+ async manualServerCleanup(): Promise {
+ try {
+ const settings = await this.getRetentionSettings();
+ if (!settings) {
+ throw new Error("Could not load retention settings");
+ }
+
+ let totalDeleted = 0;
+ const cleanedCollections: string[] = [];
+
+ // Calculate cutoff date for server data
+ const serverCutoffDate = new Date();
+ serverCutoffDate.setDate(serverCutoffDate.getDate() - settings.serverRetentionDays);
+
+ console.log(`Starting server cleanup - Cutoff: ${serverCutoffDate.toISOString()}`);
+
+ // Clean ping_data collection
+ try {
+ const pingRecords = await pb.collection('ping_data').getFullList({
+ filter: `created < "${serverCutoffDate.toISOString()}"`
+ });
+
+ console.log(`Found ${pingRecords.length} ping records to delete`);
+
+ for (const record of pingRecords) {
+ await pb.collection('ping_data').delete(record.id);
+ totalDeleted++;
+ }
+
+ if (pingRecords.length > 0) {
+ cleanedCollections.push('ping_data');
+ }
+ } catch (error) {
+ console.error("Error cleaning ping_data:", error);
+ }
+
+ console.log(`Server cleanup completed. Deleted ${totalDeleted} records`);
+
+ return {
+ deletedRecords: totalDeleted,
+ collections: cleanedCollections
+ };
+ } catch (error) {
+ console.error("Error during server cleanup:", error);
+ throw new Error("Failed to perform server data cleanup");
+ }
+ },
+
+ async manualCleanup(): Promise {
+ try {
+ // Get current retention settings
+ const settings = await this.getRetentionSettings();
+ if (!settings) {
+ throw new Error("Could not load retention settings");
+ }
+
+ let totalDeleted = 0;
+ const cleanedCollections: string[] = [];
+
+ // Calculate cutoff dates
+ const uptimeCutoffDate = new Date();
+ uptimeCutoffDate.setDate(uptimeCutoffDate.getDate() - settings.uptimeRetentionDays);
+
+ const serverCutoffDate = new Date();
+ serverCutoffDate.setDate(serverCutoffDate.getDate() - settings.serverRetentionDays);
+
+ console.log(`Starting manual cleanup - Uptime cutoff: ${uptimeCutoffDate.toISOString()}, Server cutoff: ${serverCutoffDate.toISOString()}`);
+
+ // Clean uptime_data collection
+ try {
+ const uptimeRecords = await pb.collection('uptime_data').getFullList({
+ filter: `created < "${uptimeCutoffDate.toISOString()}"`
+ });
+
+ console.log(`Found ${uptimeRecords.length} uptime records to delete`);
+
+ for (const record of uptimeRecords) {
+ await pb.collection('uptime_data').delete(record.id);
+ totalDeleted++;
+ }
+
+ if (uptimeRecords.length > 0) {
+ cleanedCollections.push('uptime_data');
+ }
+ } catch (error) {
+ console.error("Error cleaning uptime_data:", error);
+ }
+
+ // Clean ping_data collection
+ try {
+ const pingRecords = await pb.collection('ping_data').getFullList({
+ filter: `created < "${serverCutoffDate.toISOString()}"`
+ });
+
+ console.log(`Found ${pingRecords.length} ping records to delete`);
+
+ for (const record of pingRecords) {
+ await pb.collection('ping_data').delete(record.id);
+ totalDeleted++;
+ }
+
+ if (pingRecords.length > 0) {
+ cleanedCollections.push('ping_data');
+ }
+ } catch (error) {
+ console.error("Error cleaning ping_data:", error);
+ }
+
+ // Update last cleanup timestamp
+ await this.updateLastCleanupTime();
+
+ console.log(`Manual cleanup completed. Deleted ${totalDeleted} records from collections: ${cleanedCollections.join(', ')}`);
+
+ return {
+ deletedRecords: totalDeleted,
+ collections: cleanedCollections
+ };
+ } catch (error) {
+ console.error("Error during manual cleanup:", error);
+ throw new Error("Failed to perform database cleanup");
+ }
+ },
+
+ async updateLastCleanupTime(): Promise {
+ try {
+ const existingRecords = await pb.collection('data_settings').getFullList({
+ sort: '-created'
+ });
+
+ const data = {
+ last_cleanup: new Date().toISOString()
+ };
+
+ if (existingRecords.length > 0) {
+ await pb.collection('data_settings').update(existingRecords[0].id, data);
+ }
+ } catch (error) {
+ console.error("Error updating last cleanup time:", error);
+ }
+ },
+
+ async scheduleAutomaticCleanup(): Promise {
+ try {
+ const settings = await this.getRetentionSettings();
+ if (!settings) return;
+
+ // Check if enough time has passed since last cleanup (run daily)
+ if (settings.lastCleanup) {
+ const lastCleanup = new Date(settings.lastCleanup);
+ const now = new Date();
+ const hoursSinceLastCleanup = (now.getTime() - lastCleanup.getTime()) / (1000 * 60 * 60);
+
+ if (hoursSinceLastCleanup < 24) {
+ console.log("Skipping automatic cleanup - last cleanup was less than 24 hours ago");
+ return;
+ }
+ }
+
+ console.log("Starting scheduled automatic cleanup");
+ const result = await this.manualCleanup();
+ console.log(`Automatic cleanup completed. Deleted ${result.deletedRecords} records`);
+ } catch (error) {
+ console.error("Error during automatic cleanup:", error);
+ }
+ }
+};
\ No newline at end of file