diff --git a/data-retention-service/index.js b/data-retention-service/index.js
index 7f7109c..78fd072 100644
--- a/data-retention-service/index.js
+++ b/data-retention-service/index.js
@@ -308,7 +308,7 @@ class DataRetentionService {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Content-Type', 'application/json');
@@ -343,6 +343,39 @@ class DataRetentionService {
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found' }));
}
+
+ } else if (req.method === 'POST') {
+ if (parsedUrl.pathname === '/cleanup') {
+ // Manual cleanup trigger
+ if (this.isRunning) {
+ res.writeHead(409);
+ res.end(JSON.stringify({
+ error: 'Cleanup already in progress',
+ message: 'A cleanup operation is currently running. Please wait for it to complete.'
+ }));
+ return;
+ }
+
+ console.log('๐งน Manual cleanup triggered via HTTP API');
+
+ // Trigger cleanup asynchronously
+ this.performCleanup().then(() => {
+ console.log('โ
Manual cleanup completed successfully');
+ }).catch((error) => {
+ console.error('โ Manual cleanup failed:', error);
+ });
+
+ res.writeHead(202);
+ res.end(JSON.stringify({
+ success: true,
+ message: 'Data retention cleanup initiated',
+ timestamp: new Date().toISOString()
+ }));
+
+ } else {
+ res.writeHead(404);
+ res.end(JSON.stringify({ error: 'Not found' }));
+ }
} else {
res.writeHead(405);
res.end(JSON.stringify({ error: 'Method not allowed' }));
diff --git a/management/src/components/DataRetentionMetrics.jsx b/management/src/components/DataRetentionMetrics.jsx
index 546bdd6..da68a7d 100644
--- a/management/src/components/DataRetentionMetrics.jsx
+++ b/management/src/components/DataRetentionMetrics.jsx
@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react'
import api from '../services/api'
+import toast from 'react-hot-toast'
import {
TrashIcon,
ServerIcon,
ChartBarIcon,
ClockIcon,
ExclamationTriangleIcon,
- CheckCircleIcon
+ CheckCircleIcon,
+ PlayIcon,
+ EyeIcon
} from '@heroicons/react/24/outline'
const DataRetentionMetrics = () => {
@@ -14,6 +17,9 @@ const DataRetentionMetrics = () => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [lastUpdate, setLastUpdate] = useState(null)
+ const [cleanupLoading, setCleanupLoading] = useState(false)
+ const [previewData, setPreviewData] = useState(null)
+ const [showPreview, setShowPreview] = useState(false)
useEffect(() => {
loadDataRetentionMetrics()
@@ -36,6 +42,42 @@ const DataRetentionMetrics = () => {
}
}
+ const loadCleanupPreview = async () => {
+ try {
+ setError(null)
+ const response = await api.get('/data-retention/stats')
+ setPreviewData(response.data.data)
+ setShowPreview(true)
+ } catch (error) {
+ console.error('Error loading cleanup preview:', error)
+ toast.error('Failed to load cleanup preview')
+ }
+ }
+
+ const executeCleanup = async () => {
+ if (!window.confirm('Are you sure you want to execute data retention cleanup? This will permanently delete old data according to each tenant\'s retention policy.')) {
+ return
+ }
+
+ setCleanupLoading(true)
+ try {
+ // Note: This endpoint would need to be implemented in the backend
+ const response = await api.post('/data-retention/cleanup')
+ toast.success('Data retention cleanup initiated successfully')
+
+ // Refresh metrics after cleanup
+ setTimeout(() => {
+ loadDataRetentionMetrics()
+ }, 2000)
+
+ } catch (error) {
+ console.error('Error executing cleanup:', error)
+ toast.error(error.response?.data?.message || 'Failed to execute cleanup')
+ } finally {
+ setCleanupLoading(false)
+ }
+ }
+
const formatUptime = (seconds) => {
if (!seconds) return 'Unknown'
const days = Math.floor(seconds / 86400)
@@ -111,8 +153,36 @@ const DataRetentionMetrics = () => {
+ This preview shows what data would be deleted based on each tenant's retention policy. +
+ + {previewData.tenants && previewData.tenants.map((tenant, index) => ( +No data will be deleted (unlimited retention)
+ ) : ( +