Phoenix Live Dashboard Custom Page

Phoenix Live Dashboard Custom Page

Table of Contents

Today I played with custom live dashboard pages, That’s a short documentation how to use it. We will implement a page that shows the currently running system processes on the box running our elixir system.

Documentation can be found on hexdocs. Most up to date code is attached in the gist.

First we need to create a new module:

defmodule Phoenix.LiveDashboard.OsProcesses do
  @moduledoc false
  use Phoenix.LiveDashboard.PageBuilder

end

To get the list I’ll use the output of ps aux command. I’ll cheat here a bit and use jq to convert the page to json which later can be easily decoded by Jason. This also shows how to use more complicated commands. We can of course parse the string in elixir.

To use pipes inside the command I had to use the os module with a character list passed as an option. In the gist the ps parser is written in pure elixir but for simplicity I’ll leave the jq version here. On windows you can play with tasklist the output is a bit similar but there are spaces on the first line so you may want to parse it a bit different.

  defp fetch_processes(_params, _node) do
    data = 
    :os.cmd('ps aux | jq -sR \'[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]] | .[0] as $header | .[1:] | [.[] | [. as $x | range($header | length) | {"key": $header[.], "value": $x[.]}] | from_entries]\'')
    |> Jason.decode!(%{keys: :atoms!})

    {data, length(data)}
  end

The return value of this function must be a tuple with rows and the length of the rows. Next function we need to write is the columns definitions:

  defp columns() do
    [
      %{
        field: :"PID",
        sortable: :asc
      },
      %{
        field: :"TTY"
      },
      %{
        field: :"TIME",
        cell_attrs: [class: "text-right"],
      },
      %{
        field: :"USER",
      },
      %{
        field: :"%CPU",
        sortable: :desc
      },
      %{
        field: :"%MEM",
        sortable: :desc
      },
      %{
        field: :"VSZ",
        sortable: :asc
      },
      %{
        field: :"RSS",
        sortable: :asc
      },
      %{
        field: :"STAT",
      },
      %{
        field: :"START",
        sortable: :asc
      },
      %{
        field: :"COMMAND",
      },
    ]
  end

Both of this functions need to be passed to the render_page/1 callback:

  @impl true
  def render_page(_assigns) do
    table(
      columns: columns(),
      id: :os_processes,
      row_attrs: &row_attrs/1,
      row_fetcher: &fetch_processes/2,
      rows_name: "tables",
      title: "OS processes",
      sort_by: :"PID"
    )
  end

As we see there is another function required, the row_attrs that returns html attributes for our table rows. And thats basically it.

Last two pieces of code is the menu_link/2 callback:

  @impl true
  def menu_link(_, _) do
    {:ok, "OS Processes"}
  end

and the router entry for our page:

    scope "/" do
      pipe_through :browser
    live_dashboard "/dashboard", metrics: ChatterWeb.Telemetry,
    additional_pages: [
    os_processes: Phoenix.LiveDashboard.OsProcesses
  ]

And that’s basically it - The code can be found on github, with the params used for sorting, limiting and search. Have fun and link any pages you have created.

Tags :
comments powered by Disqus

Related Posts

Show Country Emoji with Elixir

Show Country Emoji with Elixir

Recently jorik posted about converting country code to flag emoji. I decided to give it a try in elixir.

Read More
Enhancements to dbg in Elixir 1.18

Enhancements to dbg in Elixir 1.18

Elixir 1.18 added some interesting features, but one that went under the radar was extended support for dbg. In 1.17 when you had this code:

Read More
Machine Learning on Ryzen 7840HS

Machine Learning on Ryzen 7840HS

I bought an AMD laptop with 7840hs and 32GB RAM with the hope that I can play with machine learning models. At the beginning it was problematic, I could not get anything bigger to work but after some time spent reading and experimenting here are some of my findings.

Read More