This commit is contained in:
Sam
2026-06-10 11:51:56 -05:00
commit 66ba338b81
57 changed files with 5509 additions and 0 deletions

126
lib/forum/admin.ex Normal file
View File

@@ -0,0 +1,126 @@
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
"""
<section>
<form class="toolbar"
hx-get="/admin_htmx/processes/rows"
hx-target="#p-rows"
hx-trigger="change">
<label><input type="checkbox" name="mine"> mine</label>
<span class="htmx-indicator">…</span>
</form>
<table>
<thead><tr>
<th>pid</th><th>name</th><th>initial call</th>
<th class="num">memory (KB)</th><th class="num">msgs</th><th>status</th>
</tr></thead>
<tbody id="p-rows"
hx-get="/admin_htmx/processes/rows"
hx-trigger="load, every 5s"
hx-include="closest section form"></tbody>
</table>
</section>
"""
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|<tr#{row_cls}>|,
~s|<td style="padding-left:#{pad}px">#{h(node["pid"])}</td>|,
~s|<td#{name_cls}>#{h(name || "-")}</td>|,
~s|<td>#{h(node["initial_call"])}</td>|,
~s|<td class="num">#{node["memory_kb"]}</td>|,
~s|<td class="num">#{node["msgs"]}</td>|,
~s|<td>#{h(node["status"])}</td>|,
"</tr>"
]
end
# ── modules panel ─────────────────────────────────────────────────
def modules_panel do
"""
<section>
<form class="toolbar"
hx-get="/admin_htmx/modules/rows"
hx-target="#m-rows"
hx-trigger="change, input changed delay:200ms from:input[name=q]">
<label><input type="checkbox" name="mine"> mine</label>
<input type="text" name="q" placeholder="filter…" autocomplete="off">
<span class="htmx-indicator">…</span>
</form>
<table>
<thead><tr>
<th>module</th><th>app</th><th>source</th>
</tr></thead>
<tbody id="m-rows"
hx-get="/admin_htmx/modules/rows"
hx-trigger="load"
hx-include="closest section form"></tbody>
</table>
</section>
"""
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|<tr#{row_cls}>| <>
~s|<td class="mod">#{h(r["module"])}</td>| <>
~s|<td>#{h(r["app"] || "-")}</td>| <>
~s|<td class="src">#{h(r["source"] || "(no source)")}</td>| <>
"</tr>"
end
# ── helpers ───────────────────────────────────────────────────────
defp h(text) when is_binary(text) do
text
|> String.replace("&", "&amp;")
|> String.replace("<", "&lt;")
|> String.replace(">", "&gt;")
|> String.replace("\"", "&quot;")
|> String.replace("'", "&#39;")
end
defp h(other), do: other |> to_string() |> h()
end