dotfiles/app/lib/prymn_web/live/server_live/show.ex

251 lines
8.5 KiB
Elixir
Raw Normal View History

2023-06-09 19:13:27 +00:00
defmodule PrymnWeb.ServerLive.Show do
use PrymnWeb, :live_view
require Logger
alias Prymn.{Agents, Servers}
2023-06-09 19:13:27 +00:00
@impl true
def mount(_params, _session, socket) do
2023-11-23 22:56:38 +00:00
# TODO: A more lightweight call instead of listing all data?
servers = Servers.list_servers()
{:ok, assign(socket, :servers, servers)}
2023-06-09 19:13:27 +00:00
end
2023-11-23 13:45:33 +00:00
@impl true
def render(assigns) do
~H"""
<div class="mx-auto max-w-2xl">
2023-11-25 14:49:51 +00:00
<div class="flex items-center justify-between">
2023-11-23 22:56:38 +00:00
<div id="server-name" class="relative flex items-center">
<.dropdown title="Select a different server">
2023-11-25 14:49:51 +00:00
<:button variant="tertiary">Server <%= @server.name %></:button>
<:item
:for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}
patch={~p"/servers/#{server}"}
>
<%= server.name %>
2023-11-23 22:56:38 +00:00
</:item>
</.dropdown>
2023-11-25 14:49:51 +00:00
<Button.tertiary title="Edit server name" phx-click={show_edit_server_name()}>
2023-11-23 22:56:38 +00:00
<.icon class="h-4 w-4" name="hero-pencil-solid" />
2023-11-25 14:49:51 +00:00
</Button.tertiary>
2023-11-23 22:56:38 +00:00
<.indicator message={@health.message} />
</div>
2023-12-13 15:41:46 +00:00
<form class="hidden items-center" id="server-name-edit" phx-submit={submit_edit_server_name()}>
<input class="outline-none" type="text" name="name" value={@server.name} required />
2023-11-25 14:49:51 +00:00
<Button.tertiary type="submit" title="Confirm">
2023-11-23 22:56:38 +00:00
<.icon name="hero-check" />
2023-11-25 14:49:51 +00:00
</Button.tertiary>
2023-11-23 13:45:33 +00:00
</form>
2023-11-23 22:56:38 +00:00
<div class="space-x-2">
<.dropdown>
2023-11-25 14:49:51 +00:00
<:button variant="tertiary" size="sm">Quick actions</:button>
2023-11-23 22:56:38 +00:00
<:item>
Test
</:item>
</.dropdown>
2023-11-25 14:49:51 +00:00
<Button.primary size="sm">New App</Button.primary>
2023-11-23 22:56:38 +00:00
</div>
</div>
<span class="text-sm opacity-75"><%= @server.public_ip %></span>
<div :for={{name, task} <- @health.tasks} class="my-3 text-sm text-slate-700">
<p>Background task in progress: <%= name %></p>
<p><%= task.progress %> complete</p>
2023-11-23 13:45:33 +00:00
</div>
<div :if={@server.status == :unregistered} class="my-10">
<p class="mb-9">
Connect to your server using root credentials and execute the following command:
</p>
<div class="group inline-flex items-center rounded-lg bg-gray-800 p-4 pl-6 text-white">
<code class="flex gap-4">
<span class="select-none text-gray-500">#</span>
<span class="flex-1">
<%= @registration_command %>
</span>
</code>
<button type="button" tabindex="-1">
<.icon
name="hero-document-duplicate-solid"
class="invisible ml-4 animate-bounce text-gray-500 group-hover:visible"
/>
</button>
</div>
</div>
<div :if={@server.status == :registered} class="my-10">
<.live_component
id={"system_info-#{@server.name}"}
module={PrymnWeb.SystemInfo}
agent={assigns[:agent]}
2023-11-23 13:45:33 +00:00
/>
<section class="mt-4">
<form phx-change="change_dry_run">
<.input type="checkbox" name="dry_run" value={@dry_run} label="Enable dry-run operations" />
</form>
</section>
<section class="mt-4">
<h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">System</h2>
<p class="mt-4">
Updates: <%= 0 %> pending updates.
<Button.primary type="button" class="ml-4" phx-click="system_update">
Update now
</Button.primary>
<p :for={output <- assigns.update_output}>
<%= output %>
</p>
</p>
</section>
<section class="mt-4">
<h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">
Backups
</h2>
<.table id="backups" rows={[%{date: "2023-10-11"}, %{date: "2023-10-10"}]}>
<:col :let={backup} label="Date"><%= backup.date %></:col>
<:action>
<Button.primary>Restore</Button.primary>
</:action>
</.table>
</section>
<section class="mt-4">
<h2 class="border-b border-solid border-gray-500 pb-1 text-2xl font-medium">
Manage Services
</h2>
<.table
id="services"
rows={[%{name: "mariadb", status: "Active"}, %{name: "php8.0", status: "Disabled"}]}
>
<:col :let={service} label="Service"><%= service.name %></:col>
<:col :let={service} label="Status"><%= service.status %></:col>
<:action>
<Button.primary>Activate</Button.primary>
<Button.secondary>Deactivate</Button.secondary>
</:action>
</.table>
</section>
</div>
<.back navigate={~p"/servers"}>Back to servers</.back>
</div>
"""
end
2023-06-09 19:13:27 +00:00
@impl true
def handle_params(%{"id" => id}, _, socket) do
server = Servers.get_server!(id)
socket =
if connected?(socket) and server.status == :registered do
agent = Agents.from_server(server)
Agents.subscribe_to_health(agent)
assign(socket, :agent, agent)
else
socket
end
2023-06-09 19:13:27 +00:00
health = Agents.get_health(server.public_ip)
2023-06-09 19:13:27 +00:00
{:noreply,
socket
|> assign(:page_title, server.name)
|> assign(:health, health || %{message: "Connecting...", tasks: []})
2023-07-09 16:41:41 +00:00
|> assign(:server, server)
|> assign(:dry_run, false)
|> assign(:update_output, [])
# TODO: Do not assign this to the socket - instead generate it in the HTML
2023-07-09 16:41:41 +00:00
|> assign(:registration_command, Servers.create_setup_command(server))}
2023-06-09 19:13:27 +00:00
end
2023-11-20 16:50:08 +00:00
@impl true
def handle_info(%PrymnProto.Prymn.SysUpdateResponse{} = response, socket) do
output = String.split(response.output, "\n")
socket = assign(socket, :update_output, output)
{:noreply, socket}
end
def handle_info(%Agents.Health{host: host} = health, socket) do
socket =
if host == socket.assigns.server.public_ip,
do: assign(socket, :health, health),
else: socket
{:noreply, socket}
end
@impl true
def handle_event("system_update", _params, socket) do
server_name = get_in(socket.assigns, [:server, Access.key(:name)])
pid = self()
if agent = socket.assigns[:agent] do
# TODO: This is ugly
Task.start_link(fn ->
Agents.sys_update(agent, %{dry_run: socket.assigns.dry_run})
|> Stream.each(fn
{:ok, msg} -> send(pid, msg)
{:error, error} -> Logger.error("error during system update call: #{inspect(error)}")
end)
|> Enum.to_list()
end)
put_flash(socket, :info, "Started a system update on server #{server_name}.")
else
put_flash(socket, :error, "Could not perform the update.")
end
{:noreply, socket}
end
2023-11-23 13:45:33 +00:00
def handle_event("edit_server_name", %{"name" => name}, socket) do
server =
socket.assigns.server
|> Servers.update_server(%{"name" => name})
|> case do
{:ok, server} -> server
{:error, _} -> raise "Oops"
end
{:noreply, assign(socket, :server, server)}
end
def handle_event("change_dry_run", %{"dry_run" => enabled}, socket) do
enabled = (enabled == "true" && true) || false
{:noreply, assign(socket, :dry_run, enabled)}
end
2023-11-23 13:45:33 +00:00
defp show_edit_server_name() do
JS.hide(to: "#server-name")
2023-12-13 15:41:46 +00:00
|> JS.show(to: "#server-name-edit", display: "flex")
2023-11-23 13:45:33 +00:00
|> JS.focus_first(to: "#server-name-edit")
end
defp submit_edit_server_name() do
JS.push("edit_server_name")
|> JS.hide()
|> JS.show(to: "#server-name", display: "flex")
end
2023-11-23 22:56:38 +00:00
defp indicator(assigns) do
~H"""
<span
role="tooltip"
class={[
"absolute -left-6 inline-flex h-3 w-3 before:-translate-x-1/2 before:-translate-y-full",
"before:-top-2 before:left-1/2 before:absolute before:text-sm before:text-white",
"before:font-normal before:content-[attr(data-tip)] before:opacity-0",
"hover:before:opacity-100 before:py-1 before:px-2 before:bg-black",
"before:rounded before:pointer-events-none before:transition-opacity"
]}
data-tip={@message}
>
<%= case @message do %>
<% "Connected" -> %>
<span class="absolute top-0 left-0 h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
<span class="h-3 w-3 rounded-full bg-green-500" />
<% "Disconnected" -> %>
<span class="h-3 w-3 rounded-full bg-red-500" />
<% _ -> %>
<span class="h-3 w-3 rounded-full bg-yellow-500" />
<% end %>
</span>
"""
end
2023-06-09 19:13:27 +00:00
end