add ping detail modal

This commit is contained in:
nate 2026-03-24 17:02:09 +04:00
parent 9a9f872fa6
commit 59c3c4b724
1 changed files with 100 additions and 5 deletions

View File

@ -116,8 +116,6 @@
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<%
%>
<thead>
<tr class="text-gray-500 text-xs">
<th class="text-left px-4 py-2 font-medium">Status</th>
@ -130,8 +128,10 @@
</tr>
</thead>
<tbody id="pings-table" class="divide-y divide-border-subtle">
<% pings.slice(0, 30).forEach(function(c) { %>
<tr class="table-row-alt">
<% pings.slice(0, 30).forEach(function(c) {
const pingJson = JSON.stringify(c).split('&').join('&amp;').split('"').join('&quot;').split('<').join('&lt;').split(String.fromCharCode(62)).join('&gt;');
%>
<tr class="table-row-alt cursor-pointer hover:bg-white/[0.02]" data-ping="<%~ pingJson %>">
<td class="px-4 py-2"><%~ c.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>' %></td>
<td class="px-4 py-2 text-gray-300"><%= c.status_code != null ? c.status_code : '—' %></td>
<td class="px-4 py-2 text-gray-300"><%= c.latency_ms != null ? c.latency_ms + 'ms' : '—' %></td>
@ -146,6 +146,18 @@
</div>
</div>
<!-- Ping detail modal -->
<div id="ping-modal" class="fixed inset-0 z-50 hidden">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="closePingModal()"></div>
<div class="absolute inset-4 sm:inset-auto sm:top-1/2 sm:left-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2 sm:w-full sm:max-w-lg sm:max-h-[80vh] card-static overflow-y-auto">
<div class="sticky top-0 bg-surface-solid flex items-center justify-between px-5 py-3 border-b divider">
<h3 class="text-sm font-medium text-gray-200">Ping Details</h3>
<button onclick="closePingModal()" class="text-gray-500 hover:text-gray-300 text-lg leading-none">&times;</button>
</div>
<div id="ping-modal-body" class="p-5 text-sm"></div>
</div>
</div>
<!-- Edit form -->
<div class="card-static p-6">
<h3 class="text-sm text-gray-400 mb-4">Edit Monitor</h3>
@ -161,6 +173,88 @@
const _initialQuery = <%~ JSON.stringify(m.query || null) %>;
<%~ include('./partials/monitor-form-js') %>
// ── Ping detail modal ──────────────────────────────────────────
function openPingModal(ping) {
const body = document.getElementById('ping-modal-body');
const meta = ping.meta || {};
const headers = meta.headers || {};
const bodyPreview = meta.body_preview || null;
const time = new Date(ping.checked_at);
const scheduled = ping.scheduled_at ? new Date(ping.scheduled_at) : null;
let html = '<div class="space-y-4">';
// Status row
html += '<div class="flex items-center gap-3">';
html += ping.up
? '<span class="inline-block w-2.5 h-2.5 rounded-full dot-up"></span><span class="text-green-400 font-medium">Up</span>'
: '<span class="inline-block w-2.5 h-2.5 rounded-full dot-down"></span><span class="text-red-400 font-medium">Down</span>';
if (ping.status_code != null) html += `<span class="text-gray-400 font-mono">${ping.status_code}</span>`;
if (ping.latency_ms != null) html += `<span class="text-gray-500">${ping.latency_ms}ms</span>`;
html += '</div>';
// Info grid
html += '<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">';
html += `<div class="text-gray-500">Checked at</div><div class="text-gray-300">${time.toLocaleString()}</div>`;
if (scheduled) html += `<div class="text-gray-500">Scheduled at</div><div class="text-gray-300">${scheduled.toLocaleString()}</div>`;
if (ping.jitter_ms != null) html += `<div class="text-gray-500">Jitter</div><div class="text-gray-300">${ping.jitter_ms}ms</div>`;
if (ping.region) html += `<div class="text-gray-500">Region</div><div class="text-gray-300">${escapeHtml(ping.region)}</div>`;
if (ping.run_id) html += `<div class="text-gray-500">Run ID</div><div class="text-gray-300 font-mono break-all">${escapeHtml(ping.run_id)}</div>`;
if (meta.cert_expiry_days != null) html += `<div class="text-gray-500">Cert expiry</div><div class="text-gray-300">${meta.cert_expiry_days} days</div>`;
html += '</div>';
// Error
if (ping.error) {
html += '<div>';
html += '<div class="text-xs text-gray-500 mb-1">Error</div>';
html += `<div class="text-red-400 text-xs bg-red-500/5 border border-red-500/10 rounded-lg px-3 py-2 font-mono break-all">${escapeHtml(ping.error)}</div>`;
html += '</div>';
}
// Response headers
const headerKeys = Object.keys(headers);
if (headerKeys.length > 0) {
html += '<div>';
html += '<div class="text-xs text-gray-500 mb-1">Response Headers</div>';
html += '<div class="bg-gray-800/50 border border-border-subtle rounded-lg px-3 py-2 text-xs font-mono max-h-48 overflow-y-auto">';
for (const [k, v] of Object.entries(headers)) {
html += `<div class="flex gap-2"><span class="text-blue-400 shrink-0">${escapeHtml(k)}:</span><span class="text-gray-300 break-all">${escapeHtml(String(v))}</span></div>`;
}
html += '</div></div>';
}
// Body preview
if (bodyPreview) {
html += '<div>';
html += '<div class="text-xs text-gray-500 mb-1">Response Body <span class="text-gray-600">(first 500 chars)</span></div>';
html += `<pre class="bg-gray-800/50 border border-border-subtle rounded-lg px-3 py-2 text-xs font-mono text-gray-300 whitespace-pre-wrap break-all max-h-64 overflow-y-auto">${escapeHtml(bodyPreview)}</pre>`;
html += '</div>';
}
html += '</div>';
body.innerHTML = html;
document.getElementById('ping-modal').classList.remove('hidden');
}
function closePingModal() {
document.getElementById('ping-modal').classList.add('hidden');
}
// Close on Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closePingModal();
});
// Click handler for ping rows (event delegation)
document.getElementById('pings-table').addEventListener('click', (e) => {
const row = e.target.closest('tr[data-ping]');
if (!row) return;
try {
const ping = JSON.parse(row.dataset.ping);
openPingModal(ping);
} catch {}
});
// Toggle button
document.getElementById('toggle-btn').onclick = async (e) => {
e.preventDefault();
@ -610,7 +704,8 @@
const tbody = document.getElementById('pings-table');
if (tbody) {
const tr = document.createElement('tr');
tr.className = 'table-row-alt';
tr.className = 'table-row-alt cursor-pointer hover:bg-white/[0.02]';
tr.dataset.ping = JSON.stringify(ping);
const regionDisplay = ping.region || '—';
tr.innerHTML = `
<td class="px-4 py-2">${ping.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>'}</td>