Show a dynamically updated list of NTS servers #1
|
@ -23,6 +23,7 @@ defmodule Chronoscope.Application do
|
||||||
{Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]},
|
{Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]},
|
||||||
# Start to serve requests, typically the last entry
|
# Start to serve requests, typically the last entry
|
||||||
ChronoscopeWeb.Endpoint,
|
ChronoscopeWeb.Endpoint,
|
||||||
|
# Initialize clients for the main LiveView
|
||||||
Chronoscope.ViewActivator
|
Chronoscope.ViewActivator
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ defmodule Chronoscope.ViewActivator do
|
||||||
@nts_file
|
@nts_file
|
||||||
|> File.stream!()
|
|> File.stream!()
|
||||||
|> Stream.map(&String.trim/1)
|
|> Stream.map(&String.trim/1)
|
||||||
|
|> Stream.filter(&(&1 != ""))
|
||||||
|> Stream.map(&Parse.parse_nts_server/1)
|
|> Stream.map(&Parse.parse_nts_server/1)
|
||||||
|> Enum.to_list()
|
|> Enum.to_list()
|
||||||
|> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end)
|
|> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end)
|
||||||
|
|
|
@ -13,7 +13,7 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
|
||||||
|
|
||||||
def get(conn, %{"host" => host, "port" => port, "path" => path}) do
|
def get(conn, %{"host" => host, "port" => port, "path" => path}) do
|
||||||
try do
|
try do
|
||||||
handle_get(conn, %{host: host, port: String.to_integer(port), path: path})
|
handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: path})
|
||||||
rescue
|
rescue
|
||||||
ArgumentError -> bad_request_response(conn, "invalid port")
|
ArgumentError -> bad_request_response(conn, "invalid port")
|
||||||
end
|
end
|
||||||
|
@ -25,20 +25,24 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
|
||||||
|
|
||||||
def get(conn, %{"host" => host, "port" => port}) do
|
def get(conn, %{"host" => host, "port" => port}) do
|
||||||
try do
|
try do
|
||||||
handle_get(conn, %{host: host, port: String.to_integer(port), path: @default_path})
|
handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: @default_path})
|
||||||
rescue
|
rescue
|
||||||
ArgumentError -> bad_request_response(conn, "invalid port")
|
ArgumentError -> bad_request_response(conn, "invalid port")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(conn, %{"host" => host}) do
|
def get(conn, %{"host" => host}) do
|
||||||
handle_get(conn, %{host: host, port: @default_port, path: @default_path})
|
handle_get(conn, %{host: String.trim(host), port: @default_port, path: @default_path})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(conn, _params) do
|
def get(conn, _params) do
|
||||||
bad_request_response(conn, "missing host")
|
bad_request_response(conn, "missing host")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_get(conn, %{host: ""}) do
|
||||||
|
bad_request_response(conn, "empty host")
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do
|
defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do
|
||||||
case connect(host, port, path) do
|
case connect(host, port, path) do
|
||||||
{:ok, response} ->
|
{:ok, response} ->
|
||||||
|
|
|
@ -12,20 +12,24 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
|
||||||
|
|
||||||
def get(conn, %{"host" => host, "port" => port}) do
|
def get(conn, %{"host" => host, "port" => port}) do
|
||||||
try do
|
try do
|
||||||
handle_get(conn, %{host: host, port: String.to_integer(port)})
|
handle_get(conn, %{host: String.trim(host), port: String.to_integer(port)})
|
||||||
rescue
|
rescue
|
||||||
ArgumentError -> bad_request_response(conn, "invalid port")
|
ArgumentError -> bad_request_response(conn, "invalid port")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(conn, %{"host" => host}) do
|
def get(conn, %{"host" => host}) do
|
||||||
handle_get(conn, %{host: host, port: @default_port})
|
handle_get(conn, %{host: String.trim(host), port: @default_port})
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(conn, _params) do
|
def get(conn, _params) do
|
||||||
bad_request_response(conn, "missing host")
|
bad_request_response(conn, "missing host")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_get(conn, %{host: ""}) do
|
||||||
|
bad_request_response(conn, "empty host")
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_get(conn, %{host: host, port: port}) when port > 0 and port < 65536 do
|
defp handle_get(conn, %{host: host, port: port}) when port > 0 and port < 65536 do
|
||||||
case key_establishment_response(host, port) do
|
case key_establishment_response(host, port) do
|
||||||
{:ok, response} ->
|
{:ok, response} ->
|
||||||
|
|
|
@ -3,26 +3,33 @@ defmodule ChronoscopeWeb.IndexLive do
|
||||||
|
|
||||||
alias Chronoscope.NTS
|
alias Chronoscope.NTS
|
||||||
alias Chronoscope.NTS.KeyEstablishmentResponse
|
alias Chronoscope.NTS.KeyEstablishmentResponse
|
||||||
|
alias Chronoscope.ViewActivator
|
||||||
|
|
||||||
@topic Application.compile_env(:chronoscope, :nts_topic)
|
@topic Application.compile_env(:chronoscope, :nts_topic)
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
ChronoscopeWeb.Endpoint.subscribe(@topic)
|
ChronoscopeWeb.Endpoint.subscribe(@topic)
|
||||||
{:ok, assign(socket, %{clients: client_list()})}
|
{:ok, assign(socket, %{servers: server_list(), clients: client_list()})}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%{topic: @topic, event: "key-exchange", payload: client}, socket) do
|
def handle_info(%{topic: @topic, event: "key-exchange", payload: client}, socket) do
|
||||||
|
if client.server in socket.assigns.servers do
|
||||||
{:noreply, update(socket, :clients, &update_client(&1, client))}
|
{:noreply, update(socket, :clients, &update_client(&1, client))}
|
||||||
|
else
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_client(client_list, client) do
|
defp update_client(client_list, client) do
|
||||||
# todo - use a map instead of list for fast lookups?
|
|
||||||
Enum.map(client_list, &if(client.server == &1.server, do: client, else: &1))
|
Enum.map(client_list, &if(client.server == &1.server, do: client, else: &1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp server_list() do
|
||||||
|
GenServer.call(ViewActivator, :get_nts_servers)
|
||||||
|
end
|
||||||
|
|
||||||
defp client_list() do
|
defp client_list() do
|
||||||
Chronoscope.ViewActivator
|
server_list()
|
||||||
|> GenServer.call(:get_nts_servers)
|
|
||||||
|> NTS.list_clients()
|
|> NTS.list_clients()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,42 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionControllerTest do
|
||||||
assert %{"error" => "missing host"} == response
|
assert %{"error" => "missing host"} == response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "requires a host name value", %{conn: conn} do
|
||||||
|
response1 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/gemini/connect?host=")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
response2 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/gemini/connect?host=&port=1966")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert %{"error" => "empty host"} == response1
|
||||||
|
assert %{"error" => "empty host"} == response2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "requires a non-blank host name", %{conn: conn} do
|
||||||
|
response1 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/gemini/connect?host=%20%20")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
response2 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
response3 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966&path=/test")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert %{"error" => "empty host"} == response1
|
||||||
|
assert %{"error" => "empty host"} == response2
|
||||||
|
assert %{"error" => "empty host"} == response3
|
||||||
|
end
|
||||||
|
|
||||||
test "uses the given host name", %{conn: conn} do
|
test "uses the given host name", %{conn: conn} do
|
||||||
GeminiMock
|
GeminiMock
|
||||||
|> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end)
|
|> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end)
|
||||||
|
|
|
@ -17,6 +17,36 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
|
||||||
assert %{"error" => "missing host"} == response
|
assert %{"error" => "missing host"} == response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "requires a host name value", %{conn: conn} do
|
||||||
|
response1 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/nts/key-establishment?host=")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
response2 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/nts/key-establishment?host=&port=4444")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert %{"error" => "empty host"} == response1
|
||||||
|
assert %{"error" => "empty host"} == response2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "requires a non-blank host name", %{conn: conn} do
|
||||||
|
response1 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/nts/key-establishment?host=%20%20")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
response2 =
|
||||||
|
conn
|
||||||
|
|> get(~p"/api/v1/nts/key-establishment?host=%20%20&port=4444")
|
||||||
|
|> json_response(400)
|
||||||
|
|
||||||
|
assert %{"error" => "empty host"} == response1
|
||||||
|
assert %{"error" => "empty host"} == response2
|
||||||
|
end
|
||||||
|
|
||||||
test "uses the given host name", %{conn: conn} do
|
test "uses the given host name", %{conn: conn} do
|
||||||
NTSMock
|
NTSMock
|
||||||
|> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)
|
|> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)
|
||||||
|
|
Loading…
Reference in New Issue