web: new server form
This commit is contained in:
parent
b5cab545e6
commit
52b892bb4f
12 changed files with 139 additions and 185 deletions
|
@ -50,7 +50,8 @@ defmodule Prymn.Servers do
|
|||
|
||||
"""
|
||||
def create_server(attrs \\ %{}) do
|
||||
%Server{}
|
||||
# FIXME: Maybe use a cryptographically secure token (if UUID v4 is not one)?
|
||||
%Server{connection_token: Ecto.UUID.generate()}
|
||||
|> Server.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule Prymn.Servers.Server do
|
|||
field :name, :string
|
||||
field :ipv4, :string
|
||||
field :ipv6, :string
|
||||
field :provider, Ecto.Enum, values: [:Hetzner, :Custom]
|
||||
field :connection_token, Ecto.UUID, redact: true
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -13,7 +15,8 @@ defmodule Prymn.Servers.Server do
|
|||
@doc false
|
||||
def changeset(server, attrs) do
|
||||
server
|
||||
|> cast(attrs, [:name])
|
||||
|> validate_required([:name])
|
||||
|> cast(attrs, [:name, :provider])
|
||||
|> validate_required([:name, :provider, :connection_token])
|
||||
|> validate_inclusion(:provider, [:Custom], message: "Provider not available (yet)")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -436,6 +436,8 @@ defmodule PrymnWeb.CoreComponents do
|
|||
attr :rows, :list, required: true
|
||||
attr :row_id, :any, default: nil, doc: "the function for generating the row id"
|
||||
attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
|
||||
attr :row_indicator, :any, default: nil
|
||||
attr :indicator_label, :string, default: "Indicator"
|
||||
|
||||
attr :row_item, :any,
|
||||
default: &Function.identity/1,
|
||||
|
@ -458,6 +460,9 @@ defmodule PrymnWeb.CoreComponents do
|
|||
<table class="w-[40rem] mt-11 sm:w-full">
|
||||
<thead class="text-left text-sm leading-6 text-zinc-500">
|
||||
<tr>
|
||||
<th class="p-0 pb-4 font-normal">
|
||||
<div class="w-min"><%= @indicator_label %></div>
|
||||
</th>
|
||||
<th :for={col <- @col} class="p-0 pr-6 pb-4 font-normal"><%= col[:label] %></th>
|
||||
<th class="relative p-0 pb-4"><span class="sr-only"><%= gettext("Actions") %></span></th>
|
||||
</tr>
|
||||
|
@ -468,13 +473,21 @@ defmodule PrymnWeb.CoreComponents do
|
|||
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
|
||||
>
|
||||
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
|
||||
<td
|
||||
class="relative w-min p-0 hover:cursor-pointer"
|
||||
phx-click={@row_click && @row_click.(row)}
|
||||
>
|
||||
<div class="block w-min py-4 pr-6">
|
||||
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
||||
<span class="relative hover:cursor-pointer"><%= @row_indicator.(row) %></span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
:for={{col, i} <- Enum.with_index(@col)}
|
||||
phx-click={@row_click && @row_click.(row)}
|
||||
class={["relative p-0", @row_click && "hover:cursor-pointer"]}
|
||||
>
|
||||
<div class="block py-4 pr-6">
|
||||
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
||||
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
|
||||
<%= render_slot(col, @row_item.(row)) %>
|
||||
</span>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
defmodule PrymnWeb.ServerLive.Edit do
|
||||
use PrymnWeb, :live_view
|
||||
|
||||
alias Prymn.Servers
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
server = Servers.get_server!(id)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, "Editing #{server.name}")
|
||||
|> assign(:server, server)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({PrymnWeb.ServerLive.FormComponent, {:saved, server}}, socket) do
|
||||
{:noreply, assign(socket, :server, server)}
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
<.header>Editing server <%= @server.name %></.header>
|
||||
|
||||
<.live_component
|
||||
module={PrymnWeb.ServerLive.FormComponent}
|
||||
title="Test"
|
||||
id={@server.id}
|
||||
action={@live_action}
|
||||
server={@server}
|
||||
/>
|
||||
|
||||
<.back navigate={~p"/servers/#{@server}"}>Go back to server <%= @server.name %></.back>
|
|
@ -1,88 +0,0 @@
|
|||
defmodule PrymnWeb.ServerLive.FormComponent do
|
||||
use PrymnWeb, :live_component
|
||||
|
||||
alias Prymn.Servers
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.simple_form
|
||||
for={@form}
|
||||
id="server-form"
|
||||
phx-target={@myself}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
>
|
||||
<.input field={@form[:name]} type="text" label="Name" />
|
||||
<:actions>
|
||||
<.button phx-disable-with="Saving...">Save Server</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(%{server: server} = assigns, socket) do
|
||||
changeset = Servers.change_server(server)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign_form(changeset)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"server" => server_params}, socket) do
|
||||
changeset =
|
||||
socket.assigns.server
|
||||
|> Servers.change_server(server_params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
{:noreply, assign_form(socket, changeset)}
|
||||
end
|
||||
|
||||
def handle_event("save", %{"server" => server_params}, socket) do
|
||||
save_server(socket, socket.assigns.action, server_params)
|
||||
end
|
||||
|
||||
defp save_server(socket, :edit, server_params) do
|
||||
case Servers.update_server(socket.assigns.server, server_params) do
|
||||
{:ok, server} ->
|
||||
notify_parent({:saved, server})
|
||||
|
||||
socket = socket |> put_flash(:info, "Server updated successfully")
|
||||
|
||||
if Map.has_key?(socket.assigns, :patch) do
|
||||
socket = push_patch(socket, to: socket.assigns.patch)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign_form(socket, changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_server(socket, :new, server_params) do
|
||||
case Servers.create_server(server_params) do
|
||||
{:ok, server} ->
|
||||
notify_parent({:saved, server})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Server created successfully")
|
||||
|> push_patch(to: socket.assigns.patch || false)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign_form(socket, changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
|
||||
assign(socket, :form, to_form(changeset))
|
||||
end
|
||||
|
||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||
end
|
|
@ -2,7 +2,6 @@ defmodule PrymnWeb.ServerLive.Index do
|
|||
use PrymnWeb, :live_view
|
||||
|
||||
alias Prymn.Servers
|
||||
alias Prymn.Servers.Server
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
|
@ -14,26 +13,21 @@ defmodule PrymnWeb.ServerLive.Index do
|
|||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
socket
|
||||
|> assign(:page_title, "Edit Server")
|
||||
|> assign(:server, Servers.get_server!(id))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "New Server")
|
||||
|> assign(:server, %Server{})
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Servers")
|
||||
|> assign(:server, nil)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({PrymnWeb.ServerLive.FormComponent, {:saved, server}}, socket) do
|
||||
def handle_info({:connect, server}, socket) do
|
||||
# TODO: Connect the new server:
|
||||
# For custom connections: will need to generate a new prompt for the user to execute
|
||||
# For API connections: the "prompt" will be automatic, but same execution server-side
|
||||
{:noreply, stream_insert(socket, :servers, server)}
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
id="servers"
|
||||
rows={@streams.servers}
|
||||
row_click={fn {_id, server} -> JS.navigate(~p"/servers/#{server}") end}
|
||||
row_indicator={
|
||||
fn {_id, _server} ->
|
||||
~H(<span class="text-indigo-600">Connecting</span>)
|
||||
end
|
||||
}
|
||||
indicator_label="Status"
|
||||
>
|
||||
<:col :let={{_id, server}} label="Name"><%= server.name %></:col>
|
||||
<:col :let={{_id, server}} label="IPv4"><%= server.ipv4 || "N/A" %></:col>
|
||||
|
@ -25,22 +31,6 @@
|
|||
</:action>
|
||||
</.table>
|
||||
|
||||
<.modal
|
||||
:if={@live_action in [:new, :edit]}
|
||||
id="server-modal"
|
||||
show
|
||||
on_cancel={JS.patch(~p"/servers")}
|
||||
>
|
||||
<.header>
|
||||
Add a new server
|
||||
<:subtitle>Connect your server to Prymn!</:subtitle>
|
||||
</.header>
|
||||
<.live_component
|
||||
module={PrymnWeb.ServerLive.FormComponent}
|
||||
id={:new}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
server={@server}
|
||||
patch={~p"/servers"}
|
||||
/>
|
||||
<.modal :if={@live_action == :new} id="server-modal" show on_cancel={JS.patch(~p"/servers")}>
|
||||
<.live_component module={PrymnWeb.ServerLive.NewServer} id={:new} patch={~p"/servers"} />
|
||||
</.modal>
|
||||
|
|
92
backend/lib/prymn_web/live/server_live/new_server.ex
Normal file
92
backend/lib/prymn_web/live/server_live/new_server.ex
Normal file
|
@ -0,0 +1,92 @@
|
|||
defmodule PrymnWeb.ServerLive.NewServer do
|
||||
use PrymnWeb, :live_component
|
||||
|
||||
alias Prymn.Servers
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.header>
|
||||
Add a new server
|
||||
<:subtitle>Connect your server to Prymn!</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.error :if={assigns[:error]}>There were some errors.</.error>
|
||||
|
||||
<.simple_form for={@form} phx-change="validate" phx-submit="connect" phx-target={@myself}>
|
||||
<.input field={@form[:name]} label="Server Name" phx-debounce={1000} />
|
||||
<.input
|
||||
field={@form[:provider]}
|
||||
label="Provider"
|
||||
type="select"
|
||||
prompt="Select a provider"
|
||||
options={Ecto.Enum.mappings(Servers.Server, :provider)}
|
||||
/>
|
||||
<.provider id="provider" provider={@form[:provider]} />
|
||||
<:actions>
|
||||
<.button>Connect</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
changeset = Servers.change_server(%Servers.Server{})
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:form, to_form(changeset))
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"server" => params}, socket) do
|
||||
form =
|
||||
%Servers.Server{}
|
||||
|> Servers.change_server(params)
|
||||
|> Map.put(:action, :validate)
|
||||
|> to_form()
|
||||
|
||||
{:noreply, assign(socket, form: form)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("connect", %{"server" => params}, socket) do
|
||||
socket =
|
||||
case Servers.create_server(params) do
|
||||
{:ok, server} ->
|
||||
# Notify parent
|
||||
send(self(), {:connect, server})
|
||||
|
||||
socket
|
||||
|> put_flash(:info, "Starting new server connection...")
|
||||
|> push_patch(to: ~p"/servers")
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
socket
|
||||
|> assign(:form, to_form(changeset))
|
||||
|> assign(:error, true)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp provider(%{provider: %{value: :Custom}} = assigns) do
|
||||
~H"""
|
||||
<p>
|
||||
<bold class="font-bold">Manual installation:</bold>
|
||||
To connect, you must have root SSH access to your server. When you click
|
||||
connect, you will be prompted to execute a command on your server to
|
||||
complete the installation.
|
||||
</p>
|
||||
"""
|
||||
end
|
||||
|
||||
defp provider(assigns), do: ~H""
|
||||
end
|
|
@ -1,33 +1,18 @@
|
|||
<.header>
|
||||
Server <%= @server.name %>
|
||||
<:subtitle>From here, you can view your server details</:subtitle>
|
||||
<%= @server.name %>
|
||||
<:subtitle>
|
||||
<span>IPv4: <%= @server.ipv4 || "Not available" %></span>
|
||||
<span :if={@server.ipv6}>IPv6: <%= @server.ipv6 %></span>
|
||||
</:subtitle>
|
||||
<:actions>
|
||||
<.link navigate={~p"/servers/#{@server}/edit"}>
|
||||
<.button>Edit server</.button>
|
||||
</.link>
|
||||
<.button>
|
||||
New site
|
||||
</.button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.list>
|
||||
<:item title="Name"><%= @server.name %></:item>
|
||||
<:item title="IPv4"><%= @server.ipv4 || "Not available" %></:item>
|
||||
<:item title="IPv6"><%= @server.ipv6 || "Not available" %></:item>
|
||||
</.list>
|
||||
<div class="mt-10">
|
||||
<h2 class="text-xl font-bold">Sites</h2>
|
||||
</div>
|
||||
|
||||
<.back navigate={~p"/servers"}>Back to servers</.back>
|
||||
|
||||
<.modal
|
||||
:if={@live_action == :edit}
|
||||
id="server-modal"
|
||||
show
|
||||
on_cancel={JS.patch(~p"/servers/#{@server}")}
|
||||
>
|
||||
<.live_component
|
||||
module={PrymnWeb.ServerLive.FormComponent}
|
||||
id={@server.id}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
server={@server}
|
||||
patch={~p"/servers/#{@server}"}
|
||||
/>
|
||||
</.modal>
|
||||
|
|
|
@ -22,9 +22,7 @@ defmodule PrymnWeb.Router do
|
|||
live "/servers", ServerLive.Index, :index
|
||||
live "/servers/new", ServerLive.Index, :new
|
||||
|
||||
live "/servers/:id", ServerLive.Show, :show
|
||||
|
||||
live "/servers/:id/edit", ServerLive.Edit, :edit
|
||||
live "/servers/:id", ServerLive.Show
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule Prymn.Repo.Migrations.CreateServers do
|
|||
add :name, :string
|
||||
add :ipv4, :string
|
||||
add :ipv6, :string
|
||||
add :provider, :string
|
||||
add :connection_token, :binary
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue