Files
ottyl/src/dto.rs
T
Hugh KaznowskiandClaude Sonnet 5 7514052048 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
2026-09-05 13:09:25 +01:00

154 lines
4.9 KiB
Rust

use crate::data_value::{MetricPrimitiveKind, RegexToMetricPrimitiveMatcher};
use crate::metric_primitive::MetricPrimitive;
use crate::settings::CssEntry;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize)]
pub struct LogEntryDto {
pub sequence: u64,
pub timestamp_ms: i64,
pub text: String,
/// Derived param values (ticket 001's `ParamDeclaration`s) for this line,
/// keyed by param name. Only params whose regex actually matched this
/// line's text (and whose captured group parsed as the declared `kind`)
/// are present — a non-matching param is omitted rather than sent as
/// null, so the frontend can render a blank/dash for anything missing
/// from the map.
pub params: BTreeMap<String, MetricPrimitive>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ParamDeclarationDto {
pub name: String,
pub message_match_regex: String,
pub capture_group: String,
pub kind: MetricPrimitiveKind,
}
#[derive(Debug, Clone, Serialize)]
pub struct MetricDeclarationDto {
pub name: String,
pub message_match_regex: String,
pub params: Vec<RegexToMetricPrimitiveMatcher>,
}
#[derive(Debug, Clone, Serialize)]
pub struct TimelineBucketDto {
pub start_ms: i64,
pub count: u64,
}
#[derive(Debug, Clone, Serialize)]
pub struct StatsDto {
pub log_count: u64,
pub first_sequence: Option<u64>,
pub last_sequence: Option<u64>,
pub first_timestamp_ms: Option<i64>,
pub last_timestamp_ms: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct LogsParams {
pub since_sequence: Option<u64>,
pub start_ms: Option<i64>,
pub end_ms: Option<i64>,
pub limit: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub struct TimelineParams {
pub start_ms: Option<i64>,
pub end_ms: Option<i64>,
pub bucket_ms: Option<i64>,
}
/// One entry from a metric's derived bucket timeseries: the label combination
/// that bucket represents, plus one `(sequence, timestamp, value)` occurrence
/// within it.
#[derive(Debug, Clone, Serialize)]
pub struct BucketValueDto {
pub labels: BTreeMap<String, MetricPrimitive>,
pub sequence: u64,
pub timestamp_ms: i64,
pub value: MetricPrimitive,
}
/// Query params for `GET /api/metrics/:name`. `labels` is a single
/// comma-separated `key=value` list (e.g. `?labels=path=/a,duration=5`)
/// rather than repeated `label.foo=bar` params, since axum's `Query`
/// extractor needs a fixed/known set of field names — an arbitrary label set
/// doesn't fit that without a custom deserializer. Every listed label must
/// match (compared against each label value's `Display` form, see
/// `MetricPrimitive`); unlisted labels on a bucket are ignored.
#[derive(Debug, Deserialize)]
pub struct BucketValuesParams {
pub labels: Option<String>,
pub since_sequence: Option<u64>,
pub start_ms: Option<i64>,
pub end_ms: Option<i64>,
pub limit: Option<usize>,
}
/// JSON error body returned by API handlers on a 4xx/5xx response, e.g.
/// `{ "error": "invalid regex: ..." }`.
#[derive(Debug, Serialize)]
pub struct ErrorDto {
pub error: String,
}
/// Request body for `POST /api/settings/css`: raw CSS content to import and
/// store alongside the settings file.
#[derive(Debug, Deserialize)]
pub struct ImportCssRequest {
pub filename: String,
pub content: String,
}
/// Request body for `POST /api/settings/css/link`: a reference to an
/// external CSS path/URL that ottyl doesn't copy or own.
#[derive(Debug, Deserialize)]
pub struct LinkCssRequest {
pub path_or_url: String,
}
/// Response shape for a single CSS entry, used both by the list endpoint and
/// by mutation endpoints that return the affected entry. `id` is the entry's
/// real id for `Imported` entries, or a stable id derived from the
/// linked path/URL for `Linked` entries (see `settings::css_entry_id`) —
/// this lets the frontend key/reference both kinds of entries uniformly,
/// even though only `Imported` entries are deletable/content-fetchable.
#[derive(Debug, Clone, Serialize)]
pub struct CssEntryDto {
pub id: String,
pub kind: CssEntryKind,
pub filename: Option<String>,
pub path_or_url: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CssEntryKind {
Imported,
Linked,
}
impl From<&CssEntry> for CssEntryDto {
fn from(entry: &CssEntry) -> Self {
match entry {
CssEntry::Imported { id, filename } => CssEntryDto {
id: id.clone(),
kind: CssEntryKind::Imported,
filename: Some(filename.clone()),
path_or_url: None,
},
CssEntry::Linked { path_or_url } => CssEntryDto {
id: crate::settings::css_entry_id(entry),
kind: CssEntryKind::Linked,
filename: None,
path_or_url: Some(path_or_url.clone()),
},
}
}
}