From e41ae5d65bb463812197b13d19aacb0876ba2751 Mon Sep 17 00:00:00 2001
From: Alexander Borg
Date: Tue, 23 Sep 2025 16:05:34 +0200
Subject: [PATCH] Fix jwt-token
---
data-retention-service/index.js | 35 ++++-
.../src/components/DataRetentionMetrics.jsx | 148 +++++++++++++++++-
management/src/pages/Dashboard.jsx | 8 +-
management/src/pages/System.jsx | 6 +
server/routes/dataRetention.js | 75 +++++++++
5 files changed, 262 insertions(+), 10 deletions(-)
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 = () => {
)}
-
- {lastUpdate && `Updated ${lastUpdate.toLocaleTimeString()}`}
+
+
+
+ {lastUpdate && `Updated ${lastUpdate.toLocaleTimeString()}`}
+
+
+ {/* Action Buttons */}
+ {isConnected && (
+
+
+
+
+ )}
@@ -211,6 +281,78 @@ const DataRetentionMetrics = () => {
)}
+
+ {/* Cleanup Preview Modal */}
+ {showPreview && previewData && (
+
+
+
+
Data Retention Cleanup Preview
+
+
+
+
+
+ This preview shows what data would be deleted based on each tenant's retention policy.
+
+
+ {previewData.tenants && previewData.tenants.map((tenant, index) => (
+
+
+
{tenant.name}
+
+ {tenant.retentionDays === -1 ? 'Unlimited' : `${tenant.retentionDays} days`}
+
+
+
+ {tenant.retentionDays === -1 ? (
+
No data will be deleted (unlimited retention)
+ ) : (
+
+
+ Detections:
+ {tenant.toDelete?.detections || 0}
+
+
+ Heartbeats:
+ {tenant.toDelete?.heartbeats || 0}
+
+
+ Logs:
+ {tenant.toDelete?.logs || 0}
+
+
+ )}
+
+ ))}
+
+
+
+
+
+
+
+
+ )}
)
}
diff --git a/management/src/pages/Dashboard.jsx b/management/src/pages/Dashboard.jsx
index f1aeeb7..77de063 100644
--- a/management/src/pages/Dashboard.jsx
+++ b/management/src/pages/Dashboard.jsx
@@ -2,7 +2,6 @@ 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({
@@ -97,8 +96,8 @@ const Dashboard = () => {
))}
- {/* Quick Actions and Data Retention */}
-
+ {/* Quick Actions and Recent Activity */}
+
Quick Actions
@@ -143,9 +142,6 @@ const Dashboard = () => {
-
- {/* Data Retention Service Metrics */}
-
)
}
diff --git a/management/src/pages/System.jsx b/management/src/pages/System.jsx
index 867edee..d1137d1 100644
--- a/management/src/pages/System.jsx
+++ b/management/src/pages/System.jsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import api from '../services/api'
import toast from 'react-hot-toast'
import { t } from '../utils/tempTranslations' // Temporary translation system
+import DataRetentionMetrics from '../components/DataRetentionMetrics'
import {
CogIcon,
ServerIcon,
@@ -516,6 +517,11 @@ const System = () => {
+
+ {/* Data Retention Service */}
+
+
+
)
}
diff --git a/server/routes/dataRetention.js b/server/routes/dataRetention.js
index 50bf282..bcae498 100644
--- a/server/routes/dataRetention.js
+++ b/server/routes/dataRetention.js
@@ -375,4 +375,79 @@ router.get('/status', async (req, res) => {
}
});
+/**
+ * POST /api/data-retention/cleanup
+ * Trigger manual data retention cleanup
+ * RESTRICTED: Management users only
+ */
+router.post('/cleanup', async (req, res) => {
+ try {
+ // Add security headers
+ res.setHeader('X-Content-Type-Options', 'nosniff');
+ res.setHeader('X-Frame-Options', 'DENY');
+ res.setHeader('X-XSS-Protection', '1; mode=block');
+
+ // Make HTTP request to data retention service cleanup endpoint
+ const response = await new Promise((resolve, reject) => {
+ const options = {
+ hostname: DATA_RETENTION_HOST,
+ port: DATA_RETENTION_PORT,
+ path: '/cleanup',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ timeout: 30000 // 30 second timeout for cleanup operation
+ };
+
+ const req = http.request(options, (res) => {
+ let data = '';
+ res.on('data', (chunk) => data += chunk);
+ res.on('end', () => {
+ try {
+ const parsed = data ? JSON.parse(data) : {};
+ resolve({ status: res.statusCode, data: parsed });
+ } catch (e) {
+ resolve({ status: res.statusCode, data: { message: data } });
+ }
+ });
+ });
+
+ req.on('error', reject);
+ req.on('timeout', () => reject(new Error('Data retention service timeout')));
+ req.end();
+ });
+
+ if (response.status === 200 || response.status === 202) {
+ // Log successful cleanup trigger
+ await auditLogger.logSuccess(req.managementUser, req, '/cleanup');
+ console.log(`โ
Data retention cleanup triggered by ${req.managementUser.username}`);
+
+ res.json({
+ success: true,
+ data: response.data,
+ message: 'Data retention cleanup initiated successfully',
+ timestamp: new Date().toISOString(),
+ triggeredBy: {
+ username: req.managementUser.username,
+ role: req.managementUser.role
+ }
+ });
+ } else {
+ res.status(response.status).json({
+ success: false,
+ error: 'Failed to trigger cleanup in data retention service',
+ details: response.data
+ });
+ }
+ } catch (error) {
+ console.error(`โ Data retention cleanup trigger error for ${req.managementUser.username}:`, error);
+ res.status(503).json({
+ success: false,
+ error: 'Data retention service unavailable',
+ details: error.message
+ });
+ }
+});
+
module.exports = router;
\ No newline at end of file