defmodule PrymnWeb.ServerLive.Show do use PrymnWeb, :live_view require Logger alias Prymn.{Agents, Servers} @impl true def mount(_params, _session, socket) do # TODO: A more lightweight call instead of listing all data? servers = Servers.list_servers() {:ok, assign(socket, :servers, servers)} end @impl true def render(assigns) do ~H""" <div class="mx-auto max-w-2xl"> <div class="flex justify-between"> <div id="server-name" class="relative flex items-center"> <.dropdown title="Select a different server"> <:button>Server <%= @server.name %></:button> <:item :for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}> <.link patch={~p"/servers/#{server}"} class="block text-sm text-gray-700" role="menuitem" > <%= server.name %> </.link> </:item> </.dropdown> <button class="ml-4 inline-flex items-center" title="Edit server name" phx-click={show_edit_server_name()} > <.icon class="h-4 w-4" name="hero-pencil-solid" /> </button> <.indicator message={@health.message} /> </div> <form class="hidden" id="server-name-edit" phx-submit={submit_edit_server_name()}> <input class="border-0 border-b border-gray-500" type="text" name="name" value={@server.name} required /> <button type="submit" title="Confirm"> <.icon name="hero-check" /> </button> </form> <div class="space-x-2"> <Button.primary size="sm">New App</Button.primary> <.dropdown> <:button> Quick actions </:button> <:item> Test </:item> </.dropdown> </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> </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} ip={@server.public_ip} /> <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 @impl true def handle_params(%{"id" => id}, _, socket) do server = Servers.get_server!(id) if connected?(socket) and server.status == :registered do Agents.subscribe_to_health(server.public_ip) Agents.start_connection(server.public_ip) end health = Agents.get_health(server.public_ip) {:noreply, socket |> assign(:page_title, server.name) |> assign(:health, health || %{message: "Connecting...", tasks: []}) |> assign(:server, server) |> assign(:dry_run, false) |> assign(:update_output, []) # TODO: Do not assign this to the socket - instead generate it in the HTML |> assign(:registration_command, Servers.create_setup_command(server))} end @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{} = health, socket) do {:noreply, assign(socket, :health, health)} end @impl true def handle_event("system_update", _params, socket) do host_address = get_in(socket.assigns, [:server, Access.key(:public_ip)]) server_name = get_in(socket.assigns, [:server, Access.key(:name)]) socket = if host_address do Agents.sys_update(host_address, socket.assigns.dry_run) put_flash(socket, :info, "Started a system update on server #{server_name}.") else put_flash( socket, :error, "Could not perform the update. Your server does not seem to have an address" ) end {:noreply, socket} end 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 defp show_edit_server_name() do JS.hide(to: "#server-name") |> JS.show(to: "#server-name-edit") |> 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 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 end