viewer

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: GPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package viewer holds the magus.viewer.v1alpha1 wire contract: the code that maps captured DOMAIN events onto the versioned protobuf tool-page contract and encodes them for a browser (a URL-fragment blob for a finished run, or a live SSE stream), plus the viewer's filter DSL. It consumes domain types straight from the repositories (e.g. the cache output store's []journal.Event) and maps them explicitly to the wire proto - no intermediate DTOs, no single-use converters. This file is the viewer encoders; query.go is the filter grammar.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyEventQuery added in v0.4.0

func ApplyEventQuery(events []journal.Event, q *viewerv1.EventQuery) []journal.Event

ApplyEventQuery returns the subset of events matching q, preserving order. A nil or empty q returns events unchanged. Set filters AND together; repeated values within a field OR; string comparison is case-insensitive; text matches are substring (a negated text match excludes events that contain it). The input slice is not mutated.

func EncodeEvent

func EncodeEvent(e journal.Event) (string, error)

EncodeEvent marshals one event to base64(protobuf) for an SSE `data:` line (SSE payloads must be UTF-8 text, so the binary message is base64-wrapped). The JS client base64- decodes then Event.fromBinary.

func EncodeJournalFragment

func EncodeJournalFragment(inv journal.Invocation, events []journal.Event) (string, error)

EncodeJournalFragment marshals a journal to protobuf, then gzip+base64url encodes it for a `#data=` URL fragment - the static delivery path. The generated JS client reverses it (base64url -> gunzip -> Journal.fromBinary). Reuses the same fragment encoder graph open uses, so the tool pages share one wire envelope.

func ParseEventQuery added in v0.4.0

func ParseEventQuery(s string) *viewerv1.EventQuery

ParseEventQuery parses a viewer filter DSL string into a typed EventQuery: whitespace separated "field:value" clauses plus free text, e.g. `project:web target:build kind:output -"cache miss"`. Repeated values on one REPEATED field OR; different fields AND; matching is case-insensitive. Field filters are include-only (they mirror the filter-menu checkboxes); negation ("-word") is supported only on free text. `status` is single-valued (a result has one status), so a second status: clause replaces the first. The time window is NOT parsed from the DSL - the filter menu sets EventQuery.Time programmatically (date pickers), and ApplyEventQuery honors it. This is the viewer's OWN grammar over the log's own fields; it deliberately does not reuse the knowledge-graph query parser (whose fields differ).

Types

type LiveServer

type LiveServer struct {
	// contains filtered or unexported fields
}

LiveServer streams one invocation's events to a browser over SSE, on a loopback httpx.Server. Start it with StartLive before the run, hand the browser LiveServer.ViewerURL, let the run emit into the broadcaster, then LiveServer.Stop when done.

func StartLive

func StartLive(origin string, bc *journal.Broadcaster) (*LiveServer, error)

StartLive starts a loopback SSE server on an ephemeral 127.0.0.1 port for bc in the background and mints a random bearer token. origin is the page allowed to read the stream. The caller owns bc and must add it to the invocation's capture logger so events flow, and call LiveServer.Stop when the run is finished.

func (*LiveServer) Addr

func (ls *LiveServer) Addr() string

Addr is the loopback "127.0.0.1:PORT" the server bound - the value the viewer connects its EventSource to.

func (*LiveServer) Stop

func (ls *LiveServer) Stop(ctx context.Context)

Stop shuts the server down, allowing a brief grace window first so a late or reloading browser still receives the full log. Call it once the run has finished and the broadcaster is closed.

func (*LiveServer) Token

func (ls *LiveServer) Token() string

Token is the per-run bearer token the viewer must present.

func (*LiveServer) ViewerURL

func (ls *LiveServer) ViewerURL(logsBase string) string

ViewerURL builds the viewer link for this live run: <logsBase>/#live=<addr>&token=<token>, where logsBase is the log viewer page URL (e.g. https://.../magus/logs/). BOTH the loopback host and the bearer token ride the URL fragment, which the browser never transmits to a server - so the connection details are handed to the page locally and nothing leaves the machine; the page reads the token and strips it from the URL.

type Service added in v0.4.0

type Service struct {
	// contains filtered or unexported fields
}

Service implements viewerv1alpha1connect.ViewerServiceHandler over the same two stores the plain-JSON run-browser routes read.

func NewService added in v0.4.0

func NewService(outputs outputSource, runs runSource) *Service

NewService builds the ViewerService Connect handler reading from the run and output stores.

func (*Service) GetInvocation added in v0.4.0

GetInvocation returns one run's header.

func (*Service) GetJournal added in v0.4.0

GetJournal returns one run whole - header plus every event.

func (*Service) GetOutput added in v0.4.0

GetOutput returns one stored run's captured bytes verbatim.

func (*Service) ListEvents added in v0.4.0

ListEvents returns a page of one run's events, narrowed by the request filter. The store returns the journal whole, so the page token is an offset into it rather than a cursor it could resume from.

The filter runs BEFORE the page is cut, so page_size counts MATCHING events. Cutting first would make a narrow filter over a long journal return an empty page while matches sat just past the boundary, and a caller cannot tell that from "no such events".

func (*Service) ListInvocations added in v0.4.0

ListInvocations returns the retained run journals, newest first.

func (*Service) ListOutputs added in v0.4.0

ListOutputs returns the stored runs' descriptors, newest first.

func (*Service) StreamEvents added in v0.4.0

StreamEvents replays one run's stored events, then tails the journal for what the run appends next, until the run finishes or the caller goes away.

It reads the journal rather than subscribing to a broadcaster because the daemon does not run the build: every `magus` invocation is its own process appending to <cacheDir>/runs/<inv>.jsonl, and that directory is the only place the daemon can observe a run from. journal.Broadcaster (the SSE route in live.go) is the in-process path for the run that owns it, and is unreachable here.

The filter runs BEFORE each send, as ListEvents applies it before paging, so a narrow filter makes a quiet stream rather than a stream of dropped frames. filter.time.since doubles as the resume cursor: the replay skips events older than it. That boundary is inclusive, so a caller resuming from the last event it saw receives that event again - at-least-once, which is the honest guarantee when the cursor is a millisecond timestamp several events can share.

A slow client cannot slow anything but itself: each stream owns its file offset and reads on the RPC's own goroutine, so it holds no lock and no buffer the run writes into. That is why there is nothing to drop here, unlike the SSE side, which shares a Broadcaster with the running process and skips a subscriber whose buffer is full.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL