feat: move response body
This commit is contained in:
parent
bb05dae926
commit
79b7f21591
|
|
@ -64,6 +64,14 @@ export async function migrate() {
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_pings_monitor ON pings(monitor_id, checked_at DESC)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_monitor ON pings(monitor_id, checked_at DESC)`;
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_pings_checked_at ON pings(checked_at)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_checked_at ON pings(checked_at)`;
|
||||||
|
|
||||||
|
// Response bodies stored separately to keep pings table lean
|
||||||
|
await sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS ping_bodies (
|
||||||
|
ping_id BIGINT PRIMARY KEY REFERENCES pings(id) ON DELETE CASCADE,
|
||||||
|
body TEXT
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
await sql`
|
await sql`
|
||||||
CREATE TABLE IF NOT EXISTS api_keys (
|
CREATE TABLE IF NOT EXISTS api_keys (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ export const ingest = new Elysia()
|
||||||
const meta = body.meta ? { ...body.meta } : {};
|
const meta = body.meta ? { ...body.meta } : {};
|
||||||
if (body.cert_expiry_days != null) meta.cert_expiry_days = body.cert_expiry_days;
|
if (body.cert_expiry_days != null) meta.cert_expiry_days = body.cert_expiry_days;
|
||||||
|
|
||||||
|
// Extract response body from meta — stored separately
|
||||||
|
const responseBody: string | null = meta.body_preview ?? null;
|
||||||
|
delete meta.body_preview;
|
||||||
|
|
||||||
const checkedAt = body.checked_at ? new Date(body.checked_at) : null;
|
const checkedAt = body.checked_at ? new Date(body.checked_at) : null;
|
||||||
const scheduledAt = body.scheduled_at ? new Date(body.scheduled_at) : null;
|
const scheduledAt = body.scheduled_at ? new Date(body.scheduled_at) : null;
|
||||||
const jitterMs = body.jitter_ms ?? null;
|
const jitterMs = body.jitter_ms ?? null;
|
||||||
|
|
@ -87,7 +91,12 @@ export const ingest = new Elysia()
|
||||||
RETURNING *
|
RETURNING *
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Look up account and publish to account-level bus
|
// Store response body separately
|
||||||
|
if (responseBody != null && ping) {
|
||||||
|
await sql`INSERT INTO ping_bodies (ping_id, body) VALUES (${ping.id}, ${responseBody})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up account and publish to account-level bus (without body to keep SSE lean)
|
||||||
const [monitor] = await sql`SELECT account_id FROM monitors WHERE id = ${body.monitor_id}`;
|
const [monitor] = await sql`SELECT account_id FROM monitors WHERE id = ${body.monitor_id}`;
|
||||||
if (monitor) publish(monitor.account_id, ping);
|
if (monitor) publish(monitor.account_id, ping);
|
||||||
|
|
||||||
|
|
@ -110,6 +119,28 @@ export const ingest = new Elysia()
|
||||||
detail: { hide: true },
|
detail: { hide: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Fetch response body for a specific ping
|
||||||
|
.get("/pings/:id/body", async ({ params, headers, cookie, set }) => {
|
||||||
|
const authHeader = headers["authorization"] ?? "";
|
||||||
|
const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim();
|
||||||
|
const key = bearer ?? cookie?.pingql_key?.value;
|
||||||
|
if (!key) { set.status = 401; return { error: "Unauthorized" }; }
|
||||||
|
|
||||||
|
const resolved = await resolveKey(key);
|
||||||
|
if (!resolved) { set.status = 401; return { error: "Unauthorized" }; }
|
||||||
|
|
||||||
|
// Verify the ping belongs to this account
|
||||||
|
const [ping] = await sql`
|
||||||
|
SELECT p.id FROM pings p
|
||||||
|
JOIN monitors m ON m.id = p.monitor_id
|
||||||
|
WHERE p.id = ${params.id} AND m.account_id = ${resolved.accountId}
|
||||||
|
`;
|
||||||
|
if (!ping) { set.status = 404; return { error: "Not found" }; }
|
||||||
|
|
||||||
|
const [row] = await sql`SELECT body FROM ping_bodies WHERE ping_id = ${params.id}`;
|
||||||
|
return { body: row?.body ?? null };
|
||||||
|
}, { detail: { hide: true } })
|
||||||
|
|
||||||
// SSE: single stream for all of the account's monitors
|
// SSE: single stream for all of the account's monitors
|
||||||
.get("/account/stream", async ({ headers, cookie }) => {
|
.get("/account/stream", async ({ headers, cookie }) => {
|
||||||
const authHeader = headers["authorization"] ?? "";
|
const authHeader = headers["authorization"] ?? "";
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,14 @@ export async function migrate() {
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_pings_monitor ON pings(monitor_id, checked_at DESC)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_monitor ON pings(monitor_id, checked_at DESC)`;
|
||||||
await sql`CREATE INDEX IF NOT EXISTS idx_pings_checked_at ON pings(checked_at)`;
|
await sql`CREATE INDEX IF NOT EXISTS idx_pings_checked_at ON pings(checked_at)`;
|
||||||
|
|
||||||
|
// Response bodies stored separately to keep pings table lean
|
||||||
|
await sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS ping_bodies (
|
||||||
|
ping_id BIGINT PRIMARY KEY REFERENCES pings(id) ON DELETE CASCADE,
|
||||||
|
body TEXT
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
await sql`
|
await sql`
|
||||||
CREATE TABLE IF NOT EXISTS api_keys (
|
CREATE TABLE IF NOT EXISTS api_keys (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,9 @@
|
||||||
|
|
||||||
// ── Ping detail modal ──────────────────────────────────────────
|
// ── Ping detail modal ──────────────────────────────────────────
|
||||||
function openPingModal(ping) {
|
function openPingModal(ping) {
|
||||||
const body = document.getElementById('ping-modal-body');
|
const modalBody = document.getElementById('ping-modal-body');
|
||||||
const meta = ping.meta || {};
|
const meta = ping.meta || {};
|
||||||
const headers = meta.headers || {};
|
const headers = meta.headers || {};
|
||||||
const bodyPreview = meta.body_preview || null;
|
|
||||||
const time = new Date(ping.checked_at);
|
const time = new Date(ping.checked_at);
|
||||||
const scheduled = ping.scheduled_at ? new Date(ping.scheduled_at) : null;
|
const scheduled = ping.scheduled_at ? new Date(ping.scheduled_at) : null;
|
||||||
|
|
||||||
|
|
@ -223,17 +222,34 @@
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body preview
|
// Response body — loaded on demand
|
||||||
if (bodyPreview) {
|
html += '<div id="ping-body-section">';
|
||||||
html += '<div>';
|
if (ping.id) {
|
||||||
html += '<div class="text-xs text-gray-500 mb-1">Response Body <span class="text-gray-600">(up to 25KB)</span></div>';
|
html += '<div class="text-xs text-gray-500 mb-1">Response Body</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-80 overflow-y-auto">${escapeHtml(bodyPreview)}</pre>`;
|
html += '<div id="ping-body-content" class="bg-gray-800/50 border border-border-subtle rounded-lg px-3 py-2 text-xs font-mono text-gray-500 min-h-[2rem] flex items-center">Loading...</div>';
|
||||||
html += '</div>';
|
|
||||||
}
|
}
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
body.innerHTML = html;
|
modalBody.innerHTML = html;
|
||||||
document.getElementById('ping-modal').classList.remove('hidden');
|
document.getElementById('ping-modal').classList.remove('hidden');
|
||||||
|
|
||||||
|
// Fetch body asynchronously
|
||||||
|
if (ping.id) {
|
||||||
|
api(`/pings/${ping.id}/body`).then(data => {
|
||||||
|
const el = document.getElementById('ping-body-content');
|
||||||
|
if (!el) return;
|
||||||
|
if (data.body) {
|
||||||
|
el.className = '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-80 overflow-y-auto';
|
||||||
|
el.textContent = data.body;
|
||||||
|
} else {
|
||||||
|
el.textContent = 'No body stored';
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
const el = document.getElementById('ping-body-content');
|
||||||
|
if (el) el.textContent = 'Failed to load';
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closePingModal() {
|
function closePingModal() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue