defmodule Wobserver.Page do @moduledoc """ Page management for custom commands and pages in api and wobserver. """ alias Wobserver.Page @pages_table :wobserver_pages @typedoc ~S""" Accepted page formats. """ @type data :: Page.t | map | {String.t, atom, fun} | {String.t, atom, fun, boolean} @typedoc ~S""" Page structure. Fields: - `title`, the name of the page. Is used for the web interface menu. - `command`, single atom to associate the page with. - `callback`, function to be evaluated, when the a api is called or page is viewd. The result is converted to JSON and displayed. - `options`, map containing options for the page. Options: - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API. - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh. """ @type t :: %__MODULE__{ title: String.t, command: atom, callback: fun, options: keyword, } defstruct [ :title, :command, :callback, options: %{ api_only: false, refresh: 1, }, ] @doc ~S""" List all registered pages. For every page the following information is given: - `title` - `command` - `api_only` - `refresh` """ @spec list :: list(map) def list do ensure_table() @pages_table |> :ets.match(:"$1") |> Enum.map(fn [{command, %Page{title: title, options: options}}] -> %{ title: title, command: command, api_only: options.api_only, refresh: options.refresh, } end) end @doc ~S""" Find the page for a given `command` Returns `:page_not_found`, if no page can be found. """ @spec find(command :: atom) :: Page.t def find(command) do ensure_table() case :ets.lookup(@pages_table, command) do [{^command, page}] -> page _ -> :page_not_found end end @doc ~S""" Calls the function associated with the `command`/page. Returns the result of the function or `:page_not_found`, if the page can not be found. """ @spec call(Page.t | atom) :: any def call(:page_not_found), do: :page_not_found def call(%Page{callback: callback}), do: callback.() def call(command) when is_atom(command), do: command |> find() |> call() def call(_), do: :page_not_found @doc ~S""" Registers a `page` with `:wobserver`. Returns true if succesfully added. (otherwise false) The following inputs are accepted: - `{title, command, callback}` - `{title, command, callback, options}` - a `map` with the following fields: - `title` - `command` - `callback` - `options` (optional) The fields are used as followed: - `title`, the name of the page. Is used for the web interface menu. - `command`, single atom to associate the page with. - `callback`, function to be evaluated, when the a api is called or page is viewd. The result is converted to JSON and displayed. - `options`, options for the page. The following options can be set: - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API. - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh. """ @spec register(page :: Page.data) :: boolean def register(page) def register({title, command, callback}), do: register(title, command, callback) def register({title, command, callback, options}), do: register(title, command, callback, options) def register(page = %Page{}) do ensure_table() :ets.insert @pages_table, {page.command, page} end def register(%{title: t, command: command, callback: call, options: options}), do: register(t, command, call, options) def register(%{title: title, command: command, callback: callback}), do: register(title, command, callback) def register(_), do: false @doc ~S""" Registers a `page` with `:wobserver`. The arguments are used as followed: - `title`, the name of the page. Is used for the web interface menu. - `command`, single atom to associate the page with. - `callback`, function to be evaluated, when the a api is called or page is viewd. The result is converted to JSON and displayed. - `options`, options for the page. The following options can be set: - `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API. - `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh. """ @spec register( title :: String.t, command :: atom, callback :: fun, options :: keyword ) :: boolean def register(title, command, callback, options \\ []) do register(%Page{ title: title, command: command, callback: callback, options: %{ api_only: Keyword.get(options, :api_only, false), refresh: Keyword.get(options, :refresh, 1.0) }, }) end @doc ~S""" Loads custom pages from configuration and adds them to `:wobserver`. To add custom pages set the `:pages` option. The `:pages` option must be a list of page data. The page data can be formatted as: - `{title, command, callback}` - `{title, command, callback, options}` - a `map` with the following fields: - `title` - `command` - `callback` - `options` (optional) For more information and types see: `Wobserver.Page.register/1`. Example: ```elixir config :wobserver, pages: [ {"Example", :example, fn -> %{x: 9} end} ] ``` """ @spec load_config :: [any] def load_config do ensure_table() :wobserver |> Application.get_env(:pages, []) |> Enum.map(®ister/1) end # Helpers defp ensure_table do case :ets.info(@pages_table) do :undefined -> :ets.new @pages_table, [:named_table, :public] true _ -> true end end end