fixed bugs
هذا الالتزام موجود في:
@@ -4,7 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>mithal.space — لوحة المراقبة</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.4/chart.umd.min.js"></script>
|
||||
<script src="chart.umd.min.js"></script>
|
||||
<script>if (typeof Chart === 'undefined') { document.write('<script src="https://cdn.jsdelivr.net/npm/chart.js"><\/script>'); }</script>
|
||||
<script src="data.js"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
@@ -161,17 +163,75 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const REFRESH_MS = 60000; // إعادة تحميل تلقائي كل دقيقة
|
||||
const REFRESH_MS = 15000; // إعادة تحميل تلقائي كل 15 ثانية لجلب البيانات الحديثة فوراً
|
||||
|
||||
let records = [];
|
||||
let records = (typeof window !== 'undefined' && Array.isArray(window.MONITOR_DATA)) ? window.MONITOR_DATA : [];
|
||||
let latencyChart = null;
|
||||
|
||||
function parseJsonl(text){
|
||||
return text.split('\n')
|
||||
const items = text.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(Boolean)
|
||||
.map(l => { try { return JSON.parse(l); } catch(e){ return null; } })
|
||||
.filter(Boolean);
|
||||
return items.flat();
|
||||
}
|
||||
|
||||
function parseCsv(text) {
|
||||
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
if (lines.length < 2) return [];
|
||||
const headers = lines[0].split(',').map(h => h.trim().replace(/^"|"$/g, ''));
|
||||
const parsed = [];
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const cols = lines[i].split(',').map(c => c.trim().replace(/^"|"$/g, ''));
|
||||
if (cols.length < headers.length) continue;
|
||||
const row = {};
|
||||
headers.forEach((h, idx) => { row[h] = cols[idx]; });
|
||||
parsed.push({
|
||||
timestamp: row.timestamp,
|
||||
dns: { time_ms: row.dns_time_ms ? parseFloat(row.dns_time_ms) : (row.dns_latency_ms ? parseFloat(row.dns_latency_ms) : null) },
|
||||
http: {
|
||||
up: row.http_up === 'True' || row.http_up === 'true' || row.uptime === 'True' || row.uptime === 'true',
|
||||
status_code: row.http_status_code ? parseInt(row.http_status_code) : (row.status_code ? parseInt(row.status_code) : null),
|
||||
latency_ms: row.http_latency_ms ? parseFloat(row.http_latency_ms) : (row.latency_ms ? parseFloat(row.latency_ms) : null)
|
||||
},
|
||||
ssl: {
|
||||
valid: row.ssl_valid === 'True' || row.ssl_valid === 'true',
|
||||
days_remaining: row.ssl_days_remaining ? parseInt(row.ssl_days_remaining) : null,
|
||||
expiry_date: row.ssl_expiry_date || row.ssl_expiry || null
|
||||
},
|
||||
search: {
|
||||
up: row.search_up === 'True' || row.search_up === 'true' || row.search_success === 'True' || row.search_success === 'true',
|
||||
status_code: row.search_status_code ? parseInt(row.search_status_code) : null,
|
||||
latency_ms: row.search_latency_ms ? parseFloat(row.search_latency_ms) : null
|
||||
},
|
||||
error: row.error || null
|
||||
});
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseFileContent(text) {
|
||||
let trimmed = text.trim();
|
||||
if (trimmed.startsWith('window.MONITOR_DATA')) {
|
||||
const eqIdx = trimmed.indexOf('=');
|
||||
if (eqIdx !== -1) {
|
||||
trimmed = trimmed.substring(eqIdx + 1).trim().replace(/;$/, '');
|
||||
}
|
||||
}
|
||||
if (trimmed.startsWith('[')) {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (Array.isArray(parsed)) return parsed.flat();
|
||||
} catch(e) {}
|
||||
}
|
||||
if (trimmed.startsWith('{')) {
|
||||
return parseJsonl(trimmed);
|
||||
}
|
||||
if (trimmed.includes(',')) {
|
||||
return parseCsv(trimmed);
|
||||
}
|
||||
return parseJsonl(trimmed);
|
||||
}
|
||||
|
||||
function fmtTime(iso){
|
||||
@@ -202,48 +262,53 @@ function statusColorClass(days){
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
if (typeof Chart === 'undefined') return;
|
||||
const chartCanvas = document.getElementById('latencyChart');
|
||||
if (!chartCanvas) return;
|
||||
const ctx = chartCanvas.getContext('2d');
|
||||
latencyChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'HTTP latency (ms)',
|
||||
data: [],
|
||||
borderColor: '#4fd1ff',
|
||||
backgroundColor: 'rgba(79,209,255,0.08)',
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
spanGaps: true,
|
||||
fill: true,
|
||||
},
|
||||
{
|
||||
label: 'Search latency (ms)',
|
||||
data: [],
|
||||
borderColor: '#f2b84b',
|
||||
backgroundColor: 'rgba(242,184,75,0.06)',
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
spanGaps: true,
|
||||
fill: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { labels: { color: '#7c8aa5', font: { family: 'JetBrains Mono', size: 11 } } }
|
||||
try {
|
||||
latencyChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'HTTP latency (ms)',
|
||||
data: [],
|
||||
borderColor: '#4fd1ff',
|
||||
backgroundColor: 'rgba(79,209,255,0.08)',
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
spanGaps: true,
|
||||
fill: true,
|
||||
},
|
||||
{
|
||||
label: 'Search latency (ms)',
|
||||
data: [],
|
||||
borderColor: '#f2b84b',
|
||||
backgroundColor: 'rgba(242,184,75,0.06)',
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
spanGaps: true,
|
||||
fill: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
scales: {
|
||||
x: { ticks: { color: '#7c8aa5', maxTicksLimit: 12 }, grid: { color: '#202838' } },
|
||||
y: { ticks: { color: '#7c8aa5' }, grid: { color: '#202838' }, title:{display:true, text:'ms', color:'#7c8aa5'} }
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { labels: { color: '#7c8aa5', font: { family: 'JetBrains Mono', size: 11 } } }
|
||||
},
|
||||
scales: {
|
||||
x: { ticks: { color: '#7c8aa5', maxTicksLimit: 12 }, grid: { color: '#202838' } },
|
||||
y: { ticks: { color: '#7c8aa5' }, grid: { color: '#202838' }, title:{display:true, text:'ms', color:'#7c8aa5'} }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
console.warn('Could not initialize Chart:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function render(){
|
||||
@@ -318,13 +383,13 @@ function render(){
|
||||
|
||||
// --- update Chart smoothly ---
|
||||
const lastHour = sorted.filter(r => nowMs - new Date(r.timestamp).getTime() <= 3600*1000);
|
||||
const chartSource = lastHour.length ? lastHour : sorted.slice(-30);
|
||||
const chartSource = lastHour.length >= 2 ? lastHour : sorted.slice(-30);
|
||||
|
||||
const labels = chartSource.map(r => fmtShortTime(r.timestamp));
|
||||
const httpData = chartSource.map(r => r.http ? r.http.latency_ms : null);
|
||||
const searchData = chartSource.map(r => r.search ? r.search.latency_ms : null);
|
||||
|
||||
if (!latencyChart) {
|
||||
if (!latencyChart && typeof Chart !== 'undefined') {
|
||||
initChart();
|
||||
}
|
||||
if (latencyChart) {
|
||||
@@ -354,15 +419,33 @@ function render(){
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFromServer(){
|
||||
try{
|
||||
const res = await fetch('data.jsonl', { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('not found');
|
||||
async function fetchFile(filename) {
|
||||
try {
|
||||
const url = `${filename}?_t=${Date.now()}`;
|
||||
const res = await fetch(url, { cache: 'no-store' });
|
||||
if (!res.ok) return null;
|
||||
const text = await res.text();
|
||||
records = parseJsonl(text);
|
||||
const data = parseFileContent(text);
|
||||
return (data && data.length) ? data : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFromServer(){
|
||||
const filesToTry = ['data.jsonl', 'data.csv', 'monitoring_data.csv', 'data.js'];
|
||||
for (const file of filesToTry) {
|
||||
const fetched = await fetchFile(file);
|
||||
if (fetched && fetched.length) {
|
||||
records = fetched;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.MONITOR_DATA && window.MONITOR_DATA.length) {
|
||||
records = window.MONITOR_DATA;
|
||||
render();
|
||||
} catch(e){
|
||||
// Fallback when served directly as file:// or if fetch fails
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,13 +454,18 @@ document.getElementById('fileInput').addEventListener('change', (e) => {
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
records = parseJsonl(evt.target.result);
|
||||
records = parseFileContent(evt.target.result);
|
||||
render();
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
initChart();
|
||||
if (typeof Chart !== 'undefined') {
|
||||
initChart();
|
||||
}
|
||||
if (records.length) {
|
||||
render();
|
||||
}
|
||||
loadFromServer();
|
||||
setInterval(loadFromServer, REFRESH_MS);
|
||||
</script>
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم