From d0cfc57c15d19ea3beebdb3fcfe352f03c1fcdff Mon Sep 17 00:00:00 2001 From: Elin Olsson Date: Tue, 10 Dec 2024 23:12:30 +0100 Subject: [PATCH] Add script section to device UI (#1689) Solves #1681 Adds a simple section on device page for listing and running support scripts. Not too much time spent on the looks since the whole UI is redesigned. --- lib/nerves_hub_web/helpers/authorization.ex | 1 + lib/nerves_hub_web/live/devices/show.ex | 68 +++++++++++++++++++ .../live/devices/show.html.heex | 23 +++++++ .../nerves_hub_web/live/devices/show_test.exs | 29 ++++++++ 4 files changed, 121 insertions(+) diff --git a/lib/nerves_hub_web/helpers/authorization.ex b/lib/nerves_hub_web/helpers/authorization.ex index c7ebabfd8..2bb10e39a 100644 --- a/lib/nerves_hub_web/helpers/authorization.ex +++ b/lib/nerves_hub_web/helpers/authorization.ex @@ -58,6 +58,7 @@ defmodule NervesHub.Helpers.Authorization do def authorized?(:"support_script:create", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"support_script:update", %OrgUser{role: role}), do: role_check(:manage, role) def authorized?(:"support_script:delete", %OrgUser{role: role}), do: role_check(:manage, role) + def authorized?(:"support_script:run", %OrgUser{role: role}), do: role_check(:view, role) defp role_check(required_role, user_role) do required_role diff --git a/lib/nerves_hub_web/live/devices/show.ex b/lib/nerves_hub_web/live/devices/show.ex index b77d1f40a..e364470b7 100644 --- a/lib/nerves_hub_web/live/devices/show.ex +++ b/lib/nerves_hub_web/live/devices/show.ex @@ -10,6 +10,7 @@ defmodule NervesHubWeb.Live.Devices.Show do alias NervesHub.Devices.Metrics alias NervesHub.Devices.UpdatePayload alias NervesHub.Firmwares + alias NervesHub.Scripts alias NervesHub.Tracker alias NervesHubWeb.Components.AuditLogFeed @@ -20,6 +21,8 @@ defmodule NervesHubWeb.Live.Devices.Show do alias Phoenix.Socket.Broadcast + @running_script_placeholder "Running Script.." + def mount(%{"device_identifier" => device_identifier}, _session, socket) do %{org: org, product: product} = socket.assigns @@ -44,6 +47,7 @@ defmodule NervesHubWeb.Live.Devices.Show do |> assign(:alarms, Alarms.get_current_alarms_for_device(device)) |> assign(:extension_overrides, extension_overrides(device, product)) |> assign(:latest_metrics, Metrics.get_latest_metric_set(device.id)) + |> assign(:scripts, scripts_with_output(product)) |> assign_metadata() |> schedule_health_check_timer() |> assign(:fwup_progress, nil) @@ -316,6 +320,63 @@ defmodule NervesHubWeb.Live.Devices.Show do end end + def handle_event( + "run-script", + %{"idx" => index}, + %{assigns: %{device: device, scripts: scripts, org_user: org_user}} = socket + ) do + authorized!(:"support_script:run", org_user) + + {script, idx} = Enum.at(scripts, String.to_integer(index)) + + socket + |> assign(:scripts, update_script_output(scripts, idx, @running_script_placeholder)) + |> start_async({:run_script, idx}, fn -> Scripts.Runner.send(device, script) end) + |> noreply() + end + + def handle_event( + "clear-script-output", + %{"idx" => index}, + %{assigns: %{scripts: scripts}} = socket + ) do + socket + |> assign(:scripts, update_script_output(scripts, String.to_integer(index), nil)) + |> noreply() + end + + def handle_async( + {:run_script, index}, + result, + %{assigns: %{scripts: scripts}} = socket + ) do + output = + case result do + {:ok, output} -> + output + + e -> + inspect(e) + end + + socket + |> assign(:scripts, update_script_output(scripts, index, output)) + |> noreply() + end + + defp scripts_with_output(product) do + product + |> Scripts.all_by_product() + |> Enum.map(&Map.put(&1, :output, nil)) + |> Enum.with_index() + end + + defp update_script_output(scripts, index, output) do + List.update_at(scripts, index, fn {script, idx} -> + {%{script | output: output}, idx} + end) + end + defp device_connection(%{device_connections: [connection]}), do: connection defp device_connection(_), do: nil @@ -385,4 +446,11 @@ defmodule NervesHubWeb.Live.Devices.Show do end) |> Enum.map(&elem(&1, 0)) end + + defp running_script_placeholder(), do: @running_script_placeholder + + defp script_button_text(output) when output == @running_script_placeholder or is_nil(output), + do: "Run" + + defp script_button_text(_), do: "Close" end diff --git a/lib/nerves_hub_web/live/devices/show.html.heex b/lib/nerves_hub_web/live/devices/show.html.heex index d4c56b865..7ea49f391 100644 --- a/lib/nerves_hub_web/live/devices/show.html.heex +++ b/lib/nerves_hub_web/live/devices/show.html.heex @@ -96,6 +96,29 @@ <% end %> + +
+

Support Scripts

+ +
+
+
+
<%= script.name %>
+ +
+
+ <%= script.output %> +
+
+
+ <.link navigate={~p"/org/#{@org.name}/#{@product.name}/scripts"}> + Add and edit scripts + +
+
+
diff --git a/test/nerves_hub_web/live/devices/show_test.exs b/test/nerves_hub_web/live/devices/show_test.exs index c5be8c238..68ecba810 100644 --- a/test/nerves_hub_web/live/devices/show_test.exs +++ b/test/nerves_hub_web/live/devices/show_test.exs @@ -392,6 +392,35 @@ defmodule NervesHubWeb.Live.Devices.ShowTest do end end + describe "support scripts" do + test "no scripts available", %{ + conn: conn, + org: org, + product: product, + device: device + } do + conn + |> visit("/org/#{org.name}/#{product.name}/devices/#{device.identifier}") + |> refute_has("h3", text: "Support Scripts") + end + + test "list scripts", %{ + conn: conn, + org: org, + product: product, + device: device + } do + {:ok, _command} = + NervesHub.Scripts.create(product, %{name: "MOTD", text: "NervesMOTD.print()"}) + + conn + |> visit("/org/#{org.name}/#{product.name}/devices/#{device.identifier}") + |> assert_has("h3", text: "Support Scripts") + |> assert_has("div", text: "MOTD") + |> assert_has("button", text: "Run") + end + end + def device_show_path(%{device: device, org: org, product: product}) do ~p"/org/#{org.name}/#{product.name}/devices/#{device.identifier}" end