LogTide
v0.9.4
Released
Fix

Cached Data-Availability, Activity Overview Multi-Engine Fix & Page Titles

Data-availability endpoint now reads cached flags on projects (6s+ → <50ms on ClickHouse), activity_overview panel works across all engines, 14 dashboard pages get proper titles, plus uuid 14 and fast-xml-parser 5.7.3 security bumps.

  • GET /api/v1/projects/data-availability rewritten to read cached has_X_at flags — 6s+ → <50ms on production ClickHouse
  • Boot-time one-shot backfill for the new project flags, throttled and gated by system_settings
  • activity_overview panel logs/log_errors series now work on ClickHouse and MongoDB via reservoir.aggregate
  • 14 dashboard pages now set proper <title> instead of falling back to generic 'LogTide'
  • Security: uuid 14.0.0 (CVE-2026-41907) and fast-xml-parser 5.7.3 (CVE-2026-41650) bumps

Changed

  • GET /api/v1/projects/data-availability rewritten to read from cached flags on projects instead of fanning out N*3 existence queries to the reservoir per call. In production (ClickHouse, ~70M rows) the old path scanned count(*) and LIMIT 1 probes across logs/traces/metrics for every project in the org, taking 6s+ on non-trivial orgs. The new path is a single SELECT id, has_logs_at, has_traces_at, has_metrics_at FROM projects WHERE organization_id = $1 plus an in-memory staleness filter against organizations.retention_days, returning in <50ms regardless of ingest volume. The response shape is unchanged ({ logs: string[], traces: string[], metrics: string[] })
  • Ingest paths mark data-availability flags fire-and-forget: after a successful batch in logs / traces / metrics ingest, projectsService.markHasData(projectId, kind) updates projects.has_X_at. A module-scoped in-memory debounce (5 min per (projectId, kind)) prevents UPDATE spam on hot projects: at most one UPDATE per 5 min per pod per project per kind. Failures are logged and swallowed so ingest is never impacted; the boot-time backfill is the safety net

Added

  • Migration 042 project_data_availability: adds nullable has_logs_at, has_traces_at, has_metrics_at TIMESTAMPTZ columns to projects. No backfill in SQL (handled at runtime, see below)
  • One-shot backfill at server boot: runDataAvailabilityBackfill() runs fire-and-forget after app.listen, guarded by a data_availability.backfilled row in system_settings so subsequent boots are a no-op. It selects every project with all three flags still NULL and, for each, runs three LIMIT 1 probes (logs / traces / metrics) via the reservoir in parallel. Throttled in batches of 10 with a 50ms pause to avoid hammering ClickHouse/Timescale/Mongo at boot. On a deployment with 1000 projects this takes ~6s on ClickHouse or MongoDB, up to ~30s on TimescaleDB when most projects are empty (chunk scan confirming zero rows). Force a re-backfill by deleting the safety row
  • Multi-engine staleness rule: a project is reported as “has data” only if has_X_at >= now() - organizations.retention_days. Handles the “data aged out by retention” case without needing a background worker. The same logic works identically on TimescaleDB, ClickHouse and MongoDB deployments because the flag lives in Postgres and the reservoir only backs the ingest and backfill paths

Fixed

  • activity_overview dashboard panel showed empty logs / log_errors series on ClickHouse and MongoDB: the fetcher in panel-data-service.ts gated both the logs and the spans queries behind reservoir.getEngineType() === 'timescale', so on non-Timescale engines those branches were skipped entirely and the panel rendered with all four log/span counters flat at zero (only detections and alerts, which read from Postgres operational tables, kept working). The logs path now branches: TimescaleDB still uses the logs_hourly_stats / logs_daily_stats continuous aggregate with the raw logs fallback, while ClickHouse and MongoDB go through reservoir.aggregate({ interval: '1h' | '1d' }) (same dual pattern already used by baseline-calculator). Bucket boundaries from toStartOfHour / $dateTrunc align with the panel’s UTC buildBucketTimes, so the keys collide cleanly. logs sums total per bucket, log_errors sums byLevel.error + byLevel.critical. Spans / span_errors stay TimescaleDB-only because IReservoir has no aggregateSpans() primitive, matching the existing convention in trace_latency and trace_volume
  • Missing <title> on 14 dashboard pages: every tab opened on /dashboard, /dashboard/admin/{organizations,projects,users,settings} (list and detail), /dashboard/monitoring (list and detail) and /dashboard/projects/[id]/{alerts,overview,performance,settings} showed the generic fallback “LogTide” from app.html because no <svelte:head><title>...</title></svelte:head> was set on the page. They now follow the existing Title - LogTide convention used by the rest of the app, with dynamic titles where the entity is already in scope: {$activeDashboard.name} for the custom dashboards root, {org.name} / {project.name} / {user.name ?? user.email} / {monitor.name} for the four [id] pages (each with a sensible fallback while the loader resolves). The two pure-redirect pages (/dashboard/projects/[id] and /dashboard/settings, which goto their default sub-route on mount) intentionally keep no title since the destination sets one immediately

Security

  • Bump uuid to 14.0.0 (GHSA-w5hq-g745-h8pq, CVE-2026-41907, MODERATE): v3, v5 and v6 accepted a caller-provided buf + offset but did not validate bounds, so a small buffer or an out-of-range offset produced silent partial writes instead of throwing RangeError like v1/v4/v7 do. uuid is pulled in transitively by bullmq and ldapts (production) and bson/ioredis (dev only); both production consumers resolve to their ESM entry points from our "type": "module" backend, so the major bump from ^11.1.0 is safe (uuid 12+ dropped CommonJS, but we never load the CJS path). Added "uuid": ">=14.0.0" to the root pnpm overrides; resolves to 14.0.0. Engines requirement of Node 20+ is already satisfied by engines.node: ">=20.0.0"
  • Bump fast-xml-parser to 5.7.3 (GHSA-gh4j-gqv2-49f6, CVE-2026-41650, MODERATE): XMLBuilder did not escape the --> sequence in comment bodies or the ]]> sequence in CDATA sections, so user-controlled data flowing into either could break out and inject sibling XML nodes (XSS in browser-rendered XML, SOAP injection, RSS/SVG payloads, etc.). The fix lands in 5.7.0; we use it transitively only via @aws-sdk/xml-builder pulled in by @types/nodemailer (dev), but bumping the override from >=5.5.7 to >=5.7.0 clears the lockfile advisory. Resolves to 5.7.3