Add selectable declared-param columns to LogPreview (ticket 015)

Extend GET /api/logs to derive each declared param's value per log line
(regex + capture group + kind, matching that line's text) and include it as
a params map on LogEntryDto, omitting entries that don't match rather than
sending nulls. Add GET /api/params (list_params, mirroring list_metrics) so
the frontend can discover currently-declared param names. In LogPreview, add
a column-picker popover backed by that list, with the selection persisted in
localStorage under `ottyl.logPreview.visibleParamColumns`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 13:09:25 +01:00
co-authored by Claude Sonnet 5
parent e48c03218f
commit 7514052048
5 changed files with 301 additions and 30 deletions
+30
View File
@@ -1,7 +1,33 @@
/// `MetricPrimitive` is a Rust enum with derived `Serialize`, so its JSON
/// form is externally tagged, e.g. `{ "String": "info" }`, `{ "Number":
/// { "Int": 5 } }`, `{ "Timestamp": 1710000000000 }`.
export type MetricPrimitive =
| { String: string }
| { Number: { Int: number } | { Float: number } }
| { Timestamp: number }
export function metricPrimitiveToDisplayString(value: MetricPrimitive): string {
if ('String' in value) return value.String
if ('Timestamp' in value) return String(value.Timestamp)
if ('Int' in value.Number) return String(value.Number.Int)
return String(value.Number.Float)
}
export interface LogEntry {
sequence: number
timestamp_ms: number
text: string
/// Derived param values (declared via `POST /api/params`) that matched
/// this line's text, keyed by param name. A param with no match on this
/// line is simply absent from the map.
params: Record<string, MetricPrimitive>
}
export interface ParamDeclaration {
name: string
message_match_regex: string
capture_group: string
kind: 'string' | 'number' | 'timestamp'
}
export interface TimelineBucket {
@@ -54,3 +80,7 @@ export function fetchTimeline(params: {
export function fetchStats(): Promise<Stats> {
return getJson('/api/stats')
}
export function fetchParamDeclarations(): Promise<ParamDeclaration[]> {
return getJson('/api/params')
}