defmodule Forum.Admin do
@moduledoc """
HTML fragments served to the HTMX admin page (/admin_htmx). Two
panels (processes, modules) plus their row-only partials.
"""
# ── processes panel ───────────────────────────────────────────────
def processes_panel do
"""
| pid | name | initial call |
memory (KB) | msgs | status |
"""
end
def processes_rows(rows) do
rows
|> build_tree()
|> render_tree(0)
|> IO.iodata_to_binary()
end
defp build_tree(rows) do
by_parent = Enum.group_by(rows, & &1["parent"])
Map.get(by_parent, nil, []) |> Enum.map(&attach_children(&1, by_parent))
end
defp attach_children(node, by_parent) do
children = Map.get(by_parent, node["pid"], [])
Map.put(node, "children", Enum.map(children, &attach_children(&1, by_parent)))
end
defp render_tree(nodes, depth) do
Enum.map(nodes, fn node ->
[process_row(node, depth) | render_tree(node["children"], depth + 1)]
end)
end
defp process_row(node, depth) do
row_cls = if node["mine"], do: ~s| class="mine"|, else: ""
pad = 8 + depth * 16
name = node["name"]
name_cls = if name, do: ~s| class="name"|, else: ""
[
~s|
|,
~s|| #{h(node["pid"])} | |,
~s|#{h(name || "-")} | |,
~s|#{h(node["initial_call"])} | |,
~s|#{node["memory_kb"]} | |,
~s|#{node["msgs"]} | |,
~s|#{h(node["status"])} | |,
"
"
]
end
# ── modules panel ─────────────────────────────────────────────────
def modules_panel do
"""
"""
end
def modules_rows(rows) do
Enum.map_join(rows, "", &module_row/1)
end
defp module_row(r) do
row_cls = if r["mine"], do: ~s| class="mine"|, else: ""
~s|| <>
~s|| #{h(r["module"])} | | <>
~s|#{h(r["app"] || "-")} | | <>
~s|#{h(r["source"] || "(no source)")} | | <>
"
"
end
# ── helpers ───────────────────────────────────────────────────────
defp h(text) when is_binary(text) do
text
|> String.replace("&", "&")
|> String.replace("<", "<")
|> String.replace(">", ">")
|> String.replace("\"", """)
|> String.replace("'", "'")
end
defp h(other), do: other |> to_string() |> h()
end