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);