remove em dashes
This commit is contained in:
Vendored
+2
-2
@@ -3,7 +3,7 @@
|
||||
// this cache each poll re-runs a 500-row scan against `monitors` with an array
|
||||
// predicate, which dominates the api's Postgres traffic at any real fleet size.
|
||||
//
|
||||
// The list almost never changes between polls — monitor create/edit/delete is at
|
||||
// The list almost never changes between polls - monitor create/edit/delete is at
|
||||
// most a few times per hour. So we memoize per-region with a short TTL and bust
|
||||
// the cache from the monitor mutation handlers so edits are visible instantly.
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function getMonitorsForRegion(region: string): Promise<MonitorRow[]
|
||||
}
|
||||
|
||||
// Called by monitor create/patch/delete/toggle handlers. Wipes the entire
|
||||
// region map — fine because (a) entries are tiny, (b) refresh is cheap, and
|
||||
// region map - fine because (a) entries are tiny, (b) refresh is cheap, and
|
||||
// (c) we don't know which regions a freshly-edited monitor belongs to without
|
||||
// reading it back. Simpler than per-region invalidation, identical net effect.
|
||||
export function invalidateMonitorList(): void {
|
||||
|
||||
@@ -9,7 +9,7 @@ import sql from "../db";
|
||||
// Each periodic pass scans ONLY pings newer than the watermark, then merges
|
||||
// them into existing rollup rows additively (total = total + new_total, etc.)
|
||||
// via ON CONFLICT … DO UPDATE. This makes per-pass work proportional to the
|
||||
// delta of new pings, not the bucket size — critical once a single account has
|
||||
// delta of new pings, not the bucket size - critical once a single account has
|
||||
// thousands of monitors.
|
||||
|
||||
type BucketType = "hourly" | "daily";
|
||||
@@ -50,7 +50,7 @@ async function rollupSinceWatermark(bucket: BucketType): Promise<number> {
|
||||
// after `boundary` will be picked up on the next pass.
|
||||
const boundary = new Date();
|
||||
|
||||
// GROUP BY 1,2,4 (ordinals) instead of repeating the date_trunc expression —
|
||||
// GROUP BY 1,2,4 (ordinals) instead of repeating the date_trunc expression -
|
||||
// when the unit is a $-bound parameter, Postgres won't recognize the two
|
||||
// expressions as identical and will reject the column. Ordinals are safe.
|
||||
//
|
||||
@@ -91,7 +91,7 @@ async function rollupSinceWatermark(bucket: BucketType): Promise<number> {
|
||||
// One-shot recompute over an arbitrary window, fully overwriting matched rows.
|
||||
// Used for the startup backfill and the "still empty after backfill" force-run.
|
||||
// Takes an explicit upper boundary so the caller can capture it BEFORE running
|
||||
// the recompute and use the same value for the watermark write afterwards —
|
||||
// the recompute and use the same value for the watermark write afterwards -
|
||||
// any ping with checked_at > boundary is guaranteed to be outside this window
|
||||
// and will be picked up by the first incremental pass instead. This closes
|
||||
// the race where a ping ingested between boundary capture and the SELECT
|
||||
@@ -137,7 +137,7 @@ export async function startRollupJob() {
|
||||
// Startup backfill. Capture the boundary FIRST, then run the one-shot
|
||||
// recompute bounded by it. The watermark is then set to the same boundary,
|
||||
// so any ping with checked_at > boundary is guaranteed to be picked up by
|
||||
// the first incremental pass — never folded in twice and never missed.
|
||||
// the first incremental pass - never folded in twice and never missed.
|
||||
try {
|
||||
const boundary = new Date();
|
||||
const [h, d] = await Promise.all([
|
||||
@@ -150,7 +150,7 @@ export async function startRollupJob() {
|
||||
setWatermark("daily", boundary),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error("[rollup] backfill FAILED — rollup table will be empty until fixed:", e);
|
||||
console.error("[rollup] backfill FAILED - rollup table will be empty until fixed:", e);
|
||||
}
|
||||
|
||||
// Force-run check: if any bucket type is still empty after the backfill,
|
||||
@@ -159,7 +159,7 @@ export async function startRollupJob() {
|
||||
try {
|
||||
for (const b of ["hourly", "daily"] as BucketType[]) {
|
||||
if (await rollupIsEmpty(b)) {
|
||||
console.log(`[rollup] ${b} still empty — forcing incremental aggregation`);
|
||||
console.log(`[rollup] ${b} still empty - forcing incremental aggregation`);
|
||||
// Reset watermark so the pass picks up everything in retention.
|
||||
await setWatermark(b, new Date(0));
|
||||
await rollupSinceWatermark(b);
|
||||
@@ -170,7 +170,7 @@ export async function startRollupJob() {
|
||||
}
|
||||
|
||||
// Periodic incremental refreshes. Each pass scans only pings newer than the
|
||||
// last watermark, so the work is proportional to the delta — not the bucket
|
||||
// last watermark, so the work is proportional to the delta - not the bucket
|
||||
// size. Hourly runs frequently so the current-hour bar appears quickly for
|
||||
// fresh monitors; daily can run less often.
|
||||
setInterval(() => { rollupSinceWatermark("hourly").catch((e) => console.warn("[rollup] hourly failed:", e)); }, 30 * 1000); // every 30s
|
||||
|
||||
@@ -108,13 +108,13 @@ export const internal = new Elysia({ prefix: "/internal", detail: { hide: true }
|
||||
const lookaheadMs = Math.min(Number(params.get('lookahead_ms') || 2000), 10000);
|
||||
|
||||
// No JOIN, no state lookup. The check schedule for a monitor is purely
|
||||
// a function of its created_at and interval_s — every interval since
|
||||
// a function of its created_at and interval_s - every interval since
|
||||
// creation is a tick. We pull all enabled monitors that match this
|
||||
// region, compute the next tick in JS, and return the ones whose next
|
||||
// tick falls within the lookahead window.
|
||||
//
|
||||
// The monitor list itself is memoized in apps/api/src/cache/monitor-list.ts
|
||||
// with a 5s TTL — runners poll this endpoint roughly once a second per
|
||||
// with a 5s TTL - runners poll this endpoint roughly once a second per
|
||||
// region, but the underlying list almost never changes between polls. The
|
||||
// cache is busted from monitor create/patch/delete/toggle so edits show up
|
||||
// immediately.
|
||||
|
||||
@@ -17,7 +17,7 @@ const MonitorBody = t.Object({
|
||||
retry_interval_s: t.Optional(t.Number({ minimum: 1, maximum: 600, default: 30, description: "Seconds between retries" })),
|
||||
resend_interval: t.Optional(t.Number({ minimum: 0, maximum: 1000, default: 0, description: "Re-alert every Nth consecutive down beat. 0 = never resend." })),
|
||||
cert_alert_days: t.Optional(t.Number({ minimum: 0, maximum: 365, default: 0, description: "Alert when TLS cert is within N days of expiry. 0 disables (default)." })),
|
||||
query: t.Optional(t.Any({ description: "PingQL query — filter conditions for up/down" })),
|
||||
query: t.Optional(t.Any({ description: "PingQL query - filter conditions for up/down" })),
|
||||
regions: t.Optional(t.Array(t.String(), { description: "Regions to run checks from. Empty array = all regions." })),
|
||||
channel_ids: t.Optional(t.Array(t.String(), { description: "Notification channel IDs to attach to this monitor." })),
|
||||
tags: t.Optional(t.Array(t.String({ pattern: "^[a-z0-9][a-z0-9-]{0,40}$" }), { description: "Lowercase tag slugs for grouping. Replaces the existing tag set." })),
|
||||
|
||||
@@ -61,7 +61,7 @@ export const ingest = new Elysia()
|
||||
// notifications never carry an empty label.
|
||||
const region = body.region && body.region.length > 0 ? body.region : 'default';
|
||||
|
||||
// The monitor lookup and the per-region state lookup are independent —
|
||||
// The monitor lookup and the per-region state lookup are independent -
|
||||
// the state row's primary key doesn't depend on anything from the monitor
|
||||
// row. Fire them in parallel to halve the wall-clock cost on the hottest
|
||||
// path in the system. (Combining them into a JOIN is a wash on a warm
|
||||
|
||||
@@ -39,7 +39,7 @@ const StatusPageBody = t.Object({
|
||||
}))),
|
||||
});
|
||||
|
||||
// Strip @import and expression() from custom CSS — basic sanity, not a full
|
||||
// Strip @import and expression() from custom CSS - basic sanity, not a full
|
||||
// parser. The CSS still runs in the visitor's browser; this just blocks the
|
||||
// most common smuggling vectors.
|
||||
function sanitizeCss(css: string | null | undefined): string | null {
|
||||
@@ -63,7 +63,7 @@ async function replaceGroupsAndMonitors(
|
||||
await sql`DELETE FROM status_page_groups WHERE status_page_id = ${pageId}`;
|
||||
}
|
||||
// Single bulk INSERT instead of one round-trip per group. The RETURNING set
|
||||
// comes back in INSERT order, which equals the array order — that lets us
|
||||
// comes back in INSERT order, which equals the array order - that lets us
|
||||
// map index → id without a follow-up SELECT. Mirrors the bulk insert pattern
|
||||
// used by the monitors block right below.
|
||||
const groupIds: string[] = [];
|
||||
|
||||
@@ -21,7 +21,7 @@ function isPrivateIP(ip: string): boolean {
|
||||
if (second >= 16 && second <= 31) return true;
|
||||
}
|
||||
|
||||
// IPv6 — normalize: strip zone ID (%eth0) and lowercase
|
||||
// IPv6 - normalize: strip zone ID (%eth0) and lowercase
|
||||
const ip6 = ip.replace(/%.*$/, "").toLowerCase();
|
||||
if (ip6 === "::1" || ip6 === "::") return true;
|
||||
if (ip6.startsWith("fe80")) return true; // fe80::/10 link-local
|
||||
@@ -29,7 +29,7 @@ function isPrivateIP(ip: string): boolean {
|
||||
if (ip6.startsWith("fd00:ec2::")) return true; // AWS EC2 metadata IPv6
|
||||
if (ip6 === "::ffff:127.0.0.1") return true;
|
||||
if (ip6.startsWith("::ffff:")) {
|
||||
// IPv4-mapped IPv6 — extract the IPv4 part and re-check
|
||||
// IPv4-mapped IPv6 - extract the IPv4 part and re-check
|
||||
const v4 = ip6.slice(7);
|
||||
return isPrivateIP(v4);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export async function validateMonitorUrl(url: string): Promise<string | null> {
|
||||
}
|
||||
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
return `Blocked scheme: ${parsed.protocol} — only http: and https: are allowed`;
|
||||
return `Blocked scheme: ${parsed.protocol} - only http: and https: are allowed`;
|
||||
}
|
||||
|
||||
const hostname = parsed.hostname.toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user