diff --git a/client/src/App.js b/client/src/App.js
index 9a8b978..7816a48 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -4,6 +4,7 @@ import axios from 'axios';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import './App.css';
import SettingsWizard from './SettingsWizard';
+import History from './History';
function Dashboard() {
const [resources, setResources] = useState([]);
@@ -500,6 +501,7 @@ function Navbar() {
🔍 Resource Monitor
Dashboard
+ History
Settings
{!notificationsConfigured && (
@@ -534,6 +536,7 @@ function App() {
} />
} />
+ } />
} />
diff --git a/client/src/History.js b/client/src/History.js
new file mode 100644
index 0000000..6893896
--- /dev/null
+++ b/client/src/History.js
@@ -0,0 +1,170 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import axios from 'axios';
+import {
+ LineChart,
+ Line,
+ AreaChart,
+ Area,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Bar,
+} from 'recharts';
+import './App.css';
+
+function History() {
+ const [historyData, setHistoryData] = useState([]);
+ const [days, setDays] = useState(7);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ loadHistory();
+ }, [days]);
+
+ const loadHistory = async () => {
+ setLoading(true);
+ try {
+ const response = await axios.get('/api/history/overview', { params: { days } });
+ setHistoryData(response.data);
+ setLoading(false);
+ } catch (error) {
+ console.error('Error loading history:', error);
+ setLoading(false);
+ }
+ };
+
+ if (loading) return Loading history...
;
+
+ return (
+
+
+
Monitoring History
+
+ {[7, 14, 30].map((d) => (
+
+ ))}
+
+
+
+ {historyData.length === 0 ? (
+
+
No check history
+
Add resources and wait for checks to complete
+
+ ) : (
+
+ {historyData.map((resource) => (
+
+
+
+ {resource.name}
+
+
+
+ Type: {resource.type} | Uptime: {resource.uptime}% | Avg Response: {resource.avgResponseTime}ms
+
+
+ {resource.checks.length > 0 ? (
+
+
+
+
+
+
+
+ {
+ if (name === 'Response Time (ms)') return `${value}ms`;
+ if (name === 'Status') return value === 1 ? 'UP' : 'DOWN';
+ return value;
+ }}
+ />
+
+
+
+
+
+
+ ) : (
+
+ No check data available
+
+ )}
+
+ {/* Statistics Summary */}
+
+
+
{resource.checks.length}
+
Total Checks
+
+
+
{resource.uptime}%
+
Uptime
+
+
+
{resource.avgResponseTime}ms
+
Avg Response
+
+
+
+ {resource.checks.filter((c) => c.status === 'up').length}/
+ {resource.checks.length}
+
+
Successful Checks
+
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+function prepareChartData(checks) {
+ return checks.map((check) => {
+ const date = new Date(check.checked_at);
+ return {
+ time: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
+ date: date.toLocaleDateString(),
+ responseTime: check.response_time || 0,
+ status: check.status,
+ statusNumeric: check.status === 'up' ? 1 : 0,
+ statusCode: check.status_code,
+ };
+ });
+}
+
+export default History;
diff --git a/server/index.js b/server/index.js
index 7fb123a..a56ddce 100644
--- a/server/index.js
+++ b/server/index.js
@@ -246,6 +246,73 @@ TIMEOUT=${timeout}
}
});
+// Get historical check data for a resource
+app.get('/api/resources/:id/history', (req, res) => {
+ const { id } = req.params;
+ const { days = 7 } = req.query;
+
+ const resource = db.prepare('SELECT * FROM resources WHERE id = ?').get(id);
+ if (!resource) {
+ return res.status(404).json({ error: 'Resource not found' });
+ }
+
+ const checks = db.prepare(`
+ SELECT
+ id,
+ status,
+ response_time,
+ status_code,
+ error_message,
+ details,
+ checked_at
+ FROM checks
+ WHERE resource_id = ? AND checked_at > datetime('now', ?)
+ ORDER BY checked_at ASC
+ `).all(id, `-${days} days`);
+
+ res.json({
+ resource,
+ checks,
+ count: checks.length,
+ });
+});
+
+// Get all resources' check history for dashboard
+app.get('/api/history/overview', (req, res) => {
+ const { days = 7 } = req.query;
+
+ const resources = db.prepare('SELECT * FROM resources WHERE enabled = 1 ORDER BY name').all();
+
+ const overview = resources.map(resource => {
+ const checks = db.prepare(`
+ SELECT
+ status,
+ response_time,
+ checked_at
+ FROM checks
+ WHERE resource_id = ? AND checked_at > datetime('now', ?)
+ ORDER BY checked_at ASC
+ `).all(resource.id, `-${days} days`);
+
+ const upCount = checks.filter(c => c.status === 'up').length;
+ const uptime = checks.length > 0 ? (upCount / checks.length * 100) : 0;
+ const avgResponseTime = checks.length > 0
+ ? checks.reduce((sum, c) => sum + (c.response_time || 0), 0) / checks.length
+ : 0;
+
+ return {
+ id: resource.id,
+ name: resource.name,
+ type: resource.type,
+ checks,
+ uptime: uptime.toFixed(2),
+ avgResponseTime: avgResponseTime.toFixed(0),
+ };
+ });
+
+ res.json(overview);
+});
+
// Test email
app.post('/api/test-email', async (req, res) => {
const nodemailer = require('nodemailer');