backend: register server api
This commit is contained in:
parent
6b14c2d1cc
commit
3c779c6b64
9 changed files with 95 additions and 47 deletions
|
@ -38,24 +38,33 @@ defmodule Prymn.Servers do
|
|||
def get_server!(id), do: Repo.get!(Server, id)
|
||||
|
||||
@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
|
||||
# FIXME: Maybe use a cryptographically secure token (if UUID v4 is not one)?
|
||||
%Server{connection_token: Ecto.UUID.generate()}
|
||||
# Create a unique registration token
|
||||
%Server{registration_token: :crypto.strong_rand_bytes(16)}
|
||||
|> Server.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
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 """
|
||||
Updates a server.
|
||||
|
||||
|
|
|
@ -4,10 +4,13 @@ defmodule Prymn.Servers.Server do
|
|||
|
||||
schema "servers" do
|
||||
field :name, :string
|
||||
field :ipv4, :string
|
||||
field :ipv6, :string
|
||||
field :public_ip, :string
|
||||
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()
|
||||
end
|
||||
|
@ -15,8 +18,8 @@ defmodule Prymn.Servers.Server do
|
|||
@doc false
|
||||
def changeset(server, attrs) do
|
||||
server
|
||||
|> cast(attrs, [:name, :provider])
|
||||
|> validate_required([:name, :provider, :connection_token])
|
||||
|> cast(attrs, [:name, :provider, :registration_token])
|
||||
|> validate_required([:name, :provider])
|
||||
|> validate_inclusion(:provider, [:Custom], message: "Provider not available (yet)")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -477,7 +477,7 @@ defmodule PrymnWeb.CoreComponents do
|
|||
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">
|
||||
<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 hover:cursor-pointer"><%= @row_indicator.(row) %></span>
|
||||
</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
|
||||
|
||||
@impl true
|
||||
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)}
|
||||
def handle_info({:connect, %Servers.Server{} = server}, socket) do
|
||||
socket =
|
||||
if server.provider == :Custom,
|
||||
do: push_navigate(socket, to: ~p"/servers/#{server}"),
|
||||
else: stream_insert(socket, :servers, server)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
@ -12,15 +12,20 @@
|
|||
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>)
|
||||
fn
|
||||
{_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
|
||||
}
|
||||
indicator_label="Status"
|
||||
>
|
||||
<: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}}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: server.id}) |> hide("##{id}")}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
<.header>
|
||||
<%= @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>
|
||||
Server <%= @server.name %>
|
||||
</.header>
|
||||
|
||||
<div class="mt-10">
|
||||
<h2 class="text-xl font-bold">Sites</h2>
|
||||
</div>
|
||||
<section :if={@server.connection_status == :awaiting} 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">
|
||||
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>
|
||||
|
|
|
@ -25,10 +25,11 @@ defmodule PrymnWeb.Router do
|
|||
live "/servers/:id", ServerLive.Show
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", PrymnWeb do
|
||||
# pipe_through :api
|
||||
# end
|
||||
scope "/api/v1", PrymnWeb do
|
||||
pipe_through :api
|
||||
|
||||
post "/servers/register", ServerController, :register
|
||||
end
|
||||
|
||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||
if Application.compile_env(:prymn, :dev_routes) do
|
||||
|
|
|
@ -2,14 +2,16 @@ defmodule Prymn.Repo.Migrations.CreateServers do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:servers) do
|
||||
create table("servers") do
|
||||
add :name, :string
|
||||
add :ipv4, :string
|
||||
add :ipv6, :string
|
||||
add :public_ip, :string
|
||||
add :provider, :string
|
||||
add :connection_token, :binary
|
||||
add :connection_status, :string
|
||||
add :registration_token, :binary
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("servers", [:registration_token], unique: true)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue