defmodule Integrations.OpenSearch.Display do @moduledoc """ Dashboard panel for `Integrations.OpenSearch` (same package). Nested under the monitor's own namespace deliberately — the package's top-level module name never changes, and this module's name is guaranteed distinct from any separately-published standalone display package. `Display.BundledDefault` auto-hooks this whenever `Integrations.OpenSearch` starts, the same as if they were still one module. """ use CodeNameRaven.Display, category: :data, size_hint: :large @default_port 9200 @impl true def display_name, do: "OpenSearch" @impl true def compatible_monitors, do: [Integrations.OpenSearch] @impl true def render(assigns) do samples = CodeNameRaven.Runtime.recent_samples(assigns.config.id, limit: 120) local_id = CodeNameRaven.Runtime.instance_id() result = assigns[:result] assigns = Map.merge(assigns, %{ samples: samples, local_id: local_id, result: result }) ~H"""
<%!-- Header --%>

<%= @config.name || instance_title(@config.params) %>

<%= instance_title(@config.params) %> <%= if @result && @result[:version], do: " · v#{@result.version}" %> <%= if @result && @result[:cluster_name], do: " · #{@result.cluster_name}" %>

<.cluster_status_badge result={@result} />
<%!-- Cluster overview --%>
<.stat label="Latency" value={"#{@result.latency_ms} ms"} /> <.stat label="Nodes" value={"#{@result[:node_count] || "–"}"} /> <.stat label="Indices" value={"#{@result[:index_count] || "–"}"} /> <.stat label="Shards" value={"#{@result[:active_shards] || "–"}"} /> <.stat :if={unassigned?(@result)} label="Unassigned" value={"#{@result[:unassigned_shards]}"} /> <.stat :if={pending?(@result)} label="Pending" value={"#{@result[:pending_tasks]}"} />
<%!-- Document and store totals --%>
<.stat label="Documents" value={format_count(@result[:docs_count])} /> <.stat label="Store Size" value={format_bytes(@result[:store_size_bytes])} />
<%!-- JVM heap bar --%>
JVM Heap <%= format_bytes(@result[:heap_used_bytes]) %> / <%= format_bytes(@result[:heap_max_bytes]) %> (<%= @result[:heap_used_pct] %>%)
<%!-- Throughput rates --%>
<.stat :if={@result[:indexing_rate]} label="Index/s" value={format_rate(@result[:indexing_rate])} /> <.stat :if={@result[:search_rate]} label="Search/s" value={format_rate(@result[:search_rate])} />
<%!-- Charts --%>

Indexing / sec

Search / sec

JVM Heap %

Latency

Checked <%= CodeNameRaven.Timezone.format_datetime(@last_checked_at, __MODULE__) %>

""" end # --------------------------------------------------------------------------- # Private components # --------------------------------------------------------------------------- defp cluster_status_badge(%{result: %{cluster_status: "green"}} = assigns) do ~H""" Green """ end defp cluster_status_badge(%{result: %{cluster_status: "yellow"}} = assigns) do ~H""" Yellow """ end defp cluster_status_badge(%{result: %{cluster_status: "red"}} = assigns) do ~H""" Red """ end defp cluster_status_badge(assigns) do ~H""" Down """ end defp stat(assigns) do ~H"""

<%= @label %>

<%= @value %>

""" end # --------------------------------------------------------------------------- # Private helpers # --------------------------------------------------------------------------- defp unassigned?(%{unassigned_shards: n}) when is_integer(n) and n > 0, do: true defp unassigned?(_), do: false defp pending?(%{pending_tasks: n}) when is_integer(n) and n > 0, do: true defp pending?(_), do: false defp heap_color(pct) when is_integer(pct) and pct >= 90, do: "bg-error" defp heap_color(pct) when is_integer(pct) and pct >= 75, do: "bg-warning" defp heap_color(_), do: "bg-success" defp instance_title(params) do host = params[:host] || params["host"] || "?" port = parse_int(params[:port] || params["port"], @default_port) "#{host}:#{port}" end defp format_count(nil), do: "–" defp format_count(n) when is_number(n) and n >= 1_000_000_000, do: "#{Float.round(n / 1_000_000_000.0, 1)}B" defp format_count(n) when is_number(n) and n >= 1_000_000, do: "#{Float.round(n / 1_000_000.0, 1)}M" defp format_count(n) when is_number(n) and n >= 1_000, do: "#{Float.round(n / 1_000.0, 1)}K" defp format_count(n), do: to_string(n) defp format_bytes(nil), do: "–" defp format_bytes(b) when is_number(b) and b >= 1_099_511_627_776, do: "#{Float.round(b / 1_099_511_627_776.0, 1)} TB" defp format_bytes(b) when is_number(b) and b >= 1_073_741_824, do: "#{Float.round(b / 1_073_741_824.0, 1)} GB" defp format_bytes(b) when is_number(b) and b >= 1_048_576, do: "#{Float.round(b / 1_048_576.0, 1)} MB" defp format_bytes(b) when is_number(b) and b >= 1_024, do: "#{Float.round(b / 1_024.0, 1)} KB" defp format_bytes(b), do: "#{b} B" defp format_rate(nil), do: "–" defp format_rate(r) when is_float(r), do: "#{Float.round(r, 1)}/s" defp format_rate(r), do: "#{r}/s" defp parse_int(nil, default), do: default defp parse_int(v, _) when is_integer(v), do: v defp parse_int(v, default) when is_binary(v) do case Integer.parse(v) do {n, _} -> n :error -> default end end defp parse_int(_, default), do: default end