diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..888d407
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*.ex]
+indent_style = space
+indent_size = 2
+
+[*.eex]
+indent_style = space
+indent_size = 2
diff --git a/lib/ha_handler.ex b/lib/ha_handler.ex
index c2252c6..056d944 100644
--- a/lib/ha_handler.ex
+++ b/lib/ha_handler.ex
@@ -6,8 +6,8 @@ defmodule HAHandler do
# Mix is not available in releases, and these things are static
# anyway (@variables are evaluated at compile time).
@otp_app Mix.Project.config()[:app]
- @version Mix.Project.config[:version]
- @env Mix.env
+ @version Mix.Project.config()[:version]
+ @env Mix.env()
def http_port, do: Application.get_env(@otp_app, :http_port)
def haproxy_socket, do: Application.get_env(@otp_app, :haproxy_socket)
@@ -26,6 +26,7 @@ defmodule HAHandler do
from: acme_challenge_path()
]
end
+
def assets_static_config() do
[
at: "/static",
diff --git a/lib/ha_handler/application.ex b/lib/ha_handler/application.ex
index 725967f..139126d 100644
--- a/lib/ha_handler/application.ex
+++ b/lib/ha_handler/application.ex
@@ -10,7 +10,8 @@ defmodule HAHandler.Application do
@impl true
def start(_type, _args) do
children = [
- {Plug.Cowboy, scheme: :http, plug: HAHandler.Web.Router, options: [port: HAHandler.http_port()]},
+ {Plug.Cowboy,
+ scheme: :http, plug: HAHandler.Web.Router, options: [port: HAHandler.http_port()]},
{HAHandler.PGSQL.Supervisor, HAHandler.pgsql_instances()},
{HAHandler.Control, []}
]
diff --git a/lib/ha_handler/control.ex b/lib/ha_handler/control.ex
index a602734..20ec667 100644
--- a/lib/ha_handler/control.ex
+++ b/lib/ha_handler/control.ex
@@ -1,82 +1,104 @@
defmodule HAHandler.Control do
- @moduledoc """
- This module handles the decision-logic and actions to be
- taken regarding the current state of the infrastructure.
- """
+ @moduledoc """
+ This module handles the decision-logic and actions to be
+ taken regarding the current state of the infrastructure.
+ """
- @haproxy_pgsql_backend "pgsql"
+ @haproxy_pgsql_backend "pgsql"
- use GenServer
+ use GenServer
- require Logger
+ require Logger
- alias HAHandler.{PGSQL, HAProxy}
+ alias HAHandler.{PGSQL, HAProxy}
- # How much do we wait (ms) between each check/decision-making round?
- @refresh 15_000
+ # How much do we wait (ms) between each check/decision-making round?
+ @refresh 15_000
- def start_link(opts) do
- GenServer.start_link(__MODULE__, opts, name: __MODULE__)
- end
+ def start_link(opts) do
+ GenServer.start_link(__MODULE__, opts, name: __MODULE__)
+ end
- @impl true
- def init(_opts) do
- state = []
+ @impl true
+ def init(_opts) do
+ state = []
- # Let's skip the initial startup round so that other components are all up
- # and running.
- Process.send_after self(), :sync, @refresh
+ # Let's skip the initial startup round so that other components are all up
+ # and running.
+ Process.send_after(self(), :sync, @refresh)
- {:ok, state}
- end
+ {:ok, state}
+ end
- @impl true
- def handle_info(:sync, state) do
- Logger.debug("Executing control logic.")
+ @impl true
+ def handle_info(:sync, state) do
+ Logger.debug("Executing control logic.")
- # Fetch PGSQL state, make sure HAProxy routes to the master
- # process.
- pgsql_state = PGSQL.get_instances()
- |> Enum.map(fn {hostname, pid} = instance->
- haproxy_server = HAHandler.pgsql_instances()
- |> Enum.filter(fn opts -> Keyword.get(opts, :hostname) == hostname end)
- |> Enum.at(0)
- |> Keyword.get(:haproxy_server)
+ # Fetch PGSQL state, make sure HAProxy routes to the master
+ # process.
+ pgsql_state =
+ PGSQL.get_instances()
+ |> Enum.map(fn {hostname, pid} = instance ->
+ haproxy_server =
+ HAHandler.pgsql_instances()
+ |> Enum.filter(fn opts -> Keyword.get(opts, :hostname) == hostname end)
+ |> Enum.at(0)
+ |> Keyword.get(:haproxy_server)
- %{
- haproxy_server: haproxy_server,
- pgsql_watcher_pid: pid,
- pgsql_operation_mode: PGSQL.get_operation_mode(instance)
- }
- end)
- haproxy_state = HAProxy.get_stats()
- |> Map.get("Server", [])
- |> Enum.filter(fn mapping -> mapping["pxname"] == @haproxy_pgsql_backend end)
- |> Enum.map(fn mapping -> %{mapping["svname"] => mapping["status"]} end)
- |> Enum.reduce(&Map.merge/2)
+ %{
+ haproxy_server: haproxy_server,
+ pgsql_watcher_pid: pid,
+ pgsql_operation_mode: PGSQL.get_operation_mode(instance)
+ }
+ end)
- for pgsql_instance <- pgsql_state do
- haproxy_state = Map.get(haproxy_state, pgsql_instance.haproxy_server)
+ haproxy_state =
+ HAProxy.get_stats()
+ |> Map.get("Server", [])
+ |> Enum.filter(fn mapping -> mapping["pxname"] == @haproxy_pgsql_backend end)
+ |> Enum.map(fn mapping -> %{mapping["svname"] => mapping["status"]} end)
+ |> Enum.reduce(&Map.merge/2)
- case {pgsql_instance.pgsql_operation_mode, haproxy_state} do
- {:primary, "UP"} ->
- :noop
- {:primary, "MAINT"} ->
- Logger.info("Enabling routing PGSQL to (now) primary #{pgsql_instance.haproxy_server}.")
- HAProxy.set_server(@haproxy_pgsql_backend, pgsql_instance.haproxy_server, "state", "ready")
- {:secondary, "UP"} ->
- Logger.info("Disabling routing PGSQL to (now) secondary #{pgsql_instance.haproxy_server}.")
- HAProxy.set_server(@haproxy_pgsql_backend, pgsql_instance.haproxy_server, "state", "maint")
- {:secondary, "MAINT"} ->
- :noop
- unknown ->
- Logger.warning("Unhandled PGSQL/HAProxy state: #{inspect(unknown)}")
- end
- end
+ for pgsql_instance <- pgsql_state do
+ haproxy_state = Map.get(haproxy_state, pgsql_instance.haproxy_server)
- # Schedule next round.
- Process.send_after self(), :sync, @refresh
+ case {pgsql_instance.pgsql_operation_mode, haproxy_state} do
+ {:primary, "UP"} ->
+ :noop
- {:noreply, state}
- end
+ {:primary, "MAINT"} ->
+ Logger.info("Enabling routing PGSQL to (now) primary #{pgsql_instance.haproxy_server}.")
+
+ HAProxy.set_server(
+ @haproxy_pgsql_backend,
+ pgsql_instance.haproxy_server,
+ "state",
+ "ready"
+ )
+
+ {:secondary, "UP"} ->
+ Logger.info(
+ "Disabling routing PGSQL to (now) secondary #{pgsql_instance.haproxy_server}."
+ )
+
+ HAProxy.set_server(
+ @haproxy_pgsql_backend,
+ pgsql_instance.haproxy_server,
+ "state",
+ "maint"
+ )
+
+ {:secondary, "MAINT"} ->
+ :noop
+
+ unknown ->
+ Logger.warning("Unhandled PGSQL/HAProxy state: #{inspect(unknown)}")
+ end
+ end
+
+ # Schedule next round.
+ Process.send_after(self(), :sync, @refresh)
+
+ {:noreply, state}
+ end
end
diff --git a/lib/ha_handler/ha_proxy.ex b/lib/ha_handler/ha_proxy.ex
index d753585..cfb313c 100644
--- a/lib/ha_handler/ha_proxy.ex
+++ b/lib/ha_handler/ha_proxy.ex
@@ -33,28 +33,31 @@ defmodule HAHandler.HAProxy do
# run out - which will block all operations.
close_socket(fd)
data
+
{:error, err} ->
{:error, err}
end
end
defp extract_stats(data) do
- extracted = for entry <- data do
- for mapping <- entry do
- case mapping do
- %{
- "id" => id,
- "proxyId" => proxy_id,
- "objType" => type,
- "field" => %{"name" => name},
- "value" => %{"value" => value},
- } ->
- %{:id => id, :proxy_id => proxy_id, :type => type, :field => name, :value => value}
- _ ->
- nil
+ extracted =
+ for entry <- data do
+ for mapping <- entry do
+ case mapping do
+ %{
+ "id" => id,
+ "proxyId" => proxy_id,
+ "objType" => type,
+ "field" => %{"name" => name},
+ "value" => %{"value" => value}
+ } ->
+ %{:id => id, :proxy_id => proxy_id, :type => type, :field => name, :value => value}
+
+ _ ->
+ nil
+ end
end
end
- end
extracted
|> List.flatten()
@@ -62,18 +65,14 @@ defmodule HAHandler.HAProxy do
fn mapping -> {mapping.type, mapping.id, mapping.proxy_id} end,
fn mapping -> %{mapping.field => mapping.value} end
)
- |> Enum.map(
- fn {{type, id, proxy_id}, grouped_mappings} ->
- grouped_mappings
- |> Enum.reduce(fn l, r -> Map.merge(l,r) end)
- |> Map.put("type", type)
- |> Map.put("id", id)
- |> Map.put("proxy_id", proxy_id)
- end
- )
- |> Enum.group_by(
- fn entry -> Map.get(entry, "type") end
- )
+ |> Enum.map(fn {{type, id, proxy_id}, grouped_mappings} ->
+ grouped_mappings
+ |> Enum.reduce(fn l, r -> Map.merge(l, r) end)
+ |> Map.put("type", type)
+ |> Map.put("id", id)
+ |> Map.put("proxy_id", proxy_id)
+ end)
+ |> Enum.group_by(fn entry -> Map.get(entry, "type") end)
end
@doc """
@@ -81,7 +80,8 @@ defmodule HAHandler.HAProxy do
a list of Maps.
"""
def get_stats(opts \\ [])
- def get_stats([hide_error: true]) do
+
+ def get_stats(hide_error: true) do
case get_stats() do
{:error, _err} ->
%{
@@ -89,9 +89,12 @@ defmodule HAHandler.HAProxy do
"Backend" => %{},
"Server" => %{}
}
- stats -> stats
+
+ stats ->
+ stats
end
end
+
def get_stats(_opts) do
case execute("show stat json") do
{:ok, raw} ->
@@ -117,8 +120,10 @@ defmodule HAHandler.HAProxy do
case execute("set server #{backend}/#{server} #{key} #{value}") do
{:ok, ""} ->
:ok
+
{:ok, err} ->
{:error, err}
+
{:error, err} ->
{:error, err}
end
diff --git a/lib/ha_handler/pgsql.ex b/lib/ha_handler/pgsql.ex
index c982de1..d8c131a 100644
--- a/lib/ha_handler/pgsql.ex
+++ b/lib/ha_handler/pgsql.ex
@@ -1,45 +1,52 @@
defmodule HAHandler.PGSQL do
- @supervisor HAHandler.PGSQL.Supervisor
- @version_query "SELECT version();"
- @is_in_recovery_query "SELECT pg_is_in_recovery();"
+ @supervisor HAHandler.PGSQL.Supervisor
+ @version_query "SELECT version();"
+ @is_in_recovery_query "SELECT pg_is_in_recovery();"
- def get_instances() do
- watchers = Supervisor.which_children(@supervisor)
- for {hostname, pid, _type, _modules} <- watchers do
- {hostname, pid}
- end
- end
+ def get_instances() do
+ watchers = Supervisor.which_children(@supervisor)
- def get_version({hostname, pid}) do
- case GenServer.call(pid, {:execute, @version_query, []}) do
- {:ok, %Postgrex.Result{rows: [[raw_version_string]]}} ->
- version = case Regex.run(~r/^PostgreSQL (\d+\.\d+)/, raw_version_string) do
- [_, version_number] -> version_number
- _ -> "unknown"
- end
- %{hostname: hostname, version: version, status: "up"}
- {:error, %DBConnection.ConnectionError{message: _msg, reason: err}} ->
- %{hostname: hostname, version: "unknown", status: err}
- _ ->
- %{hostname: hostname, version: "unknown", status: :unknown}
- end
- end
+ for {hostname, pid, _type, _modules} <- watchers do
+ {hostname, pid}
+ end
+ end
- def get_operation_mode({_hostname, pid}) do
- case GenServer.call(pid, {:execute, @is_in_recovery_query, []}) do
- {:ok, %Postgrex.Result{rows: [[false]]}} ->
- :primary
- {:ok, %Postgrex.Result{rows: [[true]]}} ->
- :secondary
- _ ->
- :unknown
- end
- end
+ def get_version({hostname, pid}) do
+ case GenServer.call(pid, {:execute, @version_query, []}) do
+ {:ok, %Postgrex.Result{rows: [[raw_version_string]]}} ->
+ version =
+ case Regex.run(~r/^PostgreSQL (\d+\.\d+)/, raw_version_string) do
+ [_, version_number] -> version_number
+ _ -> "unknown"
+ end
- def get_stats() do
- get_instances()
- |> Enum.map(fn instance ->
- get_version(instance) |> Map.put(:mode, get_operation_mode(instance))
- end)
- end
+ %{hostname: hostname, version: version, status: "up"}
+
+ {:error, %DBConnection.ConnectionError{message: _msg, reason: err}} ->
+ %{hostname: hostname, version: "unknown", status: err}
+
+ _ ->
+ %{hostname: hostname, version: "unknown", status: :unknown}
+ end
+ end
+
+ def get_operation_mode({_hostname, pid}) do
+ case GenServer.call(pid, {:execute, @is_in_recovery_query, []}) do
+ {:ok, %Postgrex.Result{rows: [[false]]}} ->
+ :primary
+
+ {:ok, %Postgrex.Result{rows: [[true]]}} ->
+ :secondary
+
+ _ ->
+ :unknown
+ end
+ end
+
+ def get_stats() do
+ get_instances()
+ |> Enum.map(fn instance ->
+ get_version(instance) |> Map.put(:mode, get_operation_mode(instance))
+ end)
+ end
end
diff --git a/lib/ha_handler/pgsql/supervisor.ex b/lib/ha_handler/pgsql/supervisor.ex
index 6d7a1d0..89cca46 100644
--- a/lib/ha_handler/pgsql/supervisor.ex
+++ b/lib/ha_handler/pgsql/supervisor.ex
@@ -1,22 +1,23 @@
defmodule HAHandler.PGSQL.Supervisor do
- use Supervisor
+ use Supervisor
- alias HAHandler.PGSQL.Watcher, as: PGSQLWatcher
+ alias HAHandler.PGSQL.Watcher, as: PGSQLWatcher
- def start_link(opts) do
- Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
- end
+ def start_link(opts) do
+ Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
- @impl true
- def init(instances) do
- children = Enum.map(instances, fn conf ->
- %{
- id: Keyword.get(conf, :hostname),
- start: {PGSQLWatcher, :start_link, [conf]}
- }
- end)
+ @impl true
+ def init(instances) do
+ children =
+ Enum.map(instances, fn conf ->
+ %{
+ id: Keyword.get(conf, :hostname),
+ start: {PGSQLWatcher, :start_link, [conf]}
+ }
+ end)
- opts = [ strategy: :one_for_one ]
- Supervisor.init(children, opts)
- end
+ opts = [strategy: :one_for_one]
+ Supervisor.init(children, opts)
+ end
end
diff --git a/lib/ha_handler/pgsql/watcher.ex b/lib/ha_handler/pgsql/watcher.ex
index b532851..ef17bd6 100644
--- a/lib/ha_handler/pgsql/watcher.ex
+++ b/lib/ha_handler/pgsql/watcher.ex
@@ -1,29 +1,29 @@
defmodule HAHandler.PGSQL.Watcher do
- use GenServer
- require Logger
+ use GenServer
+ require Logger
- def start_link(opts) do
- GenServer.start_link(__MODULE__, opts)
- end
+ def start_link(opts) do
+ GenServer.start_link(__MODULE__, opts)
+ end
- @impl true
- def init(opts) do
- # Starts a Postgrex child but does not means the connection was
- # successful.
- # TODO: set dbconnections backoff and connect hooks
- # See https://github.com/elixir-ecto/db_connection/blob/master/lib/db_connection.ex#L343
- {:ok, pid} = Postgrex.start_link(opts)
+ @impl true
+ def init(opts) do
+ # Starts a Postgrex child but does not means the connection was
+ # successful.
+ # TODO: set dbconnections backoff and connect hooks
+ # See https://github.com/elixir-ecto/db_connection/blob/master/lib/db_connection.ex#L343
+ {:ok, pid} = Postgrex.start_link(opts)
- state = %{
- backend: pid,
- hostname: Keyword.get(opts, :hostname)
- }
+ state = %{
+ backend: pid,
+ hostname: Keyword.get(opts, :hostname)
+ }
- {:ok, state}
- end
+ {:ok, state}
+ end
- @impl true
- def handle_call({:execute, query, params}, _from, %{backend: backend} = state) do
- {:reply, Postgrex.query(backend, query, params), state}
- end
+ @impl true
+ def handle_call({:execute, query, params}, _from, %{backend: backend} = state) do
+ {:reply, Postgrex.query(backend, query, params), state}
+ end
end
diff --git a/lib/ha_handler/web/controller.ex b/lib/ha_handler/web/controller.ex
index 68bcf1c..48756e8 100644
--- a/lib/ha_handler/web/controller.ex
+++ b/lib/ha_handler/web/controller.ex
@@ -1,35 +1,34 @@
defmodule HAHandler.Web.Controller do
- import Plug.Conn
+ import Plug.Conn
- alias HAHandler.{HAProxy, PGSQL}
+ alias HAHandler.{HAProxy, PGSQL}
@template_dir "lib/ha_handler/web/templates"
- @index_template EEx.compile_file(
- Path.join(@template_dir, "index.html.eex")
- )
+ @index_template EEx.compile_file(Path.join(@template_dir, "index.html.eex"))
- defp render(conn, template, assigns) do
- {body, _binding} = Code.eval_quoted(template, assigns)
+ defp render(conn, template, assigns) do
+ {body, _binding} = Code.eval_quoted(template, assigns)
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, body)
- end
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, body)
+ end
- def index(conn) do
- {:ok, hostname} = :net_adm.dns_hostname(:net_adm.localhost)
+ def index(conn) do
+ {:ok, hostname} = :net_adm.dns_hostname(:net_adm.localhost())
- haproxy_stats = HAProxy.get_stats([hide_error: true])
- pgsql_stats = PGSQL.get_stats()
+ haproxy_stats = HAProxy.get_stats(hide_error: true)
+ pgsql_stats = PGSQL.get_stats()
- assigns = [
- haproxy_stats: haproxy_stats,
- pgsql_status: pgsql_stats,
- hostname: hostname,
- otp_app: HAHandler.otp_app(),
- version: HAHandler.version(),
- env: HAHandler.env()
- ]
- render(conn, @index_template, assigns)
- end
+ assigns = [
+ haproxy_stats: haproxy_stats,
+ pgsql_status: pgsql_stats,
+ hostname: hostname,
+ otp_app: HAHandler.otp_app(),
+ version: HAHandler.version(),
+ env: HAHandler.env()
+ ]
+
+ render(conn, @index_template, assigns)
+ end
end
diff --git a/lib/ha_handler/web/router.ex b/lib/ha_handler/web/router.ex
index 1572c14..d5c2d13 100644
--- a/lib/ha_handler/web/router.ex
+++ b/lib/ha_handler/web/router.ex
@@ -1,35 +1,38 @@
defmodule HAHandler.Web.Router do
- @moduledoc """
- This module dispatch incoming HTTP requests to their
- related logic. Please refer to [1] for details.
+ @moduledoc """
+ This module dispatch incoming HTTP requests to their
+ related logic. Please refer to [1] for details.
- [1] https://hexdocs.pm/plug/Plug.Router.html#content
- """
+ [1] https://hexdocs.pm/plug/Plug.Router.html#content
+ """
- use Plug.Router
+ use Plug.Router
- alias HAHandler.Web.Controller
+ alias HAHandler.Web.Controller
- # Note for plugs: oder is important, as a plug may stop
- # want to stop the pipeline!
+ # Note for plugs: oder is important, as a plug may stop
+ # want to stop the pipeline!
- plug Plug.Logger, log: :debug
+ plug(Plug.Logger, log: :debug)
- # We use replug to allow for runtime configuration in release (as macros such
- # as the `plug` call ae evaluated are compile-time).
- plug Replug,
- plug: Plug.Static,
- opts: {HAHandler, :acme_challenges_static_config}
- plug Replug,
- plug: Plug.Static,
- opts: {HAHandler, :assets_static_config}
+ # We use replug to allow for runtime configuration in release (as macros such
+ # as the `plug` call ae evaluated are compile-time).
+ plug(Replug,
+ plug: Plug.Static,
+ opts: {HAHandler, :acme_challenges_static_config}
+ )
- plug :match
- plug :dispatch
+ plug(Replug,
+ plug: Plug.Static,
+ opts: {HAHandler, :assets_static_config}
+ )
- get "/", do: Controller.index(conn)
+ plug(:match)
+ plug(:dispatch)
- match _ do
- send_resp(conn, 404, "Not found")
- end
+ get("/", do: Controller.index(conn))
+
+ match _ do
+ send_resp(conn, 404, "Not found")
+ end
end
diff --git a/lib/ha_handler/web/templates/index.html.eex b/lib/ha_handler/web/templates/index.html.eex
index 4d2bbf4..28fcb32 100644
--- a/lib/ha_handler/web/templates/index.html.eex
+++ b/lib/ha_handler/web/templates/index.html.eex
@@ -1,124 +1,124 @@
-
- [HA] <%= hostname %>
-
-
-
-
-
-
-
+
+ [HA] <%= hostname %>
+
+
+
+
+
+
+
-
Recycled Cloud HA handler
+
Recycled Cloud HA handler
-
- This service supervises the various components of
- the Recycled Cloud's High Availability
- infrastruture. Documentation and source code can be
- found on our
- software forge.
-
+
+ This service supervises the various components of
+ the Recycled Cloud's High Availability
+ infrastruture. Documentation and source code can be
+ found on our
+ software forge.
+
-
+
-
Handler
+
Handler
- <%= otp_app %> v<%= version %> (<%= env %>) running on <%= hostname %>
+ <%= otp_app %> v<%= version %> (<%= env %>) running on <%= hostname %>
-
+
-
HAProxy
+
HAProxy
-
Frontends
-
-
-
-
Name
-
Status
-
Bytes in
-
Bytes out
-
-
-
- <%= for entry <- Map.get(haproxy_stats, "Frontend") do %>
-
-
<%= entry["pxname"] %>
-
<%= entry["status"] %>
-
<%= entry["bin"] %>
-
<%= entry["bout"] %>
-
- <% end %>
-
-
+
Frontends
+
+
+
+
Name
+
Status
+
Bytes in
+
Bytes out
+
+
+
+ <%= for entry <- Map.get(haproxy_stats, "Frontend") do %>
+
+
<%= entry["pxname"] %>
+
<%= entry["status"] %>
+
<%= entry["bin"] %>
+
<%= entry["bout"] %>
+
+ <% end %>
+
+
-
Backends
-
-
-
-
Name
-
Status
-
algo
-
-
-
- <%= for entry <- Map.get(haproxy_stats, "Backend") do %>
-
-
<%= entry["pxname"] %>
-
<%= entry["status"] %>
-
<%= entry["algo"] %>
-
- <% end %>
-
-
+
Backends
+
+
+
+
Name
+
Status
+
algo
+
+
+
+ <%= for entry <- Map.get(haproxy_stats, "Backend") do %>
+
+
<%= entry["pxname"] %>
+
<%= entry["status"] %>
+
<%= entry["algo"] %>
+
+ <% end %>
+
+
-
Servers
-
-
-
-
Name
-
Status
-
Mode
-
Address
-
-
-
- <%= for entry <- Map.get(haproxy_stats, "Server") do %>
-
-
<%= entry["pxname"] %>/<%= entry["svname"] %>
-
<%= entry["status"] %>
-
<%= entry["mode"] %>
-
<%= entry["addr"] %>
-
- <% end %>
-
-
+
Servers
+
+
+
+
Name
+
Status
+
Mode
+
Address
+
+
+
+ <%= for entry <- Map.get(haproxy_stats, "Server") do %>
+