From 8c3cc3739a353d3d668101a0a4b52a49665f13f8 Mon Sep 17 00:00:00 2001 From: nate Date: Fri, 10 Apr 2026 07:10:11 +0400 Subject: [PATCH] split timings, remove useless kvs --- apps/api/src/routes/status_pages.ts | 15 +++------- apps/monitor/src/runner.rs | 40 ++++++++++++++++++------- apps/shared/db.ts | 3 -- apps/status/src/data.ts | 17 +---------- apps/status/src/static/favicon.svg | 8 +++++ apps/status/src/views/page.ejs | 3 +- apps/web/src/routes/dashboard.ts | 6 ++-- apps/web/src/views/docs.ejs | 3 -- apps/web/src/views/status-page-edit.ejs | 9 ------ 9 files changed, 46 insertions(+), 58 deletions(-) create mode 100644 apps/status/src/static/favicon.svg diff --git a/apps/api/src/routes/status_pages.ts b/apps/api/src/routes/status_pages.ts index e676881..59cd5ca 100644 --- a/apps/api/src/routes/status_pages.ts +++ b/apps/api/src/routes/status_pages.ts @@ -14,14 +14,11 @@ const StatusPageBody = t.Object({ index_search: t.Optional(t.Boolean()), show_powered_by: t.Optional(t.Boolean()), show_response_time: t.Optional(t.Boolean()), - show_cert_expiry: t.Optional(t.Boolean()), bar_frequency: t.Optional(BarFrequency), bar_count: t.Optional(t.Number({ minimum: 1, maximum: 180 })), custom_domain: t.Optional(t.Nullable(t.String({ maxLength: 253 }))), custom_css: t.Optional(t.Nullable(t.String({ maxLength: 50_000 }))), footer_text: t.Optional(t.Nullable(t.String({ maxLength: 5000 }))), - og_image_url: t.Optional(t.Nullable(t.String({ maxLength: 2048 }))), - analytics_html: t.Optional(t.Nullable(t.String({ maxLength: 5000 }))), auto_refresh_s: t.Optional(t.Number({ minimum: 10, maximum: 3600 })), groups: t.Optional(t.Array(t.Object({ name: t.String({ minLength: 1, maxLength: 200 }), @@ -128,18 +125,17 @@ export const statusPages = new Elysia({ prefix: "/pages" }) [row] = await sql` INSERT INTO status_pages ( account_id, slug, title, description, theme, password_hash, index_search, - show_powered_by, show_response_time, show_cert_expiry, + show_powered_by, show_response_time, bar_frequency, bar_count, - custom_domain, custom_css, footer_text, og_image_url, analytics_html, auto_refresh_s + custom_domain, custom_css, footer_text, auto_refresh_s ) VALUES ( ${accountId}, ${body.slug}, ${body.title}, ${body.description ?? null}, ${body.theme ?? 'auto'}, ${password_hash}, ${body.index_search ?? true}, ${body.show_powered_by ?? true}, ${body.show_response_time ?? true}, - ${body.show_cert_expiry ?? false}, ${body.bar_frequency ?? 'daily'}, ${body.bar_count ?? 90}, - ${body.custom_domain || null}, ${css}, ${body.footer_text ?? null}, ${body.og_image_url ?? null}, - ${body.analytics_html ?? null}, ${body.auto_refresh_s ?? 60} + ${body.custom_domain || null}, ${css}, ${body.footer_text ?? null}, + ${body.auto_refresh_s ?? 60} ) RETURNING * `; @@ -191,14 +187,11 @@ export const statusPages = new Elysia({ prefix: "/pages" }) index_search = COALESCE(${body.index_search ?? null}, index_search), show_powered_by = COALESCE(${body.show_powered_by ?? null}, show_powered_by), show_response_time = COALESCE(${body.show_response_time ?? null}, show_response_time), - show_cert_expiry = COALESCE(${body.show_cert_expiry ?? null}, show_cert_expiry), bar_frequency = COALESCE(${body.bar_frequency ?? null}, bar_frequency), bar_count = COALESCE(${body.bar_count ?? null}, bar_count), custom_domain = CASE WHEN ${body.custom_domain !== undefined} THEN ${body.custom_domain || null} ELSE custom_domain END, custom_css = CASE WHEN ${body.custom_css !== undefined} THEN ${css} ELSE custom_css END, footer_text = COALESCE(${body.footer_text ?? null}, footer_text), - og_image_url = COALESCE(${body.og_image_url ?? null}, og_image_url), - analytics_html = COALESCE(${body.analytics_html ?? null}, analytics_html), auto_refresh_s = COALESCE(${body.auto_refresh_s ?? null}, auto_refresh_s), updated_at = now() WHERE id = ${params.id} AND account_id = ${accountId} diff --git a/apps/monitor/src/runner.rs b/apps/monitor/src/runner.rs index a13223a..5063db4 100644 --- a/apps/monitor/src/runner.rs +++ b/apps/monitor/src/runner.rs @@ -160,7 +160,6 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op }); let start = Instant::now(); - let method = monitor.method.as_deref().unwrap_or("GET").to_uppercase(); let timeout = std::time::Duration::from_millis(monitor.timeout_ms.unwrap_or(30000)); let is_https = monitor.url.starts_with("https://"); @@ -170,7 +169,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op let req_body = monitor.request_body.clone(); let max_redirects = monitor.max_redirects; - let (tx, rx) = tokio::sync::oneshot::channel::, String), String>>(); + let (tx, rx) = tokio::sync::oneshot::channel::, String, u64, u64), String>>(); std::thread::spawn(move || { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { run_check_blocking(&url, &method, req_headers.as_ref(), req_body.as_deref(), timeout, max_redirects) @@ -187,10 +186,9 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op .and_then(|r| r.map_err(|_| "check thread dropped".to_string())) .unwrap_or_else(|e| Err(e)); - let latency_ms = start.elapsed().as_millis() as u64; - match result { Err(ref e) => { + let latency_ms = start.elapsed().as_millis() as u64; debug!("{} check error: {e}", monitor.url); PingResult { monitor_id: monitor.id.clone(), @@ -209,7 +207,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op run_id: Some(run_id.to_string()), } }, - Ok((status, headers, body)) => { + Ok((status, headers, body, total_ms, dns_ms)) => { let cert_handle = if is_https { let cert_url = monitor.url.clone(); @@ -233,7 +231,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op status, body: body.clone(), headers: headers.clone(), - latency_ms: Some(latency_ms), + latency_ms: Some(total_ms.saturating_sub(dns_ms)), cert_expiry_days: None, cert_issuer: None, }; @@ -253,14 +251,18 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op None => None, }; let cert_expiry_days = cert_info.as_ref().map(|c| c.expiry_days); + let tls_ms = cert_info.as_ref().map(|c| c.tls_ms).unwrap_or(0); let cert_issuer = cert_info.map(|c| c.issuer); + // Subtract DNS and TLS time from total to get server response time only + let latency_ms = total_ms.saturating_sub(dns_ms).saturating_sub(tls_ms); + let meta = json!({ "headers": headers, "body_preview": &body[..body.len().min(25_000)], }); - debug!("{} → {status} {latency_ms}ms up={up}", monitor.url); + debug!("{} → {status} {latency_ms}ms (total={total_ms} dns={dns_ms} tls={tls_ms}) up={up}", monitor.url); PingResult { monitor_id: monitor.id.clone(), @@ -282,6 +284,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op } } +// Returns (status, headers, body, total_ms, dns_ms) fn run_check_blocking( url: &str, method: &str, @@ -289,7 +292,18 @@ fn run_check_blocking( body: Option<&str>, timeout: std::time::Duration, max_redirects: u32, -) -> Result<(u16, HashMap, String), String> { +) -> Result<(u16, HashMap, String, u64, u64), String> { + // Measure DNS resolution time separately (lookup only, no connection) + let dns_ms = { + let parsed = reqwest::Url::parse(url).map_err(|e| e.to_string())?; + let host = parsed.host_str().unwrap_or(""); + let port = parsed.port().unwrap_or(if parsed.scheme() == "https" { 443 } else { 80 }); + let addr = format!("{host}:{port}"); + let dns_start = Instant::now(); + let _ = std::net::ToSocketAddrs::to_socket_addrs(&addr as &str); + dns_start.elapsed().as_millis() as u64 + }; + let root_certs = ROOT_CERTS.with(|c| Arc::clone(c)); let tls = ureq::tls::TlsConfig::builder() @@ -306,6 +320,8 @@ fn run_check_blocking( .build() .new_agent(); + let request_start = Instant::now(); + let mut builder = ureq::http::Request::builder() .method(method) .uri(url); @@ -368,12 +384,14 @@ fn run_check_blocking( } }; - Ok((status, resp_headers, body_out)) + let total_ms = request_start.elapsed().as_millis() as u64; + Ok((status, resp_headers, body_out, total_ms, dns_ms)) } struct CertInfo { expiry_days: i64, issuer: String, + tls_ms: u64, } async fn check_cert(url: &str) -> Result> { @@ -398,7 +416,9 @@ async fn check_cert(url: &str) -> Result> { let server_name = ServerName::try_from(host.to_string())?; let stream = TcpStream::connect(format!("{host}:{port}")).await?; + let tls_start = Instant::now(); let tls_stream = connector.connect(server_name, stream).await?; + let tls_ms = tls_start.elapsed().as_millis() as u64; let (_, conn) = tls_stream.get_ref(); let certs = conn.peer_certificates().unwrap_or(&[]); @@ -412,7 +432,7 @@ async fn check_cert(url: &str) -> Result> { .as_secs() as i64; let days = (not_after - now) / 86400; let issuer = cert.issuer().to_string(); - return Ok(Some(CertInfo { expiry_days: days, issuer })); + return Ok(Some(CertInfo { expiry_days: days, issuer, tls_ms })); } Ok(None) diff --git a/apps/shared/db.ts b/apps/shared/db.ts index 06af9c7..31b22ee 100644 --- a/apps/shared/db.ts +++ b/apps/shared/db.ts @@ -129,14 +129,11 @@ export async function migrate(sql: any) { index_search BOOLEAN NOT NULL DEFAULT true, show_powered_by BOOLEAN NOT NULL DEFAULT true, show_response_time BOOLEAN NOT NULL DEFAULT true, - show_cert_expiry BOOLEAN NOT NULL DEFAULT false, bar_frequency TEXT NOT NULL DEFAULT 'daily', bar_count INTEGER NOT NULL DEFAULT 90, custom_domain TEXT UNIQUE, custom_css TEXT, footer_text TEXT, - og_image_url TEXT, - analytics_html TEXT, auto_refresh_s INTEGER NOT NULL DEFAULT 60, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() diff --git a/apps/status/src/data.ts b/apps/status/src/data.ts index 1a8568a..8bfa8b2 100644 --- a/apps/status/src/data.ts +++ b/apps/status/src/data.ts @@ -17,14 +17,12 @@ export interface StatusPageRow { index_search: boolean; show_powered_by: boolean; show_response_time:boolean; - show_cert_expiry: boolean; + bar_frequency: BucketType; bar_count: number; custom_domain: string | null; custom_css: string | null; footer_text: string | null; - og_image_url: string | null; - analytics_html: string | null; auto_refresh_s: number; } @@ -498,11 +496,6 @@ export async function loadMonitorDetail(slug: string, monitorId: string): Promis // The shape we actually expose to anonymous visitors. Computed by stripping // internal IDs and any field a public consumer doesn't need from the row // types - see redactPageForPublic / redactGroupsAndMonitors below. -// -// custom_css and analytics_html are kept here even though they're noisy to -// JSON consumers, because the HTML render reads from this same object - and -// they're already publicly visible in the rendered HTML, so dropping them -// from JSON wouldn't actually add any privacy. export interface PublicPageView { slug: string; title: string; @@ -511,13 +504,9 @@ export interface PublicPageView { index_search: boolean; show_powered_by: boolean; show_response_time: boolean; - show_cert_expiry: boolean; bar_frequency: BucketType; bar_count: number; - custom_css: string | null; footer_text: string | null; - og_image_url: string | null; - analytics_html: string | null; auto_refresh_s: number; has_password: boolean; } @@ -549,13 +538,9 @@ function redactPageForPublic(p: StatusPageRow): PublicPageView { index_search: p.index_search, show_powered_by: p.show_powered_by, show_response_time: p.show_response_time, - show_cert_expiry: p.show_cert_expiry, bar_frequency: p.bar_frequency, bar_count: p.bar_count, - custom_css: p.custom_css, footer_text: p.footer_text, - og_image_url: p.og_image_url, - analytics_html: p.analytics_html, auto_refresh_s: p.auto_refresh_s, has_password: !!p.password_hash, }; diff --git a/apps/status/src/static/favicon.svg b/apps/status/src/static/favicon.svg new file mode 100644 index 0000000..fba6aff --- /dev/null +++ b/apps/status/src/static/favicon.svg @@ -0,0 +1,8 @@ + + + P + Q + + + + diff --git a/apps/status/src/views/page.ejs b/apps/status/src/views/page.ejs index b0e4879..3b914c6 100644 --- a/apps/status/src/views/page.ejs +++ b/apps/status/src/views/page.ejs @@ -105,12 +105,11 @@ <% if (!page.index_search) { %><% } %> <% if (page.description) { %><% } %> - <% if (page.og_image_url) { %><% } %> + <% if (page.custom_css) { %><% } %> - <% if (page.analytics_html) { %><%~ page.analytics_html %><% } %>
diff --git a/apps/web/src/routes/dashboard.ts b/apps/web/src/routes/dashboard.ts index 1c7b352..3bcdc5d 100644 --- a/apps/web/src/routes/dashboard.ts +++ b/apps/web/src/routes/dashboard.ts @@ -738,11 +738,11 @@ export const dashboard = new Elysia() bar_frequency: b.bar_frequency || "daily", bar_count: Number(b.bar_count) || 90, show_response_time: !!b.show_response_time, - show_cert_expiry: !!b.show_cert_expiry, + show_powered_by: !!b.show_powered_by, index_search: !!b.index_search, auto_refresh_s: Number(b.auto_refresh_s) || 60, - og_image_url: b.og_image_url || null, + password: b.password || undefined, custom_domain: b.custom_domain || null, custom_css: b.custom_css || null, @@ -771,11 +771,9 @@ export const dashboard = new Elysia() bar_frequency: b.bar_frequency || "daily", bar_count: Number(b.bar_count) || 90, show_response_time: !!b.show_response_time, - show_cert_expiry: !!b.show_cert_expiry, show_powered_by: !!b.show_powered_by, index_search: !!b.index_search, auto_refresh_s: Number(b.auto_refresh_s) || 60, - og_image_url: b.og_image_url || null, custom_domain: b.custom_domain || null, custom_css: b.custom_css || null, footer_text: b.footer_text || null, diff --git a/apps/web/src/views/docs.ejs b/apps/web/src/views/docs.ejs index dbb600d..e35d583 100644 --- a/apps/web/src/views/docs.ejs +++ b/apps/web/src/views/docs.ejs @@ -300,12 +300,9 @@ Content-Type: application/json custom_domainstring?Custom domain for the page (e.g. status.example.com). CNAME to the status server IP. SSL is automatic. custom_cssstring?Custom CSS injected after the default stylesheet (up to 50KB). footer_textstring?Text shown in the page footer (up to 5000 chars). - og_image_urlstring?OpenGraph image URL for social previews. - analytics_htmlstring?Raw HTML injected in the head (e.g. analytics snippet). index_searchboolean?Allow search engine indexing. Default true. show_powered_byboolean?Show "Powered by PingQL" footer. Default true. show_response_timeboolean?Display response time per monitor. Default true. - show_cert_expiryboolean?Show cert expiry info. Default false. groupsarray?Groups to organize monitors. Each has name (string) and optional position (number). monitorsarray?Monitors to display. See monitor fields below. diff --git a/apps/web/src/views/status-page-edit.ejs b/apps/web/src/views/status-page-edit.ejs index 61abb06..a010f64 100644 --- a/apps/web/src/views/status-page-edit.ejs +++ b/apps/web/src/views/status-page-edit.ejs @@ -223,10 +223,6 @@ class="accent-blue-500"> Show response time -