From dfd3dc8f1a9de3ddbfaaa8c9a73131b247f26074 Mon Sep 17 00:00:00 2001 From: Samuel Cardoso Date: Sun, 21 Sep 2025 14:29:10 +0200 Subject: [PATCH] feat(client): add log recording Rust types and API definitions --- client/core/rs/src/api/read/log_recording.rs | 94 ++++++++++++++ client/core/rs/src/api/write/log_recording.rs | 74 +++++++++++ client/core/rs/src/entities/log_recording.rs | 116 ++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 client/core/rs/src/api/read/log_recording.rs create mode 100644 client/core/rs/src/api/write/log_recording.rs create mode 100644 client/core/rs/src/entities/log_recording.rs diff --git a/client/core/rs/src/api/read/log_recording.rs b/client/core/rs/src/api/read/log_recording.rs new file mode 100644 index 000000000..bd4d56144 --- /dev/null +++ b/client/core/rs/src/api/read/log_recording.rs @@ -0,0 +1,94 @@ +use crate::entities::{ + log_recording::{LogRecording, LogRecordingListItem}, + update::Log, + ResourceTarget, U64, +}; +use derive_empty_traits::EmptyTraits; +use resolver_api::Resolve; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use super::KomodoReadRequest; + +//===================== +// GetLogRecording +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoReadRequest)] +#[response(GetLogRecordingResponse)] +#[error(serror::Error)] +pub struct GetLogRecording { + pub id: String, +} + +#[typeshare] +pub type GetLogRecordingResponse = LogRecording; + + +//===================== +// ListLogRecordings +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoReadRequest)] +#[response(ListLogRecordingsResponse)] +#[error(serror::Error)] +pub struct ListLogRecordings { + pub target: Option, + pub active_only: bool, +} + +#[typeshare] +pub type ListLogRecordingsResponse = Vec; + + +//===================== +// GetRecordedLogs +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoReadRequest)] +#[response(GetRecordedLogsResponse)] +#[error(serror::Error)] +pub struct GetRecordedLogs { + pub recording_id: String, + pub tail: U64, + pub timestamps: bool, +} + +#[typeshare] +pub type GetRecordedLogsResponse = Log; + + +//===================== +// SearchRecordedLogs +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoReadRequest)] +#[response(SearchRecordedLogsResponse)] +#[error(serror::Error)] +pub struct SearchRecordedLogs { + pub recording_id: String, + pub terms: Vec, + pub combinator: crate::entities::SearchCombinator, + pub invert: bool, + pub timestamps: bool, +} + +#[typeshare] +pub type SearchRecordedLogsResponse = Log; + diff --git a/client/core/rs/src/api/write/log_recording.rs b/client/core/rs/src/api/write/log_recording.rs new file mode 100644 index 000000000..bae720fe8 --- /dev/null +++ b/client/core/rs/src/api/write/log_recording.rs @@ -0,0 +1,74 @@ +use crate::entities::{ + log_recording::LogRecording, + NoData, ResourceTarget, +}; +use derive_empty_traits::EmptyTraits; +use resolver_api::Resolve; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use super::KomodoWriteRequest; + +//===================== +// StartLogRecording +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoWriteRequest)] +#[response(StartLogRecordingResponse)] +#[error(serror::Error)] +pub struct StartLogRecording { + pub target: ResourceTarget, + /// Optional custom name for the recording + pub name: Option, + /// Duration in days. None means until manually stopped. + pub duration_days: Option, + /// For stacks: which services to record (empty = all) + #[serde(default)] + pub services: Vec, +} + +#[typeshare] +pub type StartLogRecordingResponse = LogRecording; + + +//===================== +// StopLogRecording +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoWriteRequest)] +#[response(StopLogRecordingResponse)] +#[error(serror::Error)] +pub struct StopLogRecording { + pub id: String, +} + +#[typeshare] +pub type StopLogRecordingResponse = LogRecording; + + +//===================== +// DeleteLogRecording +//===================== + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoWriteRequest)] +#[response(DeleteLogRecordingResponse)] +#[error(serror::Error)] +pub struct DeleteLogRecording { + pub id: String, +} + +#[typeshare] +pub type DeleteLogRecordingResponse = NoData; + diff --git a/client/core/rs/src/entities/log_recording.rs b/client/core/rs/src/entities/log_recording.rs new file mode 100644 index 000000000..715aadfc2 --- /dev/null +++ b/client/core/rs/src/entities/log_recording.rs @@ -0,0 +1,116 @@ +use serde::{Deserialize, Serialize}; +use strum::{Display, EnumString}; +use typeshare::typeshare; + +use super::{I64, MongoId, ResourceTarget}; + +/// Represents a log recording session for a resource +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[cfg_attr( + feature = "mongo", + derive(mongo_indexed::derive::MongoIndexed) +)] +#[cfg_attr(feature = "mongo", doc_index({ "target.type": 1 }))] +#[cfg_attr(feature = "mongo", doc_index({ "target.id": 1 }))] +#[cfg_attr(feature = "mongo", index({ "user_id": 1 }))] +#[cfg_attr(feature = "mongo", index({ "status": 1 }))] +pub struct LogRecording { + /// The Mongo ID of the recording + #[serde( + default, + rename = "_id", + skip_serializing_if = "String::is_empty", + with = "bson::serde_helpers::hex_string_as_object_id" + )] + pub id: MongoId, + + /// The target resource being recorded + pub target: ResourceTarget, + + /// Optional custom name for the recording + pub name: Option, + + /// The user who started the recording + pub user_id: String, + + /// When the recording started + #[cfg_attr(feature = "mongo", index)] + pub start_ts: I64, + + /// When the recording should expire (None = manual stop only) + #[cfg_attr(feature = "mongo", index)] + pub expire_ts: Option, + + /// Current status of the recording + pub status: LogRecordingStatus, + + /// For stacks: which services to record (empty = all) + #[serde(default)] + pub services: Vec, +} + +/// Status of a log recording +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Default, Display, EnumString, PartialEq, Eq, +)] +pub enum LogRecordingStatus { + #[default] + Recording, + Stopped, + Expired, +} + +/// Minimal representation for listing +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct LogRecordingListItem { + pub id: String, + pub target: ResourceTarget, + pub name: Option, + pub user_id: String, + pub username: String, + pub start_ts: I64, + pub expire_ts: Option, + pub status: LogRecordingStatus, + pub services: Vec, +} + +/// A single log entry from a recording +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[cfg_attr( + feature = "mongo", + derive(mongo_indexed::derive::MongoIndexed) +)] +#[cfg_attr(feature = "mongo", index({ "recording_id": 1 }))] +#[cfg_attr(feature = "mongo", index({ "ts": 1 }))] +pub struct RecordedLog { + /// The Mongo ID of the log entry + #[serde( + default, + rename = "_id", + skip_serializing_if = "String::is_empty", + with = "bson::serde_helpers::hex_string_as_object_id" + )] + pub id: MongoId, + + /// The recording this log belongs to + pub recording_id: String, + + /// Timestamp of the log entry + pub ts: I64, + + /// Standard output + #[serde(default)] + pub stdout: String, + + /// Standard error + #[serde(default)] + pub stderr: String, + + /// For stack services + #[serde(skip_serializing_if = "Option::is_none")] + pub service: Option, +} \ No newline at end of file