Files
dozzle/protos/cloud.proto
T

457 lines
16 KiB
Protocol Buffer

syntax = "proto3";
package cloud;
option go_package = "github.com/amir20/dozzle/proto/cloud";
service CloudToolService {
// Dozzle sends ToolResponse, cloud sends ToolRequest
rpc ToolStream(stream ToolResponse) returns (stream ToolRequest);
// Dozzle-initiated unary call: search log lines this instance has streamed
// to Cloud (gated by streamLogs opt-in). Cloud scopes the query server-side
// to the (user_id, api_key_id) derived from the authenticated connection;
// Dozzle does NOT pass any identity fields in the request.
rpc SearchLogs(SearchLogsRequest) returns (SearchLogsResponse);
// Dozzle-initiated unary call: fetch alerts that fired for these containers
// inside a time window, so the log viewer can merge them into the stream as
// the user scrolls back. Like SearchLogs, cloud scopes the query server-side
// to the (user_id, api_key_id) derived from the authenticated connection;
// Dozzle passes no identity fields. Unlike SearchLogs this does NOT require
// the streamLogs opt-in — alerts live in cloud's own database rather than
// the log store, so they exist for anyone who linked cloud and configured a
// subscription.
rpc GetAlerts(GetAlertsRequest) returns (GetAlertsResponse);
}
message ToolRequest {
string request_id = 1;
oneof type {
ListToolsRequest list_tools = 2;
CallToolRequest call_tool = 3;
CancelStreamRequest cancel_stream = 4;
}
}
message ToolResponse {
string request_id = 1;
oneof type {
ListToolsResponse list_tools = 2;
CallToolResponse call_tool = 3;
// Unsolicited server-push: batched container log lines streamed from
// Dozzle to Cloud for ingestion into VictoriaLogs. request_id is empty
// for log batches — they are not replies to a ToolRequest.
LogBatch log_batch = 4;
// Unsolicited server-push: 30s-windowed container resource metrics.
// request_id is empty for stats batches — like log batches, they are not
// replies to a ToolRequest.
StatsBatch stats_batch = 5;
}
}
// Batch of log lines from one or more containers. Dozzle pushes these
// continuously while connected. Cloud routes them to VictoriaLogs scoped
// to the owning user (derived from the connection's auth).
message LogBatch {
repeated LogBatchEntry entries = 1;
}
message LogBatchEntry {
string host_id = 1;
string container_id = 2;
string container_name = 3;
int64 timestamp_ns = 4; // unix nanoseconds
string message = 5;
string stream = 6; // "stdout" or "stderr"
string level = 7; // "info", "warn", "error", etc. (best-effort)
// Deterministic FNV-32a hash of the raw log line, the same id Dozzle
// stamps on LogEvent.Id. Cloud indexes this as a non-stream field so
// search results can produce a stable permanent link
// (/container/:id/time/:datetime?logId=...) that lands the user on
// exactly the matching line in the local log viewer.
uint32 log_id = 8;
}
// Batch of windowed container resource metrics. Dozzle aggregates raw ~1Hz
// stats into fixed windows and pushes one batch per window while connected.
// Cloud writes them to VictoriaMetrics scoped to the owning user (derived
// from the connection's auth).
message StatsBatch {
repeated StatsBatchEntry entries = 1;
}
// One container's metrics for one aggregation window.
//
// Series identity on the Cloud side is (host_id, container_name) — there is
// deliberately no container_id. Container ids churn on every redeploy and each
// new value would strand the old time series forever; keying on the name means
// a redeploy continues the same series, which is what a resource chart wants.
message StatsBatchEntry {
string host_id = 1;
string container_name = 2;
int64 timestamp_ns = 3; // window END, unix nanoseconds
// Gauges — arithmetic mean over the window.
double cpu_percent = 4; // normalised 0-100 across cores, NOT per-core
double memory_percent = 5;
double memory_usage_bytes = 6;
// Peak sample in the window. A 30s mean flattens spikes, and spikes are
// exactly what an incident chart is read for.
double cpu_percent_max = 7;
double memory_percent_max = 8;
// Monotonic counters — the LAST value observed in the window, not a delta.
// Cloud stores these as Prometheus counters so PromQL rate()/increase()
// handle the reset that happens when a container is recreated.
uint64 network_rx_total = 9;
uint64 network_tx_total = 10;
uint64 disk_read_total = 11;
uint64 disk_write_total = 12;
// Number of raw samples folded into this entry. Windows with zero samples
// are never emitted.
uint32 samples = 13;
// The core count cpu_percent was divided by, so the raw per-core figure
// Docker reports stays recoverable downstream.
double cpu_cores = 14;
}
message ListToolsRequest {}
message ListToolsResponse {
repeated ToolDefinition tools = 1;
string version = 2;
}
message ToolDefinition {
string name = 1;
string description = 2;
string parameters_json = 3;
// scope tells the cloud router how to multiplex calls to this tool. Dozzle
// itself has no notion of "multiple instances"; this field lets the router
// decide whether to fan out (default), target a specific container/host
// inferred from the existing host_id/container_id params, or target the
// whole Dozzle instance (in which case the router injects an instance_id
// argument into the LLM-facing schema and strips it before forwarding).
ToolScope scope = 4;
// read_only = true means the tool has no side effects and is safe to fan
// out across every connected Dozzle instance when the scope's routing
// argument is missing (e.g. list_notifications without instance_id queries
// all instances and merges results). Mutating tools (create/delete/deploy/
// restart/etc.) must leave this false — the router will require the scope
// argument and error if it's missing, so a write never fans out by mistake.
bool read_only = 5;
}
enum ToolScope {
// Default — treated as INSTANCE. Exists so forward-compat clients without
// a scope field don't misroute.
TOOL_SCOPE_UNSPECIFIED = 0;
// Applies to the whole Dozzle instance (list_hosts, notifications, etc.).
// Requires instance_id in the args.
TOOL_SCOPE_INSTANCE = 1;
// Targets a specific Docker host (deploy_compose, list_deploy_versions).
// Requires host_id in the args.
TOOL_SCOPE_HOST = 2;
// Targets a specific container (logs, actions, inspect). Requires
// container_id in the args (host_id is typically also present for the
// Docker API call inside Dozzle but isn't used for routing).
TOOL_SCOPE_CONTAINER = 3;
}
message CallToolRequest {
string name = 1;
string arguments_json = 2;
}
message CallToolResponse {
bool success = 1;
string error = 2;
bool stream = 9;
bool end_stream = 10;
oneof result {
ListHostsResult list_hosts = 3;
ListContainersResult list_containers = 4;
ContainerStatsResult container_stats = 5;
ActionResult action = 6;
FetchLogsResult fetch_logs = 7;
InspectContainerResult inspect_container = 8;
DeployResult deploy = 11;
NotificationResult notification = 12;
}
}
message CancelStreamRequest {
string stream_request_id = 1;
}
// Host information
message HostInfo {
string id = 1;
string name = 2;
int32 n_cpu = 3;
int64 mem_total = 4;
string docker_version = 5;
string agent_version = 6;
string type = 7;
bool available = 8;
}
message ListHostsResult {
repeated HostInfo hosts = 1;
}
// Container information
message ContainerInfo {
string id = 1;
string name = 2;
string image = 3;
string command = 4;
string created = 5;
string started_at = 6;
string finished_at = 7;
string state = 8;
string health = 9;
string host_name = 10;
string host_id = 12;
string group = 11;
}
message ListContainersResult {
repeated ContainerInfo containers = 1;
}
// Container stats
message ContainerStatEntry {
string id = 1;
string name = 2;
string host = 3;
double cpu_percent = 4;
double memory_percent = 5;
double memory_usage = 6;
double max_cpu_5min = 7;
double max_memory_5min = 8;
string host_id = 9;
uint64 network_rx_total = 10;
uint64 network_tx_total = 11;
uint64 network_rx_5min = 12;
uint64 network_tx_5min = 13;
}
message ContainerStatsResult {
repeated ContainerStatEntry stats = 1;
}
// Container logs
message LogEntry {
int64 timestamp = 1;
string message = 2;
string stream = 3; // "stdout" or "stderr"
string level = 4; // "info", "warn", "error", etc.
}
message FetchLogsResult {
string container_name = 1;
repeated LogEntry entries = 2;
}
// Container inspect result
message InspectContainerResult {
string id = 1;
string name = 2;
string image = 3;
string command = 4;
string created = 5;
string started_at = 6;
string finished_at = 7;
string state = 8;
string health = 9;
string host_name = 10;
string host_id = 19;
map<string, string> labels = 11;
uint64 memory_limit = 12;
double cpu_limit = 13;
reserved 14;
reserved "env";
repeated string ports = 15;
repeated string mounts = 16;
string restart_policy = 17;
string network_mode = 18;
}
// Container action result
message ActionResult {
bool success = 1;
string container_id = 2;
string action = 3;
string message = 4;
}
// Deploy compose result
message DeployResult {
bool success = 1;
string project = 2;
string message = 3;
}
// Notification/alert tool result (list, create).
message NotificationResult {
bool success = 1;
string message = 2;
}
// Cloud log search.
message SearchLogsRequest {
// Substring/word-filter query. Empty -> empty result. Whitespace-only
// is rejected client-side; server treats as empty.
string query = 1;
// Result cap. Default 20, server-capped at 50.
int32 limit = 2;
// Pagination cursor: return only hits with timestamp_ns < this value.
// 0 = newest. Reserved for future use.
int64 before_ts_ns = 3;
// Optional filter — narrow to a specific Docker host inside the instance.
// Empty = all hosts under this instance.
string host_id = 4;
// Optional filter — narrow to a specific container.
string container_id = 5;
}
message SearchLogsResponse {
repeated SearchLogHit hits = 1;
bool has_more = 2;
// For pagination: pass back as before_ts_ns to fetch the next page.
int64 next_before_ts_ns = 3;
}
message SearchLogHit {
int64 timestamp_ns = 1;
string host_id = 2;
string container_id = 3;
string container_name = 4;
// Full log line as indexed.
string message = 5;
string stream = 6;
string level = 7;
// FNV-32a hash of the raw log line — same id Dozzle assigns LogEvent.Id
// and exposes via "Copy permalink". Lets the search-result row deep-link
// straight to the matching line.
uint32 log_id = 8;
}
// Cloud alert history, merged into the local log stream on scrollback.
message GetAlertsRequest {
// Containers currently in the viewer. Batched into one call because the
// viewer fans out over N containers per scroll and must not pay N round
// trips to cloud. Empty -> empty result.
repeated string container_ids = 1;
// Optional filter — narrow to a specific Docker host inside the instance.
// Empty = all hosts under this instance.
string host_id = 2;
// Window to fetch, matching the [from, to] the viewer just loaded logs for.
int64 from_ts_ns = 3;
int64 to_ts_ns = 4;
// Result cap. Default 50, server-capped at 200. A single scroll window
// covering a noisy incident can legitimately hold many alerts, so this
// ceiling is higher than the log-search one.
int32 limit = 5;
// Include follow-up anchors — points where an already-open incident was
// still firing, as opposed to where it first fired. See AlertHit.is_origin.
// Default false keeps a long-running incident to a single card.
bool include_follow_ups = 6;
// Also return the individual matched events in the window, not just the
// alerts they were aggregated into. An alert means a notification was
// delivered; an event on its own may have been held back.
bool include_events = 7;
}
message AlertHit {
// Opaque, sqids-encoded — never the raw BIGSERIAL, which would leak total
// alert counts to anything reading a response or a deep link. Decoding it is
// Cloud's business; to Dozzle it is a string to compare and nothing more.
string alert_id = 1;
string container_id = 2;
string host_id = 3;
// FNV-32a hash of the log line that triggered this alert — the same id
// Dozzle stamps on LogEvent.Id. Lets the viewer splice the alert in
// directly after its trigger line instead of guessing from a timestamp.
// 0 when the alert is not log-anchored (metric and event alerts).
uint32 log_id = 4;
// Timestamp to anchor on when log_id is 0, or when the trigger line isn't
// in the loaded window.
int64 anchor_ts_ns = 5;
// Triage's one-line summary. Falls back to the alert title when no
// summarizer LLM was involved.
string headline = 6;
string level = 7;
// Raw events folded into this alert, and follow-up batches triage absorbed
// silently after it was first sent.
int32 event_count = 8;
int32 suppressed_count = 9;
// Distinct containers this alert spans. > 1 means the incident is wider
// than the container currently being viewed.
int32 container_count = 10;
// Triage's longer write-up. May be empty. Rendered collapsed.
string investigation = 11;
// 'send_new' | 'fallback' | ''. Never 'suppress' or 'update_existing':
// cloud folds those into the incident's original row rather than inserting
// one, so every hit is an incident, not a triage decision.
string triage_action = 12;
// The alert's description — the same prose the Cloud alert page shows under
// the headline. Present even when triage wrote no investigation, so it is
// usually the only detail a non-triaged alert can offer.
string summary = 17;
// True when this hit anchors where the incident FIRST fired. False for a
// follow-up anchor: cloud appends every folded batch's events to the
// original alert, so one incident can have activity in many scroll windows.
// The viewer renders a full card at the origin and a compact "still
// happening" marker at follow-ups.
bool is_origin = 16;
int64 created_at_ns = 13;
// Bumped every time a follow-up batch folds into this alert. Drives the
// "still happening" state on the card.
int64 last_activity_at_ns = 14;
// Deep link to the alert page in Dozzle Cloud, built from the API key's
// app_url. Empty when the key has no app_url configured.
string url = 15;
}
// One matched event — a notification Dozzle raised because it matched a
// subscription. Many events aggregate into one alert, so these are far more
// numerous than hits and the viewer collapses runs of them.
message EventHit {
int64 ts_ns = 1;
// FNV-32a hash of the line that matched, 0 for metric and event types.
uint32 log_id = 2;
string container_id = 3;
string host_id = 4;
string level = 5;
string message = 6;
// 'log' | 'metric' | 'event' — the notification's own type.
string type = 7;
// The incident that absorbed this event; 0 when it never reached an alert.
// Opaque, sqids-encoded — see AlertHit.alert_id. Empty when the event never
// reached an alert.
string alert_id = 8;
// True when this event produced no delivered notification of its own: it
// arrived after its alert had already been sent and was folded in silently,
// or it never became an alert at all.
bool suppressed = 9;
// Human-readable summary Dozzle wrote when it raised the notification: the
// log line for a log event, "CPU: 45.2%, Memory: 61.0%" for a metric, the
// lifecycle description for a container event. The only renderable text a
// metric or event notification has, since neither carries a log line.
string detail = 10;
}
message GetAlertsResponse {
repeated AlertHit hits = 1;
// Populated only when include_events is set.
repeated EventHit events = 3;
// The window held more alerts than the limit allowed. The viewer shows a
// "more alerts in this range" affordance rather than paginating — unlike
// log search, scrollback windows are already small and bounded.
bool truncated = 2;
}