A stored log, timeline_entries, with one row per thing that happened. Each source writes its row in the same transaction as its own record, so the history outlives the 30/90/365-day retention of the tables it points to: - alert events (core/alerts._add_event): fired, resolved, acknowledged, went silent, readings back; - finished Ansible runs (executor, and the startup sweep of interrupted runs): changed something, changed nothing, failed, dry run; - audit rows (core/audit.log_audit): Steward's settings, users and access, sign-ins, backups. Actions another writer records are left to it. Entries carry a kind (fixed) and an area (Service, Host OS, Hardware, Storage, Network, Security, Monitoring). Settings → Timeline edits the areas and each type's area and visibility; both are applied when the timeline is read, so a change re-sorts past entries and a hidden type comes back when shown. Kept forever unless a retention is set. The first start fills the timeline from the last 90 days of records once, skipping any already covered. A host rename carries its entries. UI: Timeline in the sidebar (filter by host, area, kind; scrolls on), a History card on each host page. MCP: get_timeline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
3.2 KiB
Markdown
49 lines
3.2 KiB
Markdown
# Timeline
|
|
|
|
The timeline is one list of everything that happened to the infrastructure, newest first: alerts firing and clearing, playbook runs and what they changed, and changes to Steward's own settings. Open it from **Timeline** in the sidebar, from a host's **History** card, or through the `get_timeline` MCP tool.
|
|
|
|
Each entry stands on its own. An alert event, an Ansible run or an audit row is pruned after its own retention, but the entry for it stays. Its link then reads "Details no longer kept".
|
|
|
|
**Source:** `steward/timeline/`
|
|
**Table:** `timeline_entries` (`steward/models/timeline.py`)
|
|
|
|
---
|
|
|
|
## What is recorded
|
|
|
|
Every source writes its entry in the same transaction as its own record, through `steward/timeline/record.py`:
|
|
|
|
| Source | Written by | Entry types |
|
|
|---|---|---|
|
|
| Alert events | `core/alerts._add_event` | `alert.fired`, `alert.resolved`, `alert.acknowledged`, `alert.silent`, `alert.resumed` |
|
|
| Ansible runs, when they finish | `ansible/executor.start_run`, and the startup sweep of interrupted runs | `ansible.changed`, `ansible.unchanged`, `ansible.failed`, `ansible.check` |
|
|
| Audit rows | `core/audit.log_audit` | `steward.config`, `steward.access`, `steward.signin`, `steward.backup` |
|
|
|
|
Some audit actions are left to another writer, because the event itself is recorded there: starting a run, acknowledging an alert, and starting maintenance. A person's own preferences (time zone, email settings) are not recorded. `catalog.audit_type` holds that mapping.
|
|
|
|
On its first start, Steward fills the timeline from the last 90 days of alert events, finished runs and audit rows (`service.backfill`). It does this once, and records when in `timeline.backfilled_at`. An entry for a record that is already covered is skipped.
|
|
|
|
## Areas and kinds
|
|
|
|
An entry has a **kind**, which says how it came about: Change, Automation, Alert, Anomaly, Ticket or Steward. Kinds are fixed in `catalog.KINDS`.
|
|
|
|
An entry also has an **area**, which says what part of the infrastructure it is about. The areas start as Service, Host OS, Hardware, Storage, Network, Security and Monitoring.
|
|
|
|
Each entry is written with an area of its own. An alert's area comes from what its rule watches (`catalog.area_of_metric`): drive health is Hardware, a mount is Storage, an HTTP monitor is Service. Other entries use their type's area.
|
|
|
|
## Settings → Timeline
|
|
|
|
| Setting | Key | Default |
|
|
|---|---|---|
|
|
| Areas, in order: rename, add, or remove one and move its entries to another | `timeline.areas`, `timeline.area_moved` | the seven above |
|
|
| Each type's area ("As recorded" keeps each entry's own) and whether it is shown | `timeline.types` | from the catalog |
|
|
| Keep for, in days (0 keeps everything) | `timeline.retention_days` | 0 |
|
|
|
|
These are applied when the timeline is read, never written onto an entry. A change re-sorts the whole history. A hidden type is still recorded, so showing it again brings its entries back.
|
|
|
|
Hidden by default: dry runs, playbook runs that changed nothing, and sign-ins.
|
|
|
|
## Adding a type
|
|
|
|
A plugin registers its types in `setup()` with `catalog.register_type(key, label, kind, area, shown, about)`, and writes entries with `record.record(session, type=…, at=…, summary=…, hosts=…, ref=(kind, id))`. Settings → Timeline lists the new type at once.
|