Complete Task 5: Mithal monitoring dashboard
هذا الالتزام موجود في:
120
q5-mithal-monitor/dashboard/app.js
Normal file
120
q5-mithal-monitor/dashboard/app.js
Normal file
@@ -0,0 +1,120 @@
|
||||
async function loadData() {
|
||||
|
||||
try {
|
||||
|
||||
const response = await fetch("data.json?t=" + Date.now());
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latest = data[data.length - 1];
|
||||
|
||||
const upCount = data.filter(item => item.uptime === "UP").length;
|
||||
const uptimePercentage = ((upCount / data.length) * 100).toFixed(1);
|
||||
|
||||
document.getElementById("uptime").innerText =
|
||||
uptimePercentage + "%";
|
||||
|
||||
document.getElementById("latency").innerText =
|
||||
(latest.latency_ms ?? "-") + " ms";
|
||||
|
||||
document.getElementById("dns").innerText =
|
||||
(latest.dns_ms ?? "-") + " ms";
|
||||
|
||||
if (latest.ssl && latest.ssl.valid) {
|
||||
|
||||
document.getElementById("ssl").innerHTML = `
|
||||
<strong style="color:green;">Valid</strong><br>
|
||||
<small>${latest.ssl.expires}</small>
|
||||
`;
|
||||
|
||||
} else {
|
||||
|
||||
document.getElementById("ssl").innerHTML = `
|
||||
<strong style="color:red;">Invalid</strong>
|
||||
`;
|
||||
|
||||
}
|
||||
|
||||
const table = document.getElementById("historyTable");
|
||||
table.innerHTML = "";
|
||||
|
||||
data
|
||||
.slice()
|
||||
.reverse()
|
||||
.forEach(item => {
|
||||
|
||||
table.innerHTML += `
|
||||
<tr>
|
||||
<td>${item.timestamp}</td>
|
||||
<td>${item.uptime}</td>
|
||||
<td>${item.latency_ms ?? "-"}</td>
|
||||
<td>${item.dns_ms ?? "-"}</td>
|
||||
<td>${item.ssl && item.ssl.valid ? "Valid" : "Invalid"}</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
});
|
||||
|
||||
|
||||
const labels = data.map(item => item.timestamp);
|
||||
const latency = data.map(item => item.latency_ms);
|
||||
|
||||
if (!window.latencyChart) {
|
||||
|
||||
window.latencyChart = new Chart(
|
||||
document.getElementById("latencyChart"),
|
||||
{
|
||||
type: "line",
|
||||
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Latency (ms)",
|
||||
data: latency,
|
||||
borderColor: "#0b7dda",
|
||||
backgroundColor: "rgba(11,125,218,0.15)",
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
},
|
||||
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
window.latencyChart.data.labels = labels;
|
||||
window.latencyChart.data.datasets[0].data = latency;
|
||||
window.latencyChart.update();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
|
||||
console.error(error);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loadData();
|
||||
|
||||
setInterval(loadData, 60000);
|
||||
62
q5-mithal-monitor/dashboard/data.json
Normal file
62
q5-mithal-monitor/dashboard/data.json
Normal file
@@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-07-28 17:50:54",
|
||||
"status_code": 200,
|
||||
"uptime": "UP",
|
||||
"latency_ms": 994.55,
|
||||
"dns_ms": 39.1,
|
||||
"search_response_ms": 1170.81,
|
||||
"ssl": {
|
||||
"valid": true,
|
||||
"expires": "Sep 15 13:10:47 2026 GMT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 17:50:58",
|
||||
"status_code": 200,
|
||||
"uptime": "UP",
|
||||
"latency_ms": 986.42,
|
||||
"dns_ms": 40.91,
|
||||
"search_response_ms": 1130.27,
|
||||
"ssl": {
|
||||
"valid": true,
|
||||
"expires": "Sep 15 13:10:47 2026 GMT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 17:51:01",
|
||||
"status_code": 200,
|
||||
"uptime": "UP",
|
||||
"latency_ms": 980.57,
|
||||
"dns_ms": 51.6,
|
||||
"search_response_ms": 1168.75,
|
||||
"ssl": {
|
||||
"valid": true,
|
||||
"expires": "Sep 15 13:10:47 2026 GMT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 17:51:04",
|
||||
"status_code": 200,
|
||||
"uptime": "UP",
|
||||
"latency_ms": 969.79,
|
||||
"dns_ms": 40.75,
|
||||
"search_response_ms": 1209.84,
|
||||
"ssl": {
|
||||
"valid": true,
|
||||
"expires": "Sep 15 13:10:47 2026 GMT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 17:51:07",
|
||||
"status_code": 200,
|
||||
"uptime": "UP",
|
||||
"latency_ms": 976.47,
|
||||
"dns_ms": 42.13,
|
||||
"search_response_ms": 1147.52,
|
||||
"ssl": {
|
||||
"valid": true,
|
||||
"expires": "Sep 15 13:10:47 2026 GMT"
|
||||
}
|
||||
}
|
||||
]
|
||||
94
q5-mithal-monitor/dashboard/index.html
Normal file
94
q5-mithal-monitor/dashboard/index.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mithal Monitoring Dashboard</title>
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>Mithal Monitoring Dashboard</h1>
|
||||
|
||||
<p class="subtitle">
|
||||
Website Monitoring Dashboard for mithal.space
|
||||
</p>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<h2>Uptime</h2>
|
||||
<p id="uptime">--</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Latency</h2>
|
||||
<p id="latency">--</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>DNS Time</h2>
|
||||
<p id="dns">--</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>SSL</h2>
|
||||
<p id="ssl">--</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="chart-card">
|
||||
|
||||
<h2>Latency History</h2>
|
||||
|
||||
<canvas id="latencyChart"></canvas>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
|
||||
<h2>Last 10 Checks</h2>
|
||||
|
||||
<table>
|
||||
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
|
||||
<th>Time</th>
|
||||
|
||||
<th>Status</th>
|
||||
|
||||
<th>Latency (ms)</th>
|
||||
|
||||
<th>DNS (ms)</th>
|
||||
|
||||
<th>SSL</th>
|
||||
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody id="historyTable">
|
||||
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
122
q5-mithal-monitor/dashboard/style.css
Normal file
122
q5-mithal-monitor/dashboard/style.css
Normal file
@@ -0,0 +1,122 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: #f4f7fb;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 95%;
|
||||
max-width: 1300px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-bottom: 15px;
|
||||
color: #555;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #0b7dda;
|
||||
}
|
||||
|
||||
.chart-card,
|
||||
.table-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
margin-top: 30px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.chart-card h2,
|
||||
.table-card h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #0b7dda;
|
||||
color: white;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100% !important;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
@media (max-width:900px){
|
||||
|
||||
.cards{
|
||||
|
||||
grid-template-columns:repeat(2,1fr);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width:600px){
|
||||
|
||||
.cards{
|
||||
|
||||
grid-template-columns:1fr;
|
||||
|
||||
}
|
||||
|
||||
table{
|
||||
|
||||
font-size:12px;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم