v1.1.0
Released
Feature
1.1.0: Inbound Webhook Receivers & Project Soft-Delete
A feature release. External systems can now push events into LogTide through tokenized inbound webhook receivers (GitHub, Uptime, generic JSON), and deleting a project moves it to a recoverable 30-day trash instead of wiping it. Plus five dashboard and log-count correctness fixes.
- Inbound webhook receivers: GitHub, Uptime Robot / Better Stack and generic-JSON senders can push events in via per-receiver tokenized URLs
- Received events flow through the full pipeline — PII masking, quotas, Sigma detection, metering and live tail all apply
- Project soft-delete: deletion moves a project to a recoverable 30-day trash (still queryable, read-only) with a Restore action
- Log total is now exact below 50k rows on TimescaleDB instead of an unreliable planner estimate (#271)
- Activity Overview chart tracks live data again instead of lagging 1–2h behind (#274)
- Dashboard editing works over plain HTTP on a LAN IP again (#272)
A feature release. The two headline additions are inbound webhook receivers — a tokenized ingestion path so external systems (CI/CD, uptime monitors, arbitrary tools) can push events into LogTide — and project soft-delete, which turns project deletion into a recoverable 30-day trash instead of an immediate wipe. Alongside them, five dashboard and log-count correctness fixes.
Added
- Inbound webhook receivers (#155): external systems can now push events into LogTide through per-receiver tokenized endpoints (
POST /api/v1/receivers/:id/:token). Tokens arelr_-prefixed, stored as SHA-256 hashes and compared timing-safe — the URL is the credential, since senders like GitHub and Uptime Robot can’t add custom headers. Three adapters normalize payloads into log entries: GitHub (workflow_run/deployment_statuswith conclusion-to-level mapping; ping and unsupported events marked skipped), Uptime (shape-detects Uptime Robot and Better Stack incident payloads), and Generic JSON (optional dot-path field mapping withlevelMapand defaults; the full payload is always preserved in metadata). The endpoint answers202and processing is asynchronous via a newreceiver-eventsqueue job, 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. Each outcome (processed/skipped/failed, normalized output, error) is recorded on areceiver_eventsrow pruned to the last 100 per receiver. Management is project-scoped CRUD + a recent-events API (session auth, audit-logged), a newreceivers.maxcapability limit enforced with the canonicalwithLimitLockpattern, and a “Webhook Receivers” section in project settings (adapter picker, one-time URL reveal with copy, enable/disable, recent-events viewer). Migration052 - 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 detail view shows a “deleted (read-only)” banner with the permanent-deletion date and hides the Settings tab — and a Restore action brings it back. A daily purge worker (3 AM) hard-deletes projects past the 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/slug a new project has since taken is rejected with a409instead of a raw constraint error. Soft-deleted projects are excluded from listings, data-availability widgets and API-key verification (ingestion is refused immediately). NewPOST /api/v1/projects/:id/restoreandGET /api/v1/projects?includeDeleted=true. Migrations050(soft-delete column + partial indexes) and051(dropsON DELETE CASCADEfrom logs/spans/metrics so an out-of-band project delete can’t 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 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 either direction.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 - 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 never materializes the most recent 1–2 buckets, so the chart’s latest edge showed empty while logs were still arriving. The panel now reads the aggregate only up to a bucket-aligned cutoff two buckets back and backfills the recent tail live from the raw tables. ClickHouse/MongoDB were unaffected
- Hosts missing from the filter dropdown (#273):
getDistinctHostnamesclamped its lookup to 6 hours regardless of the selected range, so hosts that had only logged 6–24h ago were dropped from the dropdown even though their logs were still visible. The window now honors the requested range (default 24h), and hostnames stay cached for 5 minutes to bound the JSONB distinct cost - 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 now go through auuid()helper that falls back tocrypto.getRandomValues()in non-secure contexts - Top Services table window was unlabeled (#275): the “Top Services by Volume” widget covers a fixed 7-day window, but sat next to the 24h-framed activity chart with no label, so its list and total read like stale data. The title now states its window (“Last 7 Days”); the 7-day window itself is intentional and unchanged