make health somewhat work
This commit is contained in:
parent
b4cd5642ed
commit
e0850c1d2b
18 changed files with 327 additions and 589 deletions
|
@ -13,10 +13,10 @@ authorization: {
|
||||||
password: demo_agent_password
|
password: demo_agent_password
|
||||||
permissions: {
|
permissions: {
|
||||||
publish: [
|
publish: [
|
||||||
"agents.v1.demo_agent.>"
|
"agents.v1.1.>"
|
||||||
]
|
]
|
||||||
subscribe: [
|
subscribe: [
|
||||||
"agents.v1.demo_agent.>"
|
"agents.v1.1.>"
|
||||||
"_INBOX_demo_agent.>"
|
"_INBOX_demo_agent.>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> anyhow::Result<()> {
|
async fn run() -> anyhow::Result<()> {
|
||||||
let client = messaging::Client::connect("demo_agent").await?;
|
// TODO: Configurable client
|
||||||
|
let client = messaging::Client::connect("1").await?;
|
||||||
|
|
||||||
init_health_subsystem(client.clone());
|
init_health_subsystem(client.clone());
|
||||||
tracing::info!("initialized health subsystem");
|
tracing::info!("initialized health subsystem");
|
||||||
|
|
|
@ -1,145 +1,21 @@
|
||||||
defmodule Prymn.Agents do
|
defmodule Prymn.Agents do
|
||||||
@moduledoc ~S"""
|
alias Prymn.Agents
|
||||||
Prymn Agents are programs that manage a remote client machine. Prymn backend
|
|
||||||
communicates with them using GRPC calls. GRPC connections are started using
|
|
||||||
the Prymn.Agents.ConnectionSupervisor and are book-kept using the
|
|
||||||
Prymn.Agents.Registry.
|
|
||||||
|
|
||||||
Agents are only valid when a `Prymn.Servers.Server` is considered registered.
|
def from_server(%Prymn.Servers.Server{} = server) do
|
||||||
|
agent_id = to_string(server.id)
|
||||||
"""
|
Phoenix.PubSub.subscribe(Prymn.PubSub, "agent:#{agent_id}")
|
||||||
|
Agents.Store.get_or_default(agent_id)
|
||||||
require Logger
|
|
||||||
alias Prymn.Agents.{Connection, Health, Agent}
|
|
||||||
alias PrymnProto.Prymn.Agent.Stub
|
|
||||||
alias PrymnProto.Prymn.{ExecRequest, SysUpdateRequest}
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Establish a connection with a Server if one does not already exist, and
|
|
||||||
return an Agent that interfaces with the rest of the system.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Prymn.Servers.get_server_by_ip!("127.0.0.1") |> Prymn.Agents.from_server()
|
|
||||||
%Prymn.Agents.Agent{}
|
|
||||||
"""
|
|
||||||
def from_server(%Prymn.Servers.Server{status: :registered} = server) do
|
|
||||||
case start_connection(server.public_ip) do
|
|
||||||
{:ok, _pid} -> Agent.new(server.public_ip)
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_server(%Prymn.Servers.Server{}) do
|
def from_servers(servers) when is_list(servers) do
|
||||||
Logger.error("Tried to establish a connection with an unregistered server.")
|
Enum.reduce(servers, [], fn server, acc ->
|
||||||
{:error, :unauthorized_action}
|
[{server.id, from_server(server)} | acc]
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
def update_health(agent_id, health) when is_binary(agent_id) do
|
||||||
Establish a connection with a Server if one does not already exist for a
|
agent = Agents.Store.update_health(agent_id, health)
|
||||||
given App. Returns an [Agent] that interfaces with the rest of the system.
|
Phoenix.PubSub.broadcast!(Prymn.PubSub, "agent:#{agent_id}", agent)
|
||||||
"""
|
|
||||||
def from_app(%Prymn.Apps.App{} = app) do
|
|
||||||
app = Prymn.Repo.preload(app, :server)
|
|
||||||
from_server(app.server)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Starts a new connection with `host_address` if one does not exist.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Prymn.Agents.start_connection("127.0.0.1")
|
|
||||||
{:ok, <PID:1234>}
|
|
||||||
iex> Prymn.Agents.start_connection("127.0.0.1")
|
|
||||||
{:ok, <PID:1234>}
|
|
||||||
"""
|
|
||||||
def start_connection(host_address) do
|
|
||||||
spec = {Connection, host_address}
|
|
||||||
|
|
||||||
case DynamicSupervisor.start_child(Prymn.Agents.ConnectionSupervisor, spec) do
|
|
||||||
{:ok, pid} -> {:ok, pid}
|
|
||||||
{:error, {:already_started, pid}} -> {:ok, pid}
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Subscribe to the host's Health using Phoenix.PubSub.
|
|
||||||
Broadcasted messages are the Health struct:
|
|
||||||
|
|
||||||
%Prymn.Agents.Health{}
|
|
||||||
"""
|
|
||||||
def subscribe_to_health(%Agent{} = agent) do
|
|
||||||
:ok = Health.subscribe(agent.host_address)
|
|
||||||
agent
|
|
||||||
end
|
|
||||||
|
|
||||||
def subscribe_to_health(host_address) do
|
|
||||||
:ok = Health.subscribe(host_address)
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# def alive?(host_address) do
|
|
||||||
# end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Return the last known health status of the Agent, or `nil` if it doesn't
|
|
||||||
exist.
|
|
||||||
"""
|
|
||||||
def get_health(host_address) do
|
|
||||||
Health.lookup(host_address)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get the system's information (CPU, Memory usage, etc.).
|
|
||||||
"""
|
|
||||||
def get_sys_info(%Agent{} = agent) do
|
|
||||||
{:error, :unimplemented}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Run a command.
|
|
||||||
"""
|
|
||||||
def exec(%Agent{} = agent, %ExecRequest{} = request) do
|
|
||||||
with {:ok, channel} <- get_channel(agent),
|
|
||||||
{:ok, result} <- Stub.exec(channel, request) do
|
|
||||||
result
|
|
||||||
else
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def exec(%Agent{} = agent, request) when is_map(request),
|
|
||||||
do: exec(agent, struct(ExecRequest, request))
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Perform a system update.
|
|
||||||
"""
|
|
||||||
def sys_update(%Agent{} = agent, %SysUpdateRequest{} = request) do
|
|
||||||
with {:ok, channel} <- get_channel(agent),
|
|
||||||
{:ok, result} <- Stub.sys_update(channel, request) do
|
|
||||||
result
|
|
||||||
else
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sys_update(%Agent{} = agent, request) when is_map(request),
|
|
||||||
do: sys_update(agent, struct(SysUpdateRequest, request))
|
|
||||||
|
|
||||||
def terminal(%Agent{} = agent) do
|
|
||||||
# TODO: Find a better solve for bi-directional GRPC stream
|
|
||||||
with {:ok, channel} <- get_channel(agent),
|
|
||||||
stream <- Stub.terminal(channel) do
|
|
||||||
stream
|
|
||||||
else
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_channel(%Agent{} = agent) do
|
|
||||||
case start_connection(agent.host_address) do
|
|
||||||
{:ok, pid} -> {:ok, Connection.get_channel(pid)}
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
defmodule Prymn.Agents.Agent do
|
defmodule Prymn.Agents.Agent do
|
||||||
@moduledoc false
|
defstruct [:id, :health, status: :disconnected]
|
||||||
|
|
||||||
defstruct [:host_address]
|
def new(id) do
|
||||||
|
%__MODULE__{id: id}
|
||||||
@type t :: %__MODULE__{
|
|
||||||
host_address: String.t()
|
|
||||||
}
|
|
||||||
|
|
||||||
def new(host_address) when is_binary(host_address) do
|
|
||||||
%__MODULE__{host_address: host_address}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
defmodule Prymn.Agents.Connection do
|
|
||||||
@moduledoc false
|
|
||||||
use GenServer, restart: :transient
|
|
||||||
|
|
||||||
def start_link(host_address) do
|
|
||||||
GenServer.start_link(__MODULE__, host_address, name: via(host_address))
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_channel(server) when is_pid(server) do
|
|
||||||
GenServer.call(server, :get_channel)
|
|
||||||
end
|
|
||||||
|
|
||||||
##
|
|
||||||
## Server callbacks
|
|
||||||
##
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def init(host) do
|
|
||||||
{:ok, {host, nil}}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp via(name) do
|
|
||||||
{:via, Registry, {Prymn.Agents.Registry, name}}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -5,104 +5,5 @@ defmodule Prymn.Agents.Health do
|
||||||
getting depleted, or if it's unable be reached.
|
getting depleted, or if it's unable be reached.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defstruct [:host, :version, :status, tasks: [], message: "Unknown"]
|
# defstruct status: :disconnected
|
||||||
|
|
||||||
alias PrymnProto.Prymn.HealthResponse
|
|
||||||
|
|
||||||
@type t :: %{
|
|
||||||
host: String.t(),
|
|
||||||
version: String.t(),
|
|
||||||
status: atom(),
|
|
||||||
message: String.t()
|
|
||||||
}
|
|
||||||
|
|
||||||
def start() do
|
|
||||||
:ets.new(__MODULE__, [:set, :public, :named_table, read_concurrency: true])
|
|
||||||
end
|
|
||||||
|
|
||||||
def subscribe(host) do
|
|
||||||
Phoenix.PubSub.subscribe(Prymn.PubSub, "health:#{host}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def broadcast!(%__MODULE__{host: host} = health) do
|
|
||||||
Phoenix.PubSub.broadcast!(Prymn.PubSub, "health:#{host}", health)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_and_broadcast(nil) do
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_and_broadcast(%__MODULE__{host: host} = health) do
|
|
||||||
:ets.insert(__MODULE__, {host, health})
|
|
||||||
broadcast!(health)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(host_address) do
|
|
||||||
:ets.delete(__MODULE__, host_address)
|
|
||||||
end
|
|
||||||
|
|
||||||
def lookup(host_address, opts \\ []) do
|
|
||||||
default = Keyword.get(opts, :default, false)
|
|
||||||
|
|
||||||
case :ets.lookup(__MODULE__, host_address) do
|
|
||||||
[{^host_address, value}] -> value
|
|
||||||
[] when default -> %__MODULE__{host: host_address}
|
|
||||||
[] -> nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def new(agent_id, %{"cpu_status" => cpu, "memory_status" => memory, "disk_status" => disk}) do
|
|
||||||
%__MODULE__{host: agent_id, version: "0.1.0"}
|
|
||||||
|> do_cpu(cpu)
|
|
||||||
|> do_memory(memory)
|
|
||||||
|> do_disks(disk)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_cpu(health, cpu) do
|
|
||||||
%__MODULE__{health | message: "Connected", status: :connected}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_memory(health, memory) do
|
|
||||||
health
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_disks(health, disks) do
|
|
||||||
health
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_timed_out(%__MODULE__{} = health) do
|
|
||||||
%__MODULE__{health | status: :unreachable, message: "Connect timed out"}
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_disconnected(%__MODULE__{} = health) do
|
|
||||||
%__MODULE__{health | status: :disconnected, message: "Disconnected"}
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_from_proto(%HealthResponse{system: system, version: version, tasks: tasks}, host) do
|
|
||||||
%__MODULE__{host: host, status: :connected}
|
|
||||||
|> do_version(version)
|
|
||||||
|> do_system(system)
|
|
||||||
|> do_tasks(tasks)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_version(health, version) do
|
|
||||||
%__MODULE__{health | version: version}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_system(health, system) do
|
|
||||||
case system.status do
|
|
||||||
"normal" -> %__MODULE__{health | message: "Connected"}
|
|
||||||
status -> %__MODULE__{health | message: "Alert: #{status}"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_tasks(health, tasks) do
|
|
||||||
tasks =
|
|
||||||
Enum.map(tasks, fn {task_key, task_value} ->
|
|
||||||
progress = Float.round(task_value.progress, 2)
|
|
||||||
{task_key, %{task_value | progress: "#{progress}%"}}
|
|
||||||
end)
|
|
||||||
|
|
||||||
%__MODULE__{health | tasks: tasks}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
44
app/lib/prymn/agents/store.ex
Normal file
44
app/lib/prymn/agents/store.ex
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule Prymn.Agents.Store do
|
||||||
|
@moduledoc false
|
||||||
|
use Agent
|
||||||
|
|
||||||
|
alias Prymn.Agents
|
||||||
|
|
||||||
|
# Stores and serves locally saved "Prymn Agents" to the application
|
||||||
|
# Not to be confused with the "Prymn Server" construct
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def start_link([]) do
|
||||||
|
Agent.start_link(fn -> %{} end, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(id) do
|
||||||
|
Agent.get(__MODULE__, fn agents -> agents[id] end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_or_default(id, new_agent \\ nil) do
|
||||||
|
case Agent.get(__MODULE__, fn agents -> agents[id] end) do
|
||||||
|
nil ->
|
||||||
|
agent = new_agent || Agents.Agent.new(id)
|
||||||
|
Agent.update(__MODULE__, &Map.put(&1, id, agent))
|
||||||
|
agent
|
||||||
|
|
||||||
|
agent ->
|
||||||
|
agent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_health(id, health) do
|
||||||
|
case get(id) do
|
||||||
|
nil ->
|
||||||
|
agent = %Agents.Agent{Agents.Agent.new(id) | status: :connected, health: health}
|
||||||
|
Agent.update(__MODULE__, &Map.put(&1, id, agent))
|
||||||
|
agent
|
||||||
|
|
||||||
|
agent ->
|
||||||
|
agent = %Agents.Agent{agent | status: :connected, health: health}
|
||||||
|
Agent.update(__MODULE__, &Map.put(&1, id, agent))
|
||||||
|
agent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,27 +0,0 @@
|
||||||
defmodule Prymn.Agents.Supervisor do
|
|
||||||
@moduledoc false
|
|
||||||
|
|
||||||
use Supervisor
|
|
||||||
|
|
||||||
@dynamic_supervisor Prymn.Agents.ConnectionSupervisor
|
|
||||||
|
|
||||||
def start_link(init_arg) do
|
|
||||||
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def init(_init_arg) do
|
|
||||||
children = [
|
|
||||||
# The registry will be used to register `Connection` processes with their
|
|
||||||
# name as their address
|
|
||||||
{Registry, keys: :unique, name: Prymn.Agents.Registry},
|
|
||||||
# Dynamically start `Connection` processes
|
|
||||||
{DynamicSupervisor, name: @dynamic_supervisor, strategy: :one_for_one, max_seconds: 60}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Register a "health" table that stores in-memory any agent health data
|
|
||||||
Prymn.Agents.Health.start()
|
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -14,7 +14,7 @@ defmodule Prymn.Application do
|
||||||
{Phoenix.PubSub, name: Prymn.PubSub},
|
{Phoenix.PubSub, name: Prymn.PubSub},
|
||||||
{Finch, name: Prymn.Finch},
|
{Finch, name: Prymn.Finch},
|
||||||
{Oban, Application.fetch_env!(:prymn, Oban)},
|
{Oban, Application.fetch_env!(:prymn, Oban)},
|
||||||
Prymn.Agents.Supervisor,
|
Prymn.Agents.Store,
|
||||||
Prymn.Messaging.ConnectionSupervisor,
|
Prymn.Messaging.ConnectionSupervisor,
|
||||||
{Task.Supervisor, name: Prymn.TaskSupervisor},
|
{Task.Supervisor, name: Prymn.TaskSupervisor},
|
||||||
PrymnWeb.Endpoint
|
PrymnWeb.Endpoint
|
||||||
|
|
|
@ -4,7 +4,6 @@ defmodule Prymn.Apps.Wordpress do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Prymn.Apps.App
|
alias Prymn.Apps.App
|
||||||
alias Prymn.Agents
|
alias Prymn.Agents
|
||||||
alias PrymnProto.Prymn.{ExecRequest, ExecResponse}
|
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
|
@ -50,150 +49,150 @@ defmodule Prymn.Apps.Wordpress do
|
||||||
# })
|
# })
|
||||||
# |> Health.update_and_broadcast()
|
# |> Health.update_and_broadcast()
|
||||||
|
|
||||||
def deploy(%App{type: "wordpress"} = app, agent, notify_fun) do
|
def deploy(%App{type: "wordpress"} = _app, _agent, _notify_fun) do
|
||||||
# TODO: Run sanity checks
|
# TODO: Run sanity checks
|
||||||
# e.g Database does not exist, domain does not exist, etc.
|
# e.g Database does not exist, domain does not exist, etc.
|
||||||
deploy(app, agent, notify_fun, 1)
|
# deploy(app, agent, notify_fun, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 1 = step) do
|
# defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 1 = step) do
|
||||||
# TODO: We need a mechanism to wait for the agent to connect before proceeding,
|
# # TODO: We need a mechanism to wait for the agent to connect before proceeding,
|
||||||
# this is executed faster than the connection (which happens on deploy_app in Worker)
|
# # this is executed faster than the connection (which happens on deploy_app in Worker)
|
||||||
|
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "root",
|
# user: "root",
|
||||||
program: "mysql",
|
# program: "mysql",
|
||||||
args: [
|
# args: [
|
||||||
"-e",
|
# "-e",
|
||||||
# TODO: Sanitize the string to protect from injection
|
# # TODO: Sanitize the string to protect from injection
|
||||||
"create user '#{wp.db_user}'@'#{wp.db_host}' identified by '#{wp.db_pass}';"
|
# "create user '#{wp.db_user}'@'#{wp.db_host}' identified by '#{wp.db_pass}';"
|
||||||
]
|
# ]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, 1 / 5 * 100)
|
# notify_fun.(:error, data, 1 / 5 * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:progress, data, step / @max_steps * 100)
|
# notify_fun.(:progress, data, step / @max_steps * 100)
|
||||||
deploy(app, agent, notify_fun, step + 1)
|
# deploy(app, agent, notify_fun, step + 1)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 2 = step) do
|
# defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 2 = step) do
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "root",
|
# user: "root",
|
||||||
program: "mysql",
|
# program: "mysql",
|
||||||
args: [
|
# args: [
|
||||||
"-e",
|
# "-e",
|
||||||
# TODO: Sanitize the string to protect from injection
|
# # TODO: Sanitize the string to protect from injection
|
||||||
"grant all privileges on #{wp.db_name}.* to '#{wp.db_user}'@'#{wp.db_host}';"
|
# "grant all privileges on #{wp.db_name}.* to '#{wp.db_user}'@'#{wp.db_host}';"
|
||||||
]
|
# ]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, step / @max_steps * 100)
|
# notify_fun.(:error, data, step / @max_steps * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:progress, data, step / @max_steps * 100)
|
# notify_fun.(:progress, data, step / @max_steps * 100)
|
||||||
deploy(app, agent, notify_fun, step + 1)
|
# deploy(app, agent, notify_fun, step + 1)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 3 = step) do
|
# defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 3 = step) do
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "vagrant",
|
# user: "vagrant",
|
||||||
program: "wp",
|
# program: "wp",
|
||||||
args: ["core", "download", "--path=#{wp.path}"]
|
# args: ["core", "download", "--path=#{wp.path}"]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, step / @max_steps * 100)
|
# notify_fun.(:error, data, step / @max_steps * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:progress, data, step / @max_steps * 100)
|
# notify_fun.(:progress, data, step / @max_steps * 100)
|
||||||
deploy(app, agent, notify_fun, step + 1)
|
# deploy(app, agent, notify_fun, step + 1)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 4 = step) do
|
# defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 4 = step) do
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "vagrant",
|
# user: "vagrant",
|
||||||
program: "wp",
|
# program: "wp",
|
||||||
args: [
|
# args: [
|
||||||
"config",
|
# "config",
|
||||||
"create",
|
# "create",
|
||||||
"--path=#{wp.path}",
|
# "--path=#{wp.path}",
|
||||||
"--dbhost=#{wp.db_host}",
|
# "--dbhost=#{wp.db_host}",
|
||||||
"--dbname=#{wp.db_name}",
|
# "--dbname=#{wp.db_name}",
|
||||||
"--dbuser=#{wp.db_user}",
|
# "--dbuser=#{wp.db_user}",
|
||||||
"--dbpass=#{wp.db_pass}"
|
# "--dbpass=#{wp.db_pass}"
|
||||||
]
|
# ]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, step / @max_steps * 100)
|
# notify_fun.(:error, data, step / @max_steps * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:progress, data, step / @max_steps * 100)
|
# notify_fun.(:progress, data, step / @max_steps * 100)
|
||||||
deploy(app, agent, notify_fun, step + 1)
|
# deploy(app, agent, notify_fun, step + 1)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 5 = step) do
|
# defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 5 = step) do
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "vagrant",
|
# user: "vagrant",
|
||||||
program: "wp",
|
# program: "wp",
|
||||||
args: ["db", "create", "--path=#{wp.path}"]
|
# args: ["db", "create", "--path=#{wp.path}"]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, step / @max_steps * 100)
|
# notify_fun.(:error, data, step / @max_steps * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:progress, data, step / @max_steps * 100)
|
# notify_fun.(:progress, data, step / @max_steps * 100)
|
||||||
deploy(app, agent, notify_fun, step + 1)
|
# deploy(app, agent, notify_fun, step + 1)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp deploy(%App{name: name, wordpress: %__MODULE__{} = wp}, agent, notify_fun, 6 = step) do
|
# defp deploy(%App{name: name, wordpress: %__MODULE__{} = wp}, agent, notify_fun, 6 = step) do
|
||||||
Agents.exec(agent, %ExecRequest{
|
# Agents.exec(agent, %ExecRequest{
|
||||||
user: "vagrant",
|
# user: "vagrant",
|
||||||
program: "wp",
|
# program: "wp",
|
||||||
args: [
|
# args: [
|
||||||
"core",
|
# "core",
|
||||||
"install",
|
# "install",
|
||||||
"--path=#{wp.path}",
|
# "--path=#{wp.path}",
|
||||||
"--url=http://site.test",
|
# "--url=http://site.test",
|
||||||
"--title=#{name}",
|
# "--title=#{name}",
|
||||||
"--admin_user=#{wp.admin_username}",
|
# "--admin_user=#{wp.admin_username}",
|
||||||
"--admin_email=#{wp.admin_email}"
|
# "--admin_email=#{wp.admin_email}"
|
||||||
]
|
# ]
|
||||||
})
|
# })
|
||||||
|> then(&get_results/1)
|
# |> then(&get_results/1)
|
||||||
|> case do
|
# |> case do
|
||||||
{_, _, exit_code} = data when exit_code != 0 ->
|
# {_, _, exit_code} = data when exit_code != 0 ->
|
||||||
notify_fun.(:error, data, step / @max_steps * 100)
|
# notify_fun.(:error, data, step / @max_steps * 100)
|
||||||
|
|
||||||
data ->
|
# data ->
|
||||||
notify_fun.(:complete, data, step / @max_steps * 100)
|
# notify_fun.(:complete, data, step / @max_steps * 100)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp get_results(stream) do
|
# defp get_results(stream) do
|
||||||
Enum.reduce_while(stream, {"", "", nil}, fn
|
# Enum.reduce_while(stream, {"", "", nil}, fn
|
||||||
{:ok, %ExecResponse{out: {:exit_code, exit_code}}}, {stdout, stderr, _} ->
|
# {:ok, %ExecResponse{out: {:exit_code, exit_code}}}, {stdout, stderr, _} ->
|
||||||
{:halt, {stdout, stderr, exit_code}}
|
# {:halt, {stdout, stderr, exit_code}}
|
||||||
|
|
||||||
{:ok, %ExecResponse{out: {:stdout, stdout}}}, {acc_stdout, stderr, exit_code} ->
|
# {:ok, %ExecResponse{out: {:stdout, stdout}}}, {acc_stdout, stderr, exit_code} ->
|
||||||
{:cont, {acc_stdout <> stdout, stderr, exit_code}}
|
# {:cont, {acc_stdout <> stdout, stderr, exit_code}}
|
||||||
|
|
||||||
{:ok, %ExecResponse{out: {:stderr, stderr}}}, {stdout, acc_stderr, exit_code} ->
|
# {:ok, %ExecResponse{out: {:stderr, stderr}}}, {stdout, acc_stderr, exit_code} ->
|
||||||
{:cont, {stdout, acc_stderr <> stderr, exit_code}}
|
# {:cont, {stdout, acc_stderr <> stderr, exit_code}}
|
||||||
end)
|
# end)
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
defmodule Prymn.Messaging.Connection do
|
defmodule Prymn.Messaging.Connection do
|
||||||
alias Prymn.Agents.Health
|
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
defstruct [:conn_pid]
|
defstruct [:conn_pid]
|
||||||
require Logger
|
require Logger
|
||||||
|
alias Prymn.Agents
|
||||||
|
alias Prymn.Messaging
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, init: 1}
|
@dialyzer {:nowarn_function, init: 1}
|
||||||
@v1_prefix "agents.v1."
|
@v1_prefix "agents.v1."
|
||||||
|
@ -18,7 +19,6 @@ defmodule Prymn.Messaging.Connection do
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
username: "prymn_admin",
|
username: "prymn_admin",
|
||||||
password: "prymn_admin",
|
password: "prymn_admin",
|
||||||
name: "Prymn Control",
|
|
||||||
auth_required: true
|
auth_required: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +53,9 @@ defmodule Prymn.Messaging.Connection do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:msg, %{body: body, topic: @v1_prefix <> topic}}, state) do
|
def handle_info({:msg, %{body: body, topic: @v1_prefix <> topic}}, state) do
|
||||||
[agent_id, "health"] = String.split(topic, ".")
|
[agent_id, topic] = String.split(topic, ".")
|
||||||
|
health = Messaging.Messages.handle_message(topic, body)
|
||||||
agent_id
|
Agents.update_health(agent_id, health)
|
||||||
|> Health.new(Jason.decode!(body))
|
|
||||||
|> Health.update_and_broadcast()
|
|
||||||
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
15
app/lib/prymn/messaging/messages.ex
Normal file
15
app/lib/prymn/messaging/messages.ex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Prymn.Messaging.Messages do
|
||||||
|
alias Prymn.Messaging.Messages.Health
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def handle_message(topic, body) do
|
||||||
|
case topic do
|
||||||
|
"health" -> Jason.decode!(body)
|
||||||
|
_ -> Logger.warning("Received unknown topic inside the Connection server: #{topic}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Prymn.Messaging.Messages.Health do
|
||||||
|
defstruct [:cpu_status, :memory_status, :disk_status]
|
||||||
|
end
|
|
@ -13,7 +13,7 @@ defmodule Prymn.Worker do
|
||||||
defp deploy_app(app_id) do
|
defp deploy_app(app_id) do
|
||||||
pid = self()
|
pid = self()
|
||||||
app = Apps.get_app!(app_id)
|
app = Apps.get_app!(app_id)
|
||||||
agent = Agents.from_app(app)
|
# agent = Agents.from_app(app)
|
||||||
|
|
||||||
Task.start_link(fn ->
|
Task.start_link(fn ->
|
||||||
notify_fun = fn
|
notify_fun = fn
|
||||||
|
@ -22,7 +22,7 @@ defmodule Prymn.Worker do
|
||||||
:error, data, _progress -> send(pid, {:error, data})
|
:error, data, _progress -> send(pid, {:error, data})
|
||||||
end
|
end
|
||||||
|
|
||||||
Apps.Wordpress.deploy(app, agent, notify_fun)
|
# Apps.Wordpress.deploy(app, agent, notify_fun)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,17 @@ defmodule PrymnWeb.SystemInfo do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
alias Phoenix.LiveView.AsyncResult
|
alias Phoenix.LiveView.AsyncResult
|
||||||
alias PrymnProto.Prymn.SysInfoResponse
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def update(assigns, socket) do
|
def update(assigns, socket) do
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:agent, assigns.agent)
|
|> assign(:agent, assigns.agent)
|
||||||
|> assign(:sys_info, AsyncResult.loading())
|
|> assign(:sys_info, AsyncResult.loading())}
|
||||||
|> start_async(:get_sys_info, fn ->
|
|
||||||
Prymn.Agents.get_sys_info(assigns.agent)
|
# |> start_async(:get_sys_info, fn ->
|
||||||
end)}
|
# Prymn.Agents.get_sys_info(assigns.agent)
|
||||||
|
# end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -60,31 +60,32 @@ defmodule PrymnWeb.SystemInfo do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_async(:get_sys_info, {:ok, %SysInfoResponse{} = sys_info}, socket) do
|
# @impl true
|
||||||
%{sys_info: sys_info_result, agent: agent} = socket.assigns
|
# def handle_async(:get_sys_info, {:ok, %SysInfoResponse{} = sys_info}, socket) do
|
||||||
|
# %{sys_info: sys_info_result, agent: agent} = socket.assigns
|
||||||
|
|
||||||
{:noreply,
|
# {:noreply,
|
||||||
socket
|
# socket
|
||||||
|> assign(:sys_info, AsyncResult.ok(sys_info_result, sys_info))
|
# |> assign(:sys_info, AsyncResult.ok(sys_info_result, sys_info))
|
||||||
|> start_async(:get_sys_info, fn ->
|
# |> start_async(:get_sys_info, fn ->
|
||||||
# 10 seconds is >5 which is gun's timeout duration (which might have a race
|
# # 10 seconds is >5 which is gun's timeout duration (which might have a race
|
||||||
# condition if they are equal)
|
# # condition if they are equal)
|
||||||
Process.sleep(:timer.seconds(10))
|
# Process.sleep(:timer.seconds(10))
|
||||||
Prymn.Agents.get_sys_info(agent)
|
# Prymn.Agents.get_sys_info(agent)
|
||||||
end)}
|
# end)}
|
||||||
end
|
# end
|
||||||
|
|
||||||
def handle_async(:get_sys_info, {:ok, {:error, grpc_error}}, socket) do
|
# def handle_async(:get_sys_info, {:ok, {:error, grpc_error}}, socket) do
|
||||||
%{sys_info: sys_info_result} = socket.assigns
|
# %{sys_info: sys_info_result} = socket.assigns
|
||||||
|
|
||||||
{:noreply,
|
# {:noreply,
|
||||||
socket
|
# socket
|
||||||
|> assign(:sys_info, AsyncResult.failed(sys_info_result, grpc_error))}
|
# |> assign(:sys_info, AsyncResult.failed(sys_info_result, grpc_error))}
|
||||||
end
|
# end
|
||||||
|
|
||||||
def handle_async(:get_sys_info, {:exit, _reason}, socket) do
|
# def handle_async(:get_sys_info, {:exit, _reason}, socket) do
|
||||||
{:noreply, socket}
|
# {:noreply, socket}
|
||||||
end
|
# end
|
||||||
|
|
||||||
defp calculate_cpu_usage(cpus) do
|
defp calculate_cpu_usage(cpus) do
|
||||||
(Enum.reduce(cpus, 0, fn x, acc -> x.usage + acc end) / Enum.count(cpus))
|
(Enum.reduce(cpus, 0, fn x, acc -> x.usage + acc end) / Enum.count(cpus))
|
||||||
|
@ -96,13 +97,14 @@ defmodule PrymnWeb.SystemInfo do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp calculate_disk_used_percent(disks) do
|
defp calculate_disk_used_percent(disks) do
|
||||||
alias PrymnProto.Prymn.SysInfoResponse.Disk
|
0
|
||||||
|
# alias PrymnProto.Prymn.SysInfoResponse.Disk
|
||||||
|
|
||||||
{used, total} =
|
# {used, total} =
|
||||||
Enum.reduce(disks, {0, 0}, fn %Disk{} = disk, {used, total} ->
|
# Enum.reduce(disks, {0, 0}, fn %Disk{} = disk, {used, total} ->
|
||||||
{used + disk.total_bytes - disk.avail_bytes, total + disk.total_bytes}
|
# {used + disk.total_bytes - disk.avail_bytes, total + disk.total_bytes}
|
||||||
end)
|
# end)
|
||||||
|
|
||||||
Float.round(100 * used / total, 2)
|
# Float.round(100 * used / total, 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
defmodule PrymnWeb.Terminal do
|
defmodule PrymnWeb.Terminal do
|
||||||
use PrymnWeb, :live_component
|
use PrymnWeb, :live_component
|
||||||
|
|
||||||
alias PrymnProto.Prymn.TerminalRequest
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(socket) do
|
def mount(socket) do
|
||||||
{:ok, assign(socket, :open, false)}
|
{:ok, assign(socket, :open, false)}
|
||||||
|
@ -47,34 +45,34 @@ defmodule PrymnWeb.Terminal do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("open_terminal", _params, socket) do
|
def handle_event("open_terminal", _params, socket) do
|
||||||
agent = Prymn.Agents.from_server(socket.assigns.server)
|
# agent = Prymn.Agents.from_server(socket.assigns.server)
|
||||||
pid = self()
|
# pid = self()
|
||||||
|
|
||||||
Task.Supervisor.start_child(Prymn.TaskSupervisor, fn ->
|
# Task.Supervisor.start_child(Prymn.TaskSupervisor, fn ->
|
||||||
# FIXME: Have to wrap this in a Task because gun sends unsolicited messages
|
# # FIXME: Have to wrap this in a Task because gun sends unsolicited messages
|
||||||
# to calling process
|
# # to calling process
|
||||||
stream = Prymn.Agents.terminal(agent)
|
# stream = Prymn.Agents.terminal(agent)
|
||||||
|
|
||||||
{:ok, mux_pid} =
|
# {:ok, mux_pid} =
|
||||||
Task.Supervisor.start_child(Prymn.TaskSupervisor, fn -> receive_loop(stream) end)
|
# Task.Supervisor.start_child(Prymn.TaskSupervisor, fn -> receive_loop(stream) end)
|
||||||
|
|
||||||
send_update(pid, PrymnWeb.Terminal, id: "terminal", mux_pid: mux_pid, open: true)
|
# send_update(pid, PrymnWeb.Terminal, id: "terminal", mux_pid: mux_pid, open: true)
|
||||||
|
|
||||||
case GRPC.Stub.recv(stream, timeout: :infinity) do
|
# case GRPC.Stub.recv(stream, timeout: :infinity) do
|
||||||
{:ok, stream} ->
|
# {:ok, stream} ->
|
||||||
Enum.map(stream, fn
|
# Enum.map(stream, fn
|
||||||
{:ok, %{output: data}} ->
|
# {:ok, %{output: data}} ->
|
||||||
send(mux_pid, :data)
|
# send(mux_pid, :data)
|
||||||
send_update(pid, PrymnWeb.Terminal, id: "terminal", data: data)
|
# send_update(pid, PrymnWeb.Terminal, id: "terminal", data: data)
|
||||||
|
|
||||||
{:error, _err} ->
|
# {:error, _err} ->
|
||||||
send_update(pid, PrymnWeb.Terminal, id: "terminal", open: false)
|
# send_update(pid, PrymnWeb.Terminal, id: "terminal", open: false)
|
||||||
end)
|
# end)
|
||||||
|
|
||||||
{:error, error} ->
|
# {:error, error} ->
|
||||||
dbg(error)
|
# dbg(error)
|
||||||
end
|
# end
|
||||||
end)
|
# end)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
@ -94,27 +92,27 @@ defmodule PrymnWeb.Terminal do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp receive_loop(stream) do
|
# defp receive_loop(stream) do
|
||||||
receive do
|
# receive do
|
||||||
{:data_event, data} ->
|
# {:data_event, data} ->
|
||||||
GRPC.Stub.send_request(stream, %TerminalRequest{input: data})
|
# GRPC.Stub.send_request(stream, %TerminalRequest{input: data})
|
||||||
receive_loop(stream)
|
# receive_loop(stream)
|
||||||
|
|
||||||
{:resize_event, rows, cols} ->
|
# {:resize_event, rows, cols} ->
|
||||||
GRPC.Stub.send_request(stream, %TerminalRequest{
|
# GRPC.Stub.send_request(stream, %TerminalRequest{
|
||||||
resize: %TerminalRequest.Resize{rows: rows, cols: cols}
|
# resize: %TerminalRequest.Resize{rows: rows, cols: cols}
|
||||||
})
|
# })
|
||||||
|
|
||||||
receive_loop(stream)
|
# receive_loop(stream)
|
||||||
|
|
||||||
:data ->
|
# :data ->
|
||||||
receive_loop(stream)
|
# receive_loop(stream)
|
||||||
|
|
||||||
:close ->
|
# :close ->
|
||||||
GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
|
# GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
|
||||||
after
|
# after
|
||||||
120_000 ->
|
# 120_000 ->
|
||||||
GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
|
# GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,19 +8,12 @@ defmodule PrymnWeb.ServerLive.Index do
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
servers = Servers.list_servers()
|
servers = Servers.list_servers()
|
||||||
|
agents = Agents.from_servers(servers)
|
||||||
healths =
|
|
||||||
for %Servers.Server{status: :registered, id: id} = server <- servers, into: %{} do
|
|
||||||
# Agents.from_server(server)
|
|
||||||
# |> Agents.subscribe_to_health()
|
|
||||||
|
|
||||||
{id, Agents.get_health(id)}
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:servers, servers)
|
|> assign(:servers, servers)
|
||||||
|> assign(:healths, healths)}
|
|> assign(:agents, agents)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -36,27 +29,14 @@ defmodule PrymnWeb.ServerLive.Index do
|
||||||
<Button.primary patch={~p"/servers/new"}>Connect a Server</Button.primary>
|
<Button.primary patch={~p"/servers/new"}>Connect a Server</Button.primary>
|
||||||
</:actions>
|
</:actions>
|
||||||
</.header>
|
</.header>
|
||||||
<div class="space-y-5" phx-update="replace" id="servers">
|
<div class="mt-10 space-y-5" phx-update="replace" id="servers">
|
||||||
<.link
|
<.link :for={server <- @servers} navigate={~p"/servers/#{server}"} class="group flex">
|
||||||
:for={server <- @servers}
|
<.status_bar agent={@agents[server.id]} />
|
||||||
navigate={~p"/servers/#{server}"}
|
<div class="flex-1 rounded-r-lg bg-gray-100 p-5 transition-colors group-hover:bg-black group-hover:text-white">
|
||||||
class="group block rounded-lg bg-gray-100 p-5 shadow-sm shadow-gray-300 hover:bg-black hover:text-white"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row flex-wrap justify-between">
|
|
||||||
<h2 class="text-xl"><%= server.name %></h2>
|
<h2 class="text-xl"><%= server.name %></h2>
|
||||||
<.server_status status={server.status} health={@healths[server.public_ip]} />
|
<div class="flex flex-row flex-wrap justify-between lg:text-sm">
|
||||||
</div>
|
<span>IP: <%= server.public_ip || "N/A" %></span>
|
||||||
<div class="flex flex-row flex-wrap justify-between lg:text-sm">
|
</div>
|
||||||
<span>IP: <%= server.public_ip || "N/A" %></span>
|
|
||||||
<%= if @healths[server.public_ip] do %>
|
|
||||||
<span
|
|
||||||
:for={{name, task} <- @healths[server.public_ip].tasks || []}
|
|
||||||
class="text-right text-xs text-slate-700"
|
|
||||||
>
|
|
||||||
<div>In progress: <%= name %></div>
|
|
||||||
<div><%= task.progress %></div>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,9 +65,9 @@ defmodule PrymnWeb.ServerLive.Index do
|
||||||
|> update(:servers, fn servers -> [server | servers] end)}
|
|> update(:servers, fn servers -> [server | servers] end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%Agents.Health{} = health, socket) do
|
def handle_info(%Agents.Agent{} = agent, socket) do
|
||||||
healths = Map.put(socket.assigns.healths, health.host, health)
|
id = String.to_integer(agent.id)
|
||||||
{:noreply, assign(socket, :healths, healths)}
|
{:noreply, update(socket, :agents, &Map.put(&1, id, agent))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(msg, state) do
|
def handle_info(msg, state) do
|
||||||
|
@ -95,34 +75,16 @@ defmodule PrymnWeb.ServerLive.Index do
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp server_status(assigns) do
|
defp status_bar(assigns) do
|
||||||
case {assigns.status, assigns.health} do
|
assigns =
|
||||||
{:unregistered, _} ->
|
assign(assigns, :class, [
|
||||||
~H"""
|
"w-3 rounded-l-lg",
|
||||||
<span class="self-center text-sm text-gray-500">Needs registration</span>
|
assigns.agent.status == :connected && "bg-teal-500",
|
||||||
"""
|
assigns.agent.status == :disconnected && "bg-red-500"
|
||||||
|
])
|
||||||
|
|
||||||
{:registered, nil} ->
|
~H"""
|
||||||
~H"""
|
<div class={@class}></div>
|
||||||
<.spinner size="md" />
|
"""
|
||||||
"""
|
|
||||||
|
|
||||||
{:registered, %Agents.Health{status: :connected}} ->
|
|
||||||
~H"""
|
|
||||||
<span class="self-center text-sm text-green-600">Connected</span>
|
|
||||||
"""
|
|
||||||
|
|
||||||
{:registered, %Agents.Health{status: :disconnected}} ->
|
|
||||||
~H"""
|
|
||||||
<span class="self-center text-sm text-red-600">Disconnected</span>
|
|
||||||
"""
|
|
||||||
|
|
||||||
{:registered, %Agents.Health{message: message}} ->
|
|
||||||
assigns = assign(assigns, :message, message)
|
|
||||||
|
|
||||||
~H"""
|
|
||||||
<span class="self-center text-sm text-yellow-900"><%= @message %></span>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -117,12 +117,12 @@ defmodule PrymnWeb.ServerLive.Show do
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
health = Agents.get_health(server.public_ip)
|
# health = Agents.get_health(server.public_ip)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, server.name)
|
|> assign(:page_title, server.name)
|
||||||
|> assign(:health, health || %{message: "Connecting...", tasks: []})
|
# |> assign(:health, health || %{message: "Connecting...", tasks: []})
|
||||||
|> assign(:server, server)
|
|> assign(:server, server)
|
||||||
|> assign(:dry_run, false)
|
|> assign(:dry_run, false)
|
||||||
|> assign(:update_output, [])
|
|> assign(:update_output, [])
|
||||||
|
@ -130,21 +130,21 @@ defmodule PrymnWeb.ServerLive.Show do
|
||||||
|> assign(:registration_command, Servers.create_setup_command(server))}
|
|> assign(:registration_command, Servers.create_setup_command(server))}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
# @impl true
|
||||||
def handle_info(%PrymnProto.Prymn.SysUpdateResponse{} = response, socket) do
|
# def handle_info(%PrymnProto.Prymn.SysUpdateResponse{} = response, socket) do
|
||||||
output = String.split(response.output, "\n")
|
# output = String.split(response.output, "\n")
|
||||||
socket = assign(socket, :update_output, output)
|
# socket = assign(socket, :update_output, output)
|
||||||
{:noreply, socket}
|
# {:noreply, socket}
|
||||||
end
|
# end
|
||||||
|
|
||||||
def handle_info(%Agents.Health{host: host} = health, socket) do
|
# def handle_info(%Agents.Health{host: host} = health, socket) do
|
||||||
socket =
|
# socket =
|
||||||
if host == socket.assigns.server.public_ip,
|
# if host == socket.assigns.server.public_ip,
|
||||||
do: assign(socket, :health, health),
|
# do: assign(socket, :health, health),
|
||||||
else: socket
|
# else: socket
|
||||||
|
|
||||||
{:noreply, socket}
|
# {:noreply, socket}
|
||||||
end
|
# end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("system_update", _params, socket) do
|
def handle_event("system_update", _params, socket) do
|
||||||
|
|
Loading…
Reference in a new issue