Commit Graph
16 Commits
Author SHA1 Message Date
Hugh KaznowskiandClaude Sonnet 5 dd3320812c Add alert rules with FilterBar-based triggers and ntfy delivery (tickets 017-019)
Adds AlertRule/AlertCondition/AlertSink models with CRUD persisted on
Settings (mirroring CssEntry's shape), per-line trigger evaluation hooked
into the engine's ingest path (Immediate/Threshold/NewValue/Ratio
conditions), and best-effort ntfy delivery spawned off the ingest hot path.

Design notes:
- Alert queries reuse FilterBar syntax but only support the
  regex()/sequence/timestamp subset (no metric[param] comparisons), since
  resolving those would require a full bucket rescan per alert per ingested
  line rather than cost proportional to new logs.
- Ratio's zero-denominator case skips evaluation for that line rather than
  treating the ratio as infinite.
- NewValue's seen-value set is in-memory only and does not survive a
  restart, matching the engine's other non-persisted runtime state.

engine::run now takes a SettingsHandle (moved from api.rs to settings.rs so
engine.rs can depend on it without a circular import) to read the current
alert rules on each ingest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 16:30:14 +01:00
Hugh KaznowskiandClaude Sonnet 5 7694dcf120 Embed webapp into the binary with fs-embed, drop --static-dir
ottyl was requiring webapp/dist to be shipped alongside the binary and
served via ServeDir/ServeFile pointed at a --static-dir CLI arg. Embed
the built webapp at compile time instead, so ottyl is a single
self-contained executable with no runtime static directory dependency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 16:21:02 +01:00
Hugh KaznowskiandClaude Sonnet 5 1d0f324c43 Add settings-configurable cap on logs kept in memory (ticket 026)
Settings.max_logs (default None = unlimited) is pushed to the engine
via a new EngineMessage::SetMaxLogs, enforced on every ingest and
whenever the setting changes at runtime (no restart required).
Eviction drops the oldest (lowest-sequence) logs; sequence numbering
isn't renumbered and buckets already derived from an evicted log are
left in place. Exposed via PATCH /api/settings (max_logs: number |
null) and a NavMenu numeric field (blank = unlimited).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 15:12:12 +01:00
Hugh KaznowskiandClaude Sonnet 5 aaa5596030 Add --file ingestion with restart-safe tailing (ticket 020)
ingest::run_file tails a path like tail -f: reads existing content,
follows appended lines, and detects file recreation/truncation (via
inode on unix, or a shrinking size) to resume tailing the new
instance from its start instead of exiting or stalling. Sequence
numbers keep incrementing across a recreation event since they're
ottyl's own line counter, not tied to the source file. stdin ingest
is unchanged and remains the default when --file is omitted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 15:08:20 +01:00
Hugh KaznowskiandClaude Sonnet 5 b3701cd2e9 Default --port to 0 for OS-assigned ephemeral port (ticket 019)
Reads back the actual bound port via listener.local_addr() since
binding to 0 means the requested and real ports differ.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 15:06:46 +01:00
Hugh Kaznowski 51e577f01e Docs, ticks, working app 2026-09-05 15:05:39 +01:00
Hugh KaznowskiandClaude Sonnet 5 ac67f9e120 Add PromQL query support for dashboard visualizations (ticket 013)
Hand-rolls a minimal PromQL parser (src/promql.rs) for metric-name +
label-matcher selection, optional [range] selectors, and top-level
sum()/avg()/rate() wrapping, rather than pulling in a full PromQL parser
crate — the required subset is small and a hand-rolled parser stays fully
engine-independent (mirrors filter_parser.rs) with no unused-AST overhead.

Adds EngineQuery::Promql/EngineResponse::Promql (engine parses + evaluates
in place against the index, no copying) and GET /api/query, returning
Vec<PromqlSeriesDto> (labels + (timestamp_ms, f64) points) for charting,
with malformed queries surfaced as 400 + ErrorDto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 13:10:41 +01:00
Hugh KaznowskiandClaude Sonnet 5 4c8918ec8c Add profile (preset) CRUD and startup replay (ticket 009)
Introduces `src/profile.rs`: a `Profile` bundles param/metric declarations
plus a placeholder dashboards field, and `ProfileStore` persists all
profiles as a single profiles.json file, resolved relative to the settings
file's directory (same convention as ticket 010's CSS storage). Adds CRUD
(create/list/get/delete/set_active) that keeps Settings.profiles.names and
active_profile in sync, plus record_param_declaration/record_metric_declaration
hooks that ticket 004's HTTP API will call to persist newly-declared
params/metrics into the active profile.

Wires main.rs to replay the active profile's declarations into the engine
before spawning the ingest task, so backfill/live-derivation semantics
apply uniformly to every ingested line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:57:20 +01:00
Hugh KaznowskiandClaude Sonnet 5 71aaa4ebb4 Add CSS import/link/list/delete/content API (ticket 010)
Wires up Settings.css_entries behind a small settings API: import raw CSS
content to a file stored alongside settings.json, link external CSS
paths/URLs by reference (never deletable/copied), list all entries, delete
imported entries, and serve back imported content. CSS storage logic lives
on Settings (import_css/link_css/remove_css/css_content) with api.rs
handlers staying thin, matching the existing engine-query pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:51:22 +01:00
Hugh Kaznowski 1d3bc0d0d6 Merge branch 'worktree-agent-a63474e92b05425a5'
# Conflicts:
#	Cargo.lock
#	Cargo.toml
2026-09-05 12:46:45 +01:00
Hugh Kaznowski 5b1e2fd5af Merge branch 'worktree-agent-a25bd4a44da9f00b3'
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/main.rs
2026-09-05 12:46:31 +01:00
Hugh KaznowskiandClaude Sonnet 5 202b1dcf9f Add FilterBar query language parser (pest grammar)
Introduces a standalone src/filter_parser.rs module (no dependency on
engine/index types) that parses the FilterBar query language into a
FilterExpr AST using a pest grammar (src/filter_parser.pest). Supports
regex("...") searches, metric_name[param] <op> value comparisons,
reserved sequence/timestamp metrics, and and/or combinators with
correct precedence (and binds tighter than or) and parenthesized
sub-expressions. Evaluation against real log data is left to a later
integration ticket.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:45:31 +01:00
Hugh KaznowskiandClaude Sonnet 5 22647620fa Add process-wide Settings model (ticket 008)
Introduces src/settings.rs with a single, process-wide Settings struct
(colour scheme, CSS entries shaped for imported-vs-linked, and a
profiles placeholder for ticket 009), JSON-serialized via serde_json to
keep deps minimal. Default settings path is computed with the
directories crate (ProjectDirs::from("", "", "ottyl")) and overridable
via a new --settings-path CLI arg. Settings::load creates and persists
a default file on first run; Settings::save persists changes.

Wires a single Arc<Mutex<Settings>> into main() and threads it through
api::router as an additive ApiState field for tickets 010/011 to build
on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:42:53 +01:00
Hugh KaznowskiandClaude Sonnet 5 0311eeb712 Add filter_datetime module for FilterBar timestamp parsing
Standalone module (no engine/index/filter-grammar deps) that normalizes
relative humantime (5h, -3m), absolute time-of-day (12:00, resolved to
today in UTC), and full date/RFC3339 literals (2024-01-15,
2024-01-15T12:00:00Z) into a single Timestamp, given a passed-in `now`
for testability. Adds humantime and chrono dependencies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:42:51 +01:00
Hugh Kaznowski 68548911bc Webapp 2026-09-05 12:23:46 +01:00
Hugh Kaznowski cbdb145f68 Initial readme/spec 2026-09-05 11:58:01 +01:00