457 lines
16 KiB
Protocol Buffer
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;
|
|
}
|