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