397 lines
11 KiB
Elixir
397 lines
11 KiB
Elixir
defmodule FarmbotOS.Configurator.Router do
|
|
@moduledoc "Routes web connections for configuring farmbot os"
|
|
|
|
require FarmbotCore.Logger
|
|
require FarmbotTelemetry
|
|
|
|
import Phoenix.HTML
|
|
use Plug.Router
|
|
use Plug.Debugger, otp_app: :farmbot
|
|
alias FarmbotOS.Configurator.ConfigDataLayer
|
|
|
|
plug(Plug.Logger)
|
|
plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/")
|
|
plug(Plug.Parsers, parsers: [:urlencoded, :multipart])
|
|
|
|
plug(Plug.Session,
|
|
store: :ets,
|
|
key: "_farmbot_session",
|
|
table: :configurator_session
|
|
)
|
|
|
|
plug(:fetch_session)
|
|
plug(:match)
|
|
plug(:dispatch)
|
|
|
|
@network_layer Application.get_env(:farmbot, FarmbotOS.Configurator)[
|
|
:network_layer
|
|
]
|
|
@telemetry_layer FarmbotOS.Configurator.DetsTelemetryLayer
|
|
|
|
# Trigger for captive portal for various operating systems
|
|
get("/gen_204", do: redir(conn, "/"))
|
|
get("/generate_204", do: redir(conn, "/"))
|
|
get("/hotspot-detect.html", do: redir(conn, "/"))
|
|
get("/library/test/success.html", do: redir(conn, "/"))
|
|
get("/connecttest.txt", do: redir(conn, "/"))
|
|
get("/redirect", do: redir(conn, "/"))
|
|
get("/check_network_status.txt", do: redir(conn, "/"))
|
|
get("/success.txt", do: redir(conn, "/"))
|
|
|
|
get "/scheduler_debugger" do
|
|
render_page(conn, "scheduler_debugger")
|
|
end
|
|
|
|
get "/logger" do
|
|
render_page(conn, "logger")
|
|
end
|
|
|
|
get "/api/telemetry/cpu_usage" do
|
|
render_json(conn, cpu_usage())
|
|
end
|
|
|
|
get "/" do
|
|
FarmbotTelemetry.event(:configurator, :configuration_start)
|
|
|
|
case load_last_reset_reason() do
|
|
reason when is_binary(reason) ->
|
|
if String.contains?(reason, "CeleryScript request.") do
|
|
redir(conn, "/network")
|
|
else
|
|
render_page(conn, "index",
|
|
version: version(),
|
|
last_reset_reason: reason
|
|
)
|
|
end
|
|
|
|
nil ->
|
|
redir(conn, "/network")
|
|
end
|
|
end
|
|
|
|
get("/setup", do: redir(conn, "/"))
|
|
|
|
# NETWORKCONFIG
|
|
get "/network" do
|
|
interfaces = list_interfaces()
|
|
|
|
case interfaces do
|
|
[{<<"w", _::binary>> = ifname, _}] ->
|
|
conn
|
|
|> put_session("iftype", "wireless")
|
|
|> put_session("ifname", ifname)
|
|
|> redir("/config_wireless")
|
|
|
|
[{ifname, _}] ->
|
|
conn
|
|
|> put_session("iftype", "wired")
|
|
|> put_session("ifname", ifname)
|
|
|> redir("/config_wired")
|
|
|
|
_ ->
|
|
render_page(conn, "network",
|
|
interfaces: interfaces,
|
|
post_action: "select_interface"
|
|
)
|
|
end
|
|
end
|
|
|
|
post "select_interface" do
|
|
{:ok, _, conn} = read_body(conn)
|
|
ifname = conn.body_params["interface"] |> remove_empty_string()
|
|
|
|
case ifname do
|
|
nil ->
|
|
redir(conn, "/network")
|
|
|
|
<<"w", _::binary>> ->
|
|
conn
|
|
|> put_session("iftype", "wireless")
|
|
|> put_session("ifname", ifname)
|
|
|> redir("/config_wireless")
|
|
|
|
_ ->
|
|
conn
|
|
|> put_session("iftype", "wired")
|
|
|> put_session("ifname", ifname)
|
|
|> redir("/config_wired")
|
|
end
|
|
end
|
|
|
|
get "/config_wired" do
|
|
ifname = get_session(conn, "ifname")
|
|
|
|
render_page(conn, "config_wired",
|
|
ifname: ifname,
|
|
advanced_network: advanced_network()
|
|
)
|
|
end
|
|
|
|
get "/config_wireless" do
|
|
ifname = get_session(conn, "ifname")
|
|
|
|
render_page(conn, "/config_wireless_step_1",
|
|
ifname: ifname,
|
|
ssids: scan(ifname),
|
|
post_action: "config_wireless_step_1"
|
|
)
|
|
end
|
|
|
|
post "config_wireless_step_1" do
|
|
ifname = get_session(conn, "ifname")
|
|
ssid = conn.params["ssid"] |> remove_empty_string()
|
|
security = conn.params["security"] |> remove_empty_string()
|
|
manualssid = conn.params["manualssid"] |> remove_empty_string()
|
|
|
|
opts = [
|
|
ssid: ssid,
|
|
ifname: ifname,
|
|
security: security,
|
|
advanced_network: advanced_network(),
|
|
post_action: "config_network"
|
|
]
|
|
|
|
cond do
|
|
manualssid != nil ->
|
|
render_page(
|
|
conn,
|
|
"/config_wireless_step_2_custom",
|
|
Keyword.put(opts, :ssid, manualssid)
|
|
)
|
|
|
|
ssid == nil ->
|
|
redir(conn, "/config_wireless")
|
|
|
|
security == nil ->
|
|
redir(conn, "/config_wireless")
|
|
|
|
security == "WPA-PSK" ->
|
|
render_page(conn, "/config_wireless_step_2_PSK", opts)
|
|
|
|
security == "WPA2-PSK" ->
|
|
render_page(conn, "/config_wireless_step_2_PSK", opts)
|
|
|
|
security == "WPA-EAP" ->
|
|
render_page(conn, "/config_wireless_step_2_EAP", opts)
|
|
|
|
security == "NONE" ->
|
|
render_page(conn, "/config_wireless_step_2_NONE", opts)
|
|
|
|
true ->
|
|
render_page(conn, "/config_wireless_step_2_other", opts)
|
|
end
|
|
end
|
|
|
|
post "/config_network" do
|
|
# Global configuration data
|
|
dns_name = conn.params["dns_name"] |> remove_empty_string()
|
|
ntp1 = conn.params["ntp_server_1"] |> remove_empty_string()
|
|
ntp2 = conn.params["ntp_server_2"] |> remove_empty_string()
|
|
ssh_key = conn.params["ssh_key"] |> remove_empty_string()
|
|
|
|
# Network Interface configuration data
|
|
ssid = conn.params["ssid"] |> remove_empty_string()
|
|
security = conn.params["security"] |> remove_empty_string()
|
|
psk = conn.params["psk"] |> remove_empty_string()
|
|
identity = conn.params["identity"] |> remove_empty_string()
|
|
password = conn.params["password"] |> remove_empty_string()
|
|
domain = conn.params["domain"] |> remove_empty_string()
|
|
name_servers = conn.params["name_servers"] |> remove_empty_string()
|
|
ipv4_method = conn.params["ipv4_method"] |> remove_empty_string()
|
|
ipv4_address = conn.params["ipv4_address"] |> remove_empty_string()
|
|
ipv4_gateway = conn.params["ipv4_gateway"] |> remove_empty_string()
|
|
ipv4_subnet_mask = conn.params["ipv4_subnet_mask"] |> remove_empty_string()
|
|
reg_domain = conn.params["regulatory_domain"] |> remove_empty_string()
|
|
|
|
conn
|
|
|> put_session("net_config_dns_name", dns_name)
|
|
|> put_session("net_config_ntp1", ntp1)
|
|
|> put_session("net_config_ntp2", ntp2)
|
|
|> put_session("net_config_ssh_key", ssh_key)
|
|
|> put_session("net_config_ssid", ssid)
|
|
|> put_session("net_config_security", security)
|
|
|> put_session("net_config_psk", psk)
|
|
|> put_session("net_config_identity", identity)
|
|
|> put_session("net_config_password", password)
|
|
|> put_session("net_config_domain", domain)
|
|
|> put_session("net_config_name_servers", name_servers)
|
|
|> put_session("net_config_ipv4_method", ipv4_method)
|
|
|> put_session("net_config_ipv4_address", ipv4_address)
|
|
|> put_session("net_config_ipv4_gateway", ipv4_gateway)
|
|
|> put_session("net_config_ipv4_subnet_mask", ipv4_subnet_mask)
|
|
|> put_session("net_config_reg_domain", reg_domain)
|
|
|> redir("/credentials")
|
|
end
|
|
|
|
# /NETWORKCONFIG
|
|
|
|
get "/credentials" do
|
|
email = get_session(conn, "auth_config_email") || load_email() || ""
|
|
pass = get_session(conn, "auth_config_password") || load_password() || ""
|
|
server = get_session(conn, "auth_config_server") || load_server() || ""
|
|
|
|
render_page(conn, "credentials",
|
|
server: server,
|
|
email: email,
|
|
password: pass
|
|
)
|
|
end
|
|
|
|
post "/configure_credentials" do
|
|
{:ok, _, conn} = read_body(conn)
|
|
|
|
case conn.body_params do
|
|
%{"email" => email, "password" => pass, "server" => server} ->
|
|
if server = test_uri(server) do
|
|
FarmbotCore.Logger.info(1, "server valid: #{server}")
|
|
|
|
conn
|
|
|> put_session("auth_config_email", email)
|
|
|> put_session("auth_config_password", pass)
|
|
|> put_session("auth_config_server", server)
|
|
|> redir("/finish")
|
|
else
|
|
conn
|
|
|> put_session("__error", "Server is not a valid URI")
|
|
|> redir("/credentials")
|
|
end
|
|
|
|
_ ->
|
|
conn
|
|
|> put_session(
|
|
"__error",
|
|
"Email, Server, or Password are missing or invalid"
|
|
)
|
|
|> redir("/credentials")
|
|
end
|
|
end
|
|
|
|
get "/finish" do
|
|
FarmbotCore.Logger.debug(1, "Configuration complete")
|
|
|
|
# TODO(Rick): This pattern match is not 100% accurate.
|
|
# TO see what I mean, try calling `save_config/1` with
|
|
# _only_ the parameters provided in the line below-
|
|
# it will crash as it is missing numerous keys.
|
|
# It might be good to add an error page or something.
|
|
case get_session(conn) do
|
|
%{
|
|
"ifname" => _,
|
|
"auth_config_email" => _,
|
|
"auth_config_password" => _,
|
|
"auth_config_server" => _
|
|
} ->
|
|
save_config(get_session(conn))
|
|
render_page(conn, "finish")
|
|
|
|
_ ->
|
|
redir(conn, "/")
|
|
end
|
|
end
|
|
|
|
match(_, do: send_resp(conn, 404, "Page not found"))
|
|
|
|
defp redir(conn, loc) do
|
|
conn
|
|
|> put_resp_header("location", loc)
|
|
|> send_resp(302, loc)
|
|
end
|
|
|
|
defp render_page(conn, page, info \\ []) do
|
|
page
|
|
|> template_file()
|
|
|> EEx.eval_file(Keyword.merge([version: version()], info),
|
|
engine: Phoenix.HTML.Engine
|
|
)
|
|
|> (fn {:safe, contents} -> send_resp(conn, 200, contents) end).()
|
|
rescue
|
|
e ->
|
|
IO.warn("render error", __STACKTRACE__)
|
|
|
|
send_resp(
|
|
conn,
|
|
500,
|
|
"Failed to render page: #{page} error: #{Exception.message(e)}"
|
|
)
|
|
end
|
|
|
|
defp render_json(conn, data) do
|
|
conn = put_resp_header(conn, "content-type", "application/json")
|
|
|
|
case FarmbotCore.JSON.encode(data) do
|
|
{:ok, json} ->
|
|
send_resp(conn, 200, json)
|
|
|
|
_ ->
|
|
send_resp(
|
|
conn,
|
|
501,
|
|
FarmbotCore.JSON.encode!(%{error: "failed to render json"})
|
|
)
|
|
end
|
|
end
|
|
|
|
defp template_file(file) do
|
|
"#{:code.priv_dir(:farmbot)}/static/templates/#{file}.html.eex"
|
|
end
|
|
|
|
defp remove_empty_string(""), do: nil
|
|
defp remove_empty_string(str), do: str
|
|
|
|
defp advanced_network do
|
|
template_file("advanced_network")
|
|
|> EEx.eval_file([])
|
|
|> raw()
|
|
end
|
|
|
|
defp test_uri(nil), do: nil
|
|
|
|
defp test_uri(uri) do
|
|
case URI.parse(uri) do
|
|
%URI{host: host, port: port, scheme: scheme}
|
|
when scheme in ["https", "http"] and is_binary(host) and is_integer(port) ->
|
|
uri
|
|
|
|
_ ->
|
|
FarmbotCore.Logger.error(1, "#{inspect(uri)} is not valid")
|
|
nil
|
|
end
|
|
end
|
|
|
|
defp load_last_reset_reason do
|
|
ConfigDataLayer.load_last_reset_reason()
|
|
end
|
|
|
|
defp load_email do
|
|
ConfigDataLayer.load_email()
|
|
end
|
|
|
|
defp load_password do
|
|
ConfigDataLayer.load_password()
|
|
end
|
|
|
|
def load_server do
|
|
ConfigDataLayer.load_server()
|
|
end
|
|
|
|
defp save_config(conf) do
|
|
ConfigDataLayer.save_config(conf)
|
|
end
|
|
|
|
defp list_interfaces() do
|
|
@network_layer.list_interfaces()
|
|
end
|
|
|
|
defp scan(interface) do
|
|
@network_layer.scan(interface)
|
|
end
|
|
|
|
## Telemetry layer calls
|
|
|
|
defp cpu_usage() do
|
|
@telemetry_layer.cpu_usage()
|
|
end
|
|
|
|
defp version, do: FarmbotCore.Project.version()
|
|
end
|