feat: create a LiveDashboard page for viewing configuration servers.
This commit is contained in:
parent
477e938539
commit
94b23e98de
6 changed files with 286 additions and 16 deletions
38
LICENSE
Normal file
38
LICENSE
Normal file
|
@ -0,0 +1,38 @@
|
|||
Copyright 2021 James Harton
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
* No Harm: The software may not be used by anyone for systems or activities that
|
||||
actively and knowingly endanger, harm, or otherwise threaten the physical,
|
||||
mental, economic, or general well-being of other individuals or groups, in
|
||||
violation of the United Nations Universal Declaration of Human Rights
|
||||
(https://www.un.org/en/universal-declaration-human-rights/).
|
||||
* Services: If the Software is used to provide a service to others, the licensee
|
||||
shall, as a condition of use, require those others not to use the service in
|
||||
any way that violates the No Harm clause above.
|
||||
* Enforceability: If any portion or provision of this License shall to any
|
||||
extent be declared illegal or unenforceable by a court of competent
|
||||
jurisdiction, then the remainder of this License, or the application of such
|
||||
portion or provision in circumstances other than those as to which it is so
|
||||
declared illegal or unenforceable, shall not be affected thereby, and each
|
||||
portion and provision of this Agreement shall be valid and enforceable to the
|
||||
fullest extent permitted by law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This Hippocratic License is an Ethical Source license
|
||||
(https://ethicalsource.dev) derived from the MIT License, amended to limit the
|
||||
impact of the unethical use of open source software.
|
||||
|
28
README.md
28
README.md
|
@ -1,11 +1,15 @@
|
|||
# Lamina.Dashboard
|
||||
|
||||
**TODO: Add description**
|
||||
`Lamina.Dashboard` is a tool to visualise the current runtime configuration of
|
||||
the system.
|
||||
|
||||
It works as an additional page for [Phoenix LiveDashboard](https://hex.pm/packages/phoenix_live_dashboard).
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `lamina_dashboard` to your list of dependencies in `mix.exs`:
|
||||
`Lamina.Dashboard` is [available in Hex](https://hex.pm/packages/lamina_dashboard),
|
||||
the package can be installed by adding `lamina_dashboard` to your list of
|
||||
dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
|
@ -15,7 +19,19 @@ def deps do
|
|||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/lamina_dashboard](https://hexdocs.pm/lamina_dashboard).
|
||||
Documentation for the latest release can be found on [HexDocs](https://hexdocs.pm/lamina) and for the `main` branch [here](https://jimsy.gitlab.io/lamina_dashboard/api-reference.html).
|
||||
|
||||
## Integration with LiveDashboard
|
||||
|
||||
You can add this page to your Phoenix LiveDashboard by adding it as a page in the `live_dashboard` macro in your router file:
|
||||
|
||||
```elixir
|
||||
live_dashboard "/dashboard",
|
||||
additional_pages: [lamina: Lamina.Dashboard]
|
||||
```
|
||||
|
||||
Once configured, you will be able to access `Lamina.Dashboard` at `/dashboard/lamina`.
|
||||
|
||||
## Distribution
|
||||
|
||||
You can use `Lamina.Dashboard` to view the configuration on remote nodes by simply adding the `lamina_dashboard` package as a dependency of your remote nodes.
|
||||
|
|
|
@ -1,18 +1,117 @@
|
|||
defmodule Lamina.Dashboard do
|
||||
use Phoenix.LiveDashboard.PageBuilder
|
||||
alias Lamina.Dashboard.Remote
|
||||
alias Lamina.Registry.ServerRegistry
|
||||
alias Phoenix.LiveView.Socket
|
||||
|
||||
@moduledoc """
|
||||
Documentation for `Lamina.Dashboard`.
|
||||
A LiveDashboard.PageBuilder which shows information about currently running
|
||||
Lamina configuration servers.
|
||||
|
||||
## Usage:
|
||||
|
||||
Add the following to your LiveDashboard entry in your Phoenix router:
|
||||
|
||||
```elixir
|
||||
live_dashboard "/dashboard",
|
||||
additional_pages: [lamina: Lamina.Dashboard]
|
||||
```
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Hello world.
|
||||
@doc false
|
||||
@impl true
|
||||
@spec mount(map, any, Socket.t()) :: {:ok, Socket.t()}
|
||||
def mount(params, _session, socket) do
|
||||
node = socket.assigns.page.node
|
||||
servers = Remote.list_servers(node)
|
||||
socket = assign(socket, servers: servers)
|
||||
nav = params["nav"]
|
||||
server = find_server(nav)
|
||||
first_server = hd(servers)
|
||||
|
||||
## Examples
|
||||
cond do
|
||||
is_nil(server) && first_server ->
|
||||
to = live_dashboard_path(socket, socket.assigns.page, nav: server_id(first_server))
|
||||
{:ok, push_redirect(socket, to: to)}
|
||||
|
||||
iex> Lamina.Dashboard.hello()
|
||||
:world
|
||||
server ->
|
||||
config_keys = Remote.list_config_keys(node, server)
|
||||
providers = Remote.list_providers(node, server)
|
||||
|
||||
"""
|
||||
def hello do
|
||||
:world
|
||||
{:ok, assign(socket, providers: providers, config_keys: config_keys, server: server)}
|
||||
|
||||
true ->
|
||||
{:ok, socket}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
def menu_link(_, _), do: {:ok, "Lamina"}
|
||||
|
||||
@doc false
|
||||
@impl true
|
||||
def render_page(%{servers: []} = assigns) do
|
||||
fn ->
|
||||
~H"""
|
||||
No Lamina servers running on #{@page.node}.
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
def render_page(assigns) do
|
||||
items =
|
||||
for server <- assigns.servers do
|
||||
{server_id(server),
|
||||
name: inspect(server), render: render_server(assigns), method: :redirect}
|
||||
end
|
||||
|
||||
nav_bar(items: items)
|
||||
end
|
||||
|
||||
defp render_server(assigns) do
|
||||
fn ->
|
||||
table(
|
||||
columns: table_columns(),
|
||||
id: assigns.server,
|
||||
row_attrs: &row_attrs/1,
|
||||
row_fetcher: &fetch_table(assigns.server, &1, &2),
|
||||
title: "configuration settings"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp server_id(server) when is_atom(server) do
|
||||
server
|
||||
|> Atom.to_string()
|
||||
|> Base.encode64(padding: false)
|
||||
end
|
||||
|
||||
defp find_server(server_id) do
|
||||
ServerRegistry.all_servers()
|
||||
|> Enum.find(&(server_id(&1) == server_id))
|
||||
end
|
||||
|
||||
defp table_columns,
|
||||
do: [
|
||||
%{
|
||||
field: :name,
|
||||
header: "Config key",
|
||||
header_attrs: [class: "p1-4"],
|
||||
cell_attrs: [class: "tabular-column-name p1-4"],
|
||||
sortable: :asc
|
||||
},
|
||||
%{
|
||||
field: :value,
|
||||
format: &format_value/1
|
||||
}
|
||||
]
|
||||
|
||||
defp format_value(value), do: inspect(value)
|
||||
|
||||
defp row_attrs(_table), do: []
|
||||
|
||||
defp fetch_table(server, params, node) do
|
||||
Remote.get_configs(node, server, params)
|
||||
end
|
||||
end
|
||||
|
|
99
lib/lamina/dashboard/remote.ex
Normal file
99
lib/lamina/dashboard/remote.ex
Normal file
|
@ -0,0 +1,99 @@
|
|||
defmodule Lamina.Dashboard.Remote do
|
||||
alias Lamina.Registry.ServerRegistry
|
||||
alias Lamina.Server
|
||||
|
||||
@moduledoc """
|
||||
Uses the Erlang `:rpc` module to interact with potentially-remote Lamina
|
||||
servers.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
List the running configuration servers on the remote node.
|
||||
"""
|
||||
@spec list_servers(node) :: [module]
|
||||
def list_servers(node) do
|
||||
:rpc.call(node, __MODULE__, :do_list_servers, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
List the configuration keys for a specific server on the remote node.
|
||||
"""
|
||||
@spec list_config_keys(node, module) :: [atom]
|
||||
def list_config_keys(node, server) do
|
||||
:rpc.call(node, __MODULE__, :do_list_config_keys, [server])
|
||||
end
|
||||
|
||||
@doc """
|
||||
List the configured providers for server on node.
|
||||
"""
|
||||
@spec list_providers(node, module) :: [{module, keyword}]
|
||||
def list_providers(node, server) do
|
||||
:rpc.call(node, __MODULE__, :do_list_providers, [server])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieve all configuration values from a remote server.
|
||||
"""
|
||||
@spec get_configs(node, module, map) :: [map]
|
||||
def get_configs(node, server, params) do
|
||||
:rpc.call(node, __MODULE__, :do_get_configs, [server, params])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec do_list_servers :: [module]
|
||||
def do_list_servers do
|
||||
ServerRegistry.all_servers()
|
||||
|> Enum.sort()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec do_list_config_keys(module) :: [atom]
|
||||
def do_list_config_keys(server) do
|
||||
apply(server, :__lamina__, [:config_keys])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec do_list_providers(module) :: [{module, keyword}]
|
||||
def do_list_providers(server) do
|
||||
apply(server, :__lamina__, [:providers])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec do_get_configs(module, map) :: {[map], non_neg_integer()}
|
||||
def do_get_configs(server, params) do
|
||||
sort_dir = if params.sort_dir == :asc, do: &<=/2, else: &>=/2
|
||||
|
||||
all_rows =
|
||||
server
|
||||
|> apply(:__lamina__, [:config_keys])
|
||||
|> Enum.map(fn config_key ->
|
||||
%{
|
||||
name: config_key,
|
||||
value: Server.get!(server, config_key)
|
||||
}
|
||||
end)
|
||||
|
||||
rows =
|
||||
all_rows
|
||||
|> maybe_filter_rows(params.search)
|
||||
|> Enum.sort_by(&Map.get(&1, params.sort_by), sort_dir)
|
||||
|> Enum.take(params.limit)
|
||||
|
||||
{rows, length(all_rows)}
|
||||
end
|
||||
|
||||
defp maybe_filter_rows(rows, nil), do: rows
|
||||
|
||||
defp maybe_filter_rows(rows, search_term) do
|
||||
Stream.filter(rows, fn row ->
|
||||
row
|
||||
|> Map.values()
|
||||
|> Enum.any?(fn value ->
|
||||
value
|
||||
|> inspect()
|
||||
|> String.downcase()
|
||||
|> String.contains?(search_term)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
6
mix.exs
6
mix.exs
|
@ -39,7 +39,11 @@ defmodule Lamina.Dashboard.MixProject do
|
|||
[
|
||||
{:credo, "~> 1.5", only: ~w[dev test]a},
|
||||
{:ex_doc, ">= 0.0.0", only: ~w[dev test]a},
|
||||
{:git_ops, "~> 2.3", only: ~w[dev test]a, runtime: false}
|
||||
{:git_ops, "~> 2.3", only: ~w[dev test]a, runtime: false},
|
||||
{:jason, "~> 1.0", only: ~w[dev test]a},
|
||||
{:lamina, "~> 0.4"},
|
||||
{:phoenix_live_dashboard, "~> 0.5.1", optional: true},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
14
mix.lock
14
mix.lock
|
@ -7,8 +7,22 @@
|
|||
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
||||
"git_ops": {:hex, :git_ops, "2.4.5", "185a724dfde3745edd22f7571d59c47a835cf54ded67e9ccbc951920b7eec4c2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e323a5b01ad53bc8c19c3a444be3e61ed7803ecd2e95530446ae9327d0143ecc"},
|
||||
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
|
||||
"lamina": {:hex, :lamina, "0.4.0", "d9f984a53e64cfb1c6fcf2409fc7cd3347e12cca7887735da312651858459598", [:mix], [{:recase, "~> 0.7", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "99add10816fa58ea5f0f7e3a24341b1f2037f9b2fd199f950f47027db0644596"},
|
||||
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.0", "7b85023f7ddef9a5c70909a51cc37c8b868b474d853f90f4280efd26b0e7cce5", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "52ffdd31f2daeb399b2e1eb57d468f99a1ad6eee5d8ea19d2353492f06c9fc96"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.2", "b3b863ba9da3c9bd0b18fc32e96e8e5e25faf6a5f62db1dd91029835ea4cc90f", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "6d7124f36ee6c74be334386b8b5a1eb27223c77f86f4167de132b9358036f199"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.16.4", "5692edd0bac247a9a816eee7394e32e7a764959c7d0cf9190662fc8b0cd24c97", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "754ba49aa2e8601afd4f151492c93eb72df69b0b9856bab17711b8397e43bba0"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
|
||||
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
|
||||
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue