backend: register server api
This commit is contained in:
parent
6b14c2d1cc
commit
3c779c6b64
9 changed files with 95 additions and 47 deletions
backend
|
@ -38,24 +38,33 @@ defmodule Prymn.Servers do
|
||||||
def get_server!(id), do: Repo.get!(Server, id)
|
def get_server!(id), do: Repo.get!(Server, id)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a server.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
iex> create_server(%{field: value})
|
|
||||||
{:ok, %Server{}}
|
|
||||||
|
|
||||||
iex> create_server(%{field: bad_value})
|
|
||||||
{:error, %Ecto.Changeset{}}
|
|
||||||
|
|
||||||
|
Start a new server connection with the app.
|
||||||
"""
|
"""
|
||||||
def create_server(attrs \\ %{}) do
|
def create_server(attrs \\ %{}) do
|
||||||
# FIXME: Maybe use a cryptographically secure token (if UUID v4 is not one)?
|
# Create a unique registration token
|
||||||
%Server{connection_token: Ecto.UUID.generate()}
|
%Server{registration_token: :crypto.strong_rand_bytes(16)}
|
||||||
|> Server.changeset(attrs)
|
|> Server.changeset(attrs)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Registers a server using a registration token.
|
||||||
|
"""
|
||||||
|
def register_server(token, public_ip) do
|
||||||
|
# TODO: Validate public ip
|
||||||
|
|
||||||
|
case Base.decode64(token) do
|
||||||
|
{:ok, token} ->
|
||||||
|
from(s in Server, where: s.registration_token == ^token, select: s)
|
||||||
|
|> Repo.one()
|
||||||
|
|> update_server(%{public_ip: public_ip})
|
||||||
|
|
||||||
|
:error ->
|
||||||
|
{:error, "token is not a valid base64 encoded string"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a server.
|
Updates a server.
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,13 @@ defmodule Prymn.Servers.Server do
|
||||||
|
|
||||||
schema "servers" do
|
schema "servers" do
|
||||||
field :name, :string
|
field :name, :string
|
||||||
field :ipv4, :string
|
field :public_ip, :string
|
||||||
field :ipv6, :string
|
|
||||||
field :provider, Ecto.Enum, values: [:Hetzner, :Custom]
|
field :provider, Ecto.Enum, values: [:Hetzner, :Custom]
|
||||||
field :connection_token, Ecto.UUID, redact: true
|
field :registration_token, :binary, redact: true
|
||||||
|
|
||||||
|
field :connection_status, Ecto.Enum,
|
||||||
|
values: [:awaiting, :connecting, :installing, :connected, :disconnected],
|
||||||
|
default: :awaiting
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -15,8 +18,8 @@ defmodule Prymn.Servers.Server do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(server, attrs) do
|
def changeset(server, attrs) do
|
||||||
server
|
server
|
||||||
|> cast(attrs, [:name, :provider])
|
|> cast(attrs, [:name, :provider, :registration_token])
|
||||||
|> validate_required([:name, :provider, :connection_token])
|
|> validate_required([:name, :provider])
|
||||||
|> validate_inclusion(:provider, [:Custom], message: "Provider not available (yet)")
|
|> validate_inclusion(:provider, [:Custom], message: "Provider not available (yet)")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -477,7 +477,7 @@ defmodule PrymnWeb.CoreComponents do
|
||||||
class="relative w-min p-0 hover:cursor-pointer"
|
class="relative w-min p-0 hover:cursor-pointer"
|
||||||
phx-click={@row_click && @row_click.(row)}
|
phx-click={@row_click && @row_click.(row)}
|
||||||
>
|
>
|
||||||
<div class="block w-min py-4 pr-6">
|
<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="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>
|
<span class="relative hover:cursor-pointer"><%= @row_indicator.(row) %></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
19
backend/lib/prymn_web/controllers/server_controller.ex
Normal file
19
backend/lib/prymn_web/controllers/server_controller.ex
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule PrymnWeb.ServerController do
|
||||||
|
use PrymnWeb, :controller
|
||||||
|
|
||||||
|
alias Prymn.Servers
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Used by clients to request a new server connection to the prymn backend
|
||||||
|
validating their registration token.
|
||||||
|
"""
|
||||||
|
def register(conn, %{"token" => token, "ip" => ip}) do
|
||||||
|
case Servers.register_server(token, ip) do
|
||||||
|
{:ok, _server} ->
|
||||||
|
json(conn, %{"connected" => true})
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
raise inspect(error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,11 +24,13 @@ defmodule PrymnWeb.ServerLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:connect, server}, socket) do
|
def handle_info({:connect, %Servers.Server{} = server}, socket) do
|
||||||
# TODO: Connect the new server:
|
socket =
|
||||||
# For custom connections: will need to generate a new prompt for the user to execute
|
if server.provider == :Custom,
|
||||||
# For API connections: the "prompt" will be automatic, but same execution server-side
|
do: push_navigate(socket, to: ~p"/servers/#{server}"),
|
||||||
{:noreply, stream_insert(socket, :servers, server)}
|
else: stream_insert(socket, :servers, server)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
|
@ -12,15 +12,20 @@
|
||||||
rows={@streams.servers}
|
rows={@streams.servers}
|
||||||
row_click={fn {_id, server} -> JS.navigate(~p"/servers/#{server}") end}
|
row_click={fn {_id, server} -> JS.navigate(~p"/servers/#{server}") end}
|
||||||
row_indicator={
|
row_indicator={
|
||||||
fn {_id, _server} ->
|
fn
|
||||||
~H(<span class="text-indigo-600">Connecting</span>)
|
{_id, %Servers.Server{connection_status: :awaiting}} ->
|
||||||
|
~H(<span class="text-grey-600">Awaiting connection</span>)
|
||||||
|
|
||||||
|
{_id, %Servers.Server{connection_status: :connecting}} ->
|
||||||
|
~H(<span class="text-purple-600">Connecting</span>)
|
||||||
|
|
||||||
|
{_id, %Servers.Server{connection_status: :connected}} ->
|
||||||
|
~H(<span class="text-green-600">Connected</span>)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
indicator_label="Status"
|
indicator_label="Status"
|
||||||
>
|
>
|
||||||
<:col :let={{_id, server}} label="Name"><%= server.name %></:col>
|
<:col :let={{_id, server}} label="Name"><%= server.name %></:col>
|
||||||
<:col :let={{_id, server}} label="IPv4"><%= server.ipv4 || "N/A" %></:col>
|
|
||||||
<:col :let={{_id, server}} label="IPv6"><%= server.ipv6 || "N/A" %></:col>
|
|
||||||
<:action :let={{id, server}}>
|
<:action :let={{id, server}}>
|
||||||
<.link
|
<.link
|
||||||
phx-click={JS.push("delete", value: %{id: server.id}) |> hide("##{id}")}
|
phx-click={JS.push("delete", value: %{id: server.id}) |> hide("##{id}")}
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
<.header>
|
<.header>
|
||||||
<%= @server.name %>
|
Server <%= @server.name %>
|
||||||
<:subtitle>
|
|
||||||
<span>IPv4: <%= @server.ipv4 || "Not available" %></span>
|
|
||||||
<span :if={@server.ipv6}>IPv6: <%= @server.ipv6 %></span>
|
|
||||||
</:subtitle>
|
|
||||||
<:actions>
|
|
||||||
<.button>
|
|
||||||
New site
|
|
||||||
</.button>
|
|
||||||
</:actions>
|
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div class="mt-10">
|
<section :if={@server.connection_status == :awaiting} class="my-10">
|
||||||
<h2 class="text-xl font-bold">Sites</h2>
|
<p class="mb-9">
|
||||||
</div>
|
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">
|
||||||
|
your command here that contains token: <%= Base.encode64(@server.registration_token) %>
|
||||||
|
</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>
|
||||||
|
</section>
|
||||||
|
|
||||||
<.back navigate={~p"/servers"}>Back to servers</.back>
|
<.back navigate={~p"/servers"}>Back to servers</.back>
|
||||||
|
|
|
@ -25,10 +25,11 @@ defmodule PrymnWeb.Router do
|
||||||
live "/servers/:id", ServerLive.Show
|
live "/servers/:id", ServerLive.Show
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
scope "/api/v1", PrymnWeb do
|
||||||
# scope "/api", PrymnWeb do
|
pipe_through :api
|
||||||
# pipe_through :api
|
|
||||||
# end
|
post "/servers/register", ServerController, :register
|
||||||
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
if Application.compile_env(:prymn, :dev_routes) do
|
if Application.compile_env(:prymn, :dev_routes) do
|
||||||
|
|
|
@ -2,14 +2,16 @@ defmodule Prymn.Repo.Migrations.CreateServers do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:servers) do
|
create table("servers") do
|
||||||
add :name, :string
|
add :name, :string
|
||||||
add :ipv4, :string
|
add :public_ip, :string
|
||||||
add :ipv6, :string
|
|
||||||
add :provider, :string
|
add :provider, :string
|
||||||
add :connection_token, :binary
|
add :connection_status, :string
|
||||||
|
add :registration_token, :binary
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create index("servers", [:registration_token], unique: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue