diff --git a/management/src/components/DataRetentionMetrics.jsx b/management/src/components/DataRetentionMetrics.jsx
new file mode 100644
index 0000000..546bdd6
--- /dev/null
+++ b/management/src/components/DataRetentionMetrics.jsx
@@ -0,0 +1,218 @@
+import React, { useState, useEffect } from 'react'
+import api from '../services/api'
+import {
+ TrashIcon,
+ ServerIcon,
+ ChartBarIcon,
+ ClockIcon,
+ ExclamationTriangleIcon,
+ CheckCircleIcon
+} from '@heroicons/react/24/outline'
+
+const DataRetentionMetrics = () => {
+ const [metrics, setMetrics] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+ const [lastUpdate, setLastUpdate] = useState(null)
+
+ useEffect(() => {
+ loadDataRetentionMetrics()
+ // Auto-refresh every 30 seconds
+ const interval = setInterval(loadDataRetentionMetrics, 30000)
+ return () => clearInterval(interval)
+ }, [])
+
+ const loadDataRetentionMetrics = async () => {
+ try {
+ setError(null)
+ const response = await api.get('/data-retention/status')
+ setMetrics(response.data)
+ setLastUpdate(new Date())
+ } catch (error) {
+ console.error('Error loading data retention metrics:', error)
+ setError(error.response?.data?.message || 'Failed to load data retention metrics')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const formatUptime = (seconds) => {
+ if (!seconds) return 'Unknown'
+ const days = Math.floor(seconds / 86400)
+ const hours = Math.floor((seconds % 86400) / 3600)
+ const minutes = Math.floor((seconds % 3600) / 60)
+
+ if (days > 0) return `${days}d ${hours}h ${minutes}m`
+ if (hours > 0) return `${hours}h ${minutes}m`
+ return `${minutes}m`
+ }
+
+ const formatMemory = (mb) => {
+ if (!mb) return 'Unknown'
+ if (mb > 1024) return `${(mb / 1024).toFixed(1)} GB`
+ return `${mb} MB`
+ }
+
+ if (loading) {
+ return (
+
+
+
+
Data Retention Service
+
+
+
+ )
+ }
+
+ if (error) {
+ return (
+
+
+
+
Data Retention Service
+
+
+
+
+
+
+
Service Unavailable
+
{error}
+
+
+
+
+
+ )
+ }
+
+ const isConnected = metrics?.service?.connected
+ const serviceHealth = metrics?.health
+ const serviceMetrics = metrics?.metrics
+
+ return (
+
+
+
+
+
Data Retention Service
+ {isConnected ? (
+
+ ) : (
+
+ )}
+
+
+ {lastUpdate && `Updated ${lastUpdate.toLocaleTimeString()}`}
+
+
+
+ {isConnected ? (
+
+ {/* Service Status */}
+
+
+
+
+
+
Status
+
{serviceMetrics?.service?.status || 'Running'}
+
+
+
+
+
+
+
+
+
Uptime
+
+ {formatUptime(serviceMetrics?.service?.uptime || serviceHealth?.uptime)}
+
+
+
+
+
+
+
+
+
+
Memory
+
+ {formatMemory(serviceMetrics?.performance?.memoryUsage?.heapUsed)}
+
+
+
+
+
+
+ {/* Cleanup Information */}
+ {serviceMetrics?.cleanup && (
+
+
Cleanup Operations
+
+
+
Last Cleanup
+
+ {serviceMetrics.cleanup.lastRunFormatted || 'Never'}
+
+
+
+
Next Scheduled
+
{serviceMetrics.cleanup.nextScheduledRun || '2:00 AM UTC daily'}
+
+
+
+ {serviceMetrics.cleanup.stats && (
+
+
Last Cleanup Stats
+
+
+ Detections: {serviceMetrics.cleanup.stats.totalDetections || 0}
+
+
+ Heartbeats: {serviceMetrics.cleanup.stats.totalHeartbeats || 0}
+
+
+ Logs: {serviceMetrics.cleanup.stats.totalLogs || 0}
+
+
+
+ )}
+
+ )}
+
+ {/* Schedule Information */}
+ {serviceMetrics?.schedule && (
+
+
Schedule
+
{serviceMetrics.schedule.description}
+
+ Cron: {serviceMetrics.schedule.cronExpression} ({serviceMetrics.schedule.timezone})
+
+
+ )}
+
+ ) : (
+
+
+
Data retention service is not connected
+
+ {metrics?.service?.error || 'Service health check failed'}
+
+
+ )}
+
+ )
+}
+
+export default DataRetentionMetrics
\ No newline at end of file
diff --git a/management/src/pages/Dashboard.jsx b/management/src/pages/Dashboard.jsx
index 3871222..f1aeeb7 100644
--- a/management/src/pages/Dashboard.jsx
+++ b/management/src/pages/Dashboard.jsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import api from '../services/api'
import { t } from '../utils/tempTranslations' // Temporary translation system
import { BuildingOfficeIcon, UsersIcon, ServerIcon, ChartBarIcon } from '@heroicons/react/24/outline'
+import DataRetentionMetrics from '../components/DataRetentionMetrics'
const Dashboard = () => {
const [stats, setStats] = useState({
@@ -96,8 +97,8 @@ const Dashboard = () => {
))}
- {/* Quick Actions */}
-
+ {/* Quick Actions and Data Retention */}
+
Quick Actions
@@ -142,6 +143,9 @@ const Dashboard = () => {
+
+ {/* Data Retention Service Metrics */}
+
)
}
diff --git a/server/index.js b/server/index.js
index d1490c1..2508feb 100644
--- a/server/index.js
+++ b/server/index.js
@@ -93,6 +93,11 @@ app.use((req, res, next) => ipRestriction.checkIPRestriction(req, res, next));
// Tenant-specific API rate limiting (for authenticated endpoints)
const { enforceApiRateLimit } = require('./middleware/tenant-limits');
app.use('/api', (req, res, next) => {
+ // Skip tenant rate limiting for management and data-retention endpoints
+ if (req.path.startsWith('/management') || req.path.startsWith('/data-retention')) {
+ return next();
+ }
+
// Apply tenant rate limiting only to authenticated API endpoints
if (req.headers.authorization) {
return enforceApiRateLimit()(req, res, next);