DEV Community

Cover image for Phoenix Live Dashboard custom page using the table component
Daniel Kukula
Daniel Kukula

Posted on • Updated on

Phoenix Live Dashboard custom page using the table component

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  ]
Enter fullscreen mode Exit fullscreen mode

and that 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.

Top comments (2)

Collapse
 
allanmacgregor profile image
Allan MacGregor 🇨🇦

Excellent Great article!!!!

Collapse
 
dkuku profile image
Daniel Kukula

Thanks Allan. I updated it so jq is not needed, I'm parsing the ps output in elixir and I also hooked up the search and filter params