v1.1.0
The first feature minor since the 1.0.x line. Headline additions are inbound webhook receivers (push events into LogTide from CI/CD, uptime monitors and arbitrary tools through tokenized per-receiver endpoints) and project soft-delete with a 30-day recoverable grace window. Alongside them, five community-reported dashboard and query fixes (#271 to #275) tighten how totals, filter dropdowns and the activity/top-services widgets behave. Database migrations 050, 051 and 052 run on upgrade.
Added
- Inbound webhook receivers (#155): external systems (CI/CD, uptime monitors, arbitrary tools) can now push events into LogTide through per-receiver tokenized endpoints (
POST /api/v1/receivers/:id/:token,lr_-prefixed tokens stored as SHA-256 hashes, timing-safe compare; the URL is the credential since GitHub/Uptime Robot cannot send custom headers). Three adapters normalize payloads into log entries: GitHub (workflow_run and deployment_status events with conclusion-to-level mapping; ping and unsupported events marked skipped), Uptime (shape-detects Uptime Robot alertType and Better Stack incident payloads), and Generic JSON (optional dot-path field mapping with levelMap and defaults, validated by a shared Zod schema; full payload always preserved in metadata). The endpoint answers 202 and processing is asynchronous via a newreceiver-eventsqueue job (queue abstraction, both backends), which runs the adapter, validates againstlogSchemaand ingests throughingestionService.ingestLogs, so PII masking (fail-closed), usage quotas, Sigma detection, pipelines, metering and live tail all apply automatically; the outcome (processed/skipped/failed, normalized output, error) is recorded on areceiver_eventsrow pruned to the last 100 per receiver. Management: project-scoped CRUD + recent-events API under/api/v1/projects/:projectId/receivers(session auth, audit-logged asreceiver.created/receiver.deleted), a newreceivers.maxcapability limit enforced with the canonical withLimitLock pattern (4-case + race tests), and a "Webhook Receivers" section in project settings (create dialog with adapter picker and generic field-mapping inputs, one-time URL reveal with copy, enable/disable switch, recent-events viewer with raw/normalized JSON). Migration 052 (receivers,receiver_events); public docs indocs/receivers.md - Soft-delete for projects with a 30-day grace window: deleting a project now moves it to a recoverable "trash" state (
deleted_at) instead of removing it immediately. Its logs, traces and metrics stay queryable and the project stays viewable read-only during the window (the project detail layout loads withincludeDeleted, shows a "deleted (read-only)" banner with the permanent-deletion date, and hides the Settings tab); a "Restore" action brings it back. A daily purge worker (3 AM) hard-deletes projects past the grace window, callingreservoir.purgeProject()to clear logs/spans/metrics from every storage engine before deleting the row. Name and slug uniqueness moved to partial unique indexes (active projects only) so a deleted project's name/slug can be reused; restoring into a name or slug a new active project has since taken is rejected with a 409 instead of a raw constraint error. Soft-deleted projects are excluded from listings, data-availability widgets and API-key verification (ingestion is refused immediately, with the key-verification cache invalidated on delete). NewPOST /api/v1/projects/:id/restoreandGET /api/v1/projects?includeDeleted=true. Migrations 050 (soft-delete column + partial indexes) and 051 (dropON DELETE CASCADEfrom logs/spans/metrics so an out-of-band project delete cannot bulk-wipe data; the purge worker clears reservoir data explicitly first)
Fixed
- Log total no longer disagrees with the visible logs on TimescaleDB (#271): the displayed result count came from the Postgres query planner's row estimate, which is unreliable at small scale (stale
ANALYZEstats, few rows per chunk) and could be wrong in both directions, so the header showed a total that did not match the listed logs.countEstimatenow falls back to an exactCOUNTbelow a 50k-row estimate and keeps the fast planner estimate above it, so small and moderate deployments see an accurate total while large datasets stay performant. - Dashboard editing over plain HTTP on a LAN IP (#272): adding a panel called
crypto.randomUUID(), which only exists in a secure context (HTTPS or localhost), so it threw when LogTide was accessed over plain HTTP by IP. Panel IDs are now generated through auuid()helper that falls back tocrypto.getRandomValues()(available in non-secure contexts) whenrandomUUIDis missing. - Hosts missing from the filter dropdown (#273):
getDistinctHostnamesclamped its lookup window to 6 hours regardless of the selected range, so hosts that had only logged 6-24h ago were dropped from the hosts dropdown even though their logs were still visible in the (24h) logs view. The window now honors the requested range (default 24h, matching the log query default) so the dropdown lists every host with logs in the same window; hostnames stay cached for 5 minutes to bound the JSONB distinct cost. - Activity Overview chart stuck 1-2h behind live data (#274): on TimescaleDB the panel read its logs/spans/detections series entirely from continuous aggregates, whose refresh policy (
end_offset = 1 bucket, running once per bucket) never materializes the most recent 1-2 buckets, so the chart's latest edge showed empty while logs were still arriving (the raw-backed logs view stayed current, which read like a timezone offset). The panel now reads the cagg only up to a bucket-aligned cutoff two buckets back and backfills the recent tail live from the raw tables, keeping the fast aggregate for the bulk of the window while the latest buckets track real time. ClickHouse/MongoDB are unaffected (no continuous aggregates); alerts already read raw. - Top Services table window was unlabeled (#275): the "Top Services by Volume" widget covers a fixed 7-day window (and its total is a 7-day total), but it sat next to the 24h-framed activity chart with no window label, so its service list and total looked inconsistent with the logs page (which follows the selected range, 24h by default) and read like stale/stuck data. The widget title now states its window ("Last 7 Days") so the different counts are self-explanatory; the 7-day window itself is intentional (daily continuous aggregate for the historical span) and unchanged.
Full Changelog: v1.0.3...v1.1.0