defmodule Bloccs.Web.Panels.Messages do @moduledoc """ Panel 5 — packages moving through the network. A live throughput chart over the per-second `Bloccs.Web.Telemetry.Flow` buckets, plus a scrolling feed of recent message events: each edge a message crossed (`from.port → to.port`), its outcome, the emitting node's latency, and — when `Bloccs.Inspect` capture is enabled (bloccs 0.3+) — a bounded, redacted snapshot of the payload. Filterable by node and outcome. """ use Bloccs.Web, :html import Bloccs.Web.Components.Chart alias Bloccs.Web.Format attr :network, :any, required: true attr :base_path, :string, required: true attr :flow, :map, default: %{events: [], series: [], rate: 0} attr :filters, :map, default: %{node: nil, outcome: nil} def render(assigns) do events = filter(assigns.flow.events, assigns.filters) assigns = assigns |> assign(:events, events) |> assign(:any_payload, Enum.any?(events, & &1[:payload])) ~H"""

Messages

live · {@flow.rate}/s
<.throughput series={@flow.series} />
{length(@events)} shown
Time From → To Payload Outcome Latency
{time(e.at)} {edge(e)} {payload(e)} <.status_pill state={pill(e.outcome)} label={Atom.to_string(e.outcome)} /> {Format.latency(e.duration_ms)}

No messages yet. Send traffic through the network and it appears here live.

Payload contents are hidden. Enable capture with config :bloccs, :inspect, enabled: true (bloccs 0.3+).

""" end defp filter(events, %{node: node, outcome: outcome}) do events |> reject_blank(:node, node, fn e, v -> to_string(e.node) == v end) |> reject_blank(:outcome, outcome, fn e, v -> outcome_class(e.outcome) == v end) end defp reject_blank(events, _key, v, _match) when v in [nil, ""], do: events defp reject_blank(events, _key, v, match), do: Enum.filter(events, &match.(&1, v)) defp outcome_class(:ok), do: "ok" defp outcome_class(o) when o in [:dropped, :skipped], do: "dropped" defp outcome_class(_), do: "failed" defp node_ids(%{nodes: nodes}), do: nodes |> Enum.map(& &1.id) |> Enum.sort() defp payload(%{payload: p}) when is_binary(p), do: p defp payload(_), do: "—" defp edge(%{out_port: nil, node: node}), do: "#{node}" defp edge(%{node: node, out_port: port, to: nil}), do: "#{node}.#{port} → ·" defp edge(%{node: node, out_port: port, to: {tn, tp}}), do: "#{node}.#{port} → #{tn}.#{tp}" defp pill(:ok), do: :ok defp pill(o) when o in [:dropped, :skipped], do: :idle defp pill(_), do: :failed defp time(ms) do ms |> DateTime.from_unix!(:millisecond) |> Calendar.strftime("%H:%M:%S") |> Kernel.<>("." <> (ms |> rem(1000) |> Integer.to_string() |> String.pad_leading(3, "0"))) end end