diff --git a/lib/nerves_hub/devices.ex b/lib/nerves_hub/devices.ex index d693d38af..5a84d2417 100644 --- a/lib/nerves_hub/devices.ex +++ b/lib/nerves_hub/devices.ex @@ -1156,6 +1156,21 @@ defmodule NervesHub.Devices do update_device_with_audit(device, params, user, description) end + @spec move_many_to_deployment([integer()], integer()) :: {integer(), nil} + def move_many_to_deployment(device_ids, deployment_id) do + firmware = + Firmware + |> join(:inner, [f], d in Deployment, on: d.id == ^deployment_id) + |> limit(1) + |> Repo.one() + + Device + |> where([d], d.id in ^device_ids) + |> where([d], d.firmware_metadata["platform"] == ^firmware.platform) + |> where([d], d.firmware_metadata["architecture"] == ^firmware.architecture) + |> Repo.update_all(set: [deployment_id: deployment_id]) + end + @spec move_many([Device.t()], Product.t(), User.t()) :: %{ ok: [Device.t()], error: [{Ecto.Multi.name(), any()}] diff --git a/lib/nerves_hub/products/product.ex b/lib/nerves_hub/products/product.ex index 8cc649698..3a2c38ee1 100644 --- a/lib/nerves_hub/products/product.ex +++ b/lib/nerves_hub/products/product.ex @@ -4,6 +4,7 @@ defmodule NervesHub.Products.Product do alias NervesHub.Accounts.Org alias NervesHub.Archives.Archive + alias NervesHub.Deployments.Deployment alias NervesHub.Scripts.Script alias NervesHub.Devices.CACertificate alias NervesHub.Devices.Device @@ -22,6 +23,7 @@ defmodule NervesHub.Products.Product do has_many(:jitp, CACertificate.JITP) has_many(:archives, Archive) has_many(:scripts, Script) + has_many(:deployments, Deployment) has_many(:shared_secret_auths, SharedSecretAuth, preload_order: [desc: :deactivated_at, asc: :id] diff --git a/lib/nerves_hub_web/live/devices/index.ex b/lib/nerves_hub_web/live/devices/index.ex index 4557503d2..9ae9318ad 100644 --- a/lib/nerves_hub_web/live/devices/index.ex +++ b/lib/nerves_hub_web/live/devices/index.ex @@ -12,6 +12,7 @@ defmodule NervesHubWeb.Live.Devices.Index do alias NervesHub.Firmwares alias NervesHub.Products.Product alias NervesHub.Tracker + alias NervesHub.Repo alias Phoenix.Socket.Broadcast alias Phoenix.LiveView.JS @@ -73,8 +74,8 @@ defmodule NervesHubWeb.Live.Devices.Index do total_pages: :integer } - def mount(_params, _session, socket) do - %{product: product} = socket.assigns + def mount(_params, _session, %{assigns: %{product: product}} = socket) do + product = Repo.preload(product, :deployments) socket |> page_title("Devices - #{product.name}") @@ -93,6 +94,8 @@ defmodule NervesHubWeb.Live.Devices.Index do |> assign(:total_entries, 0) |> assign(:current_alarms, Alarms.get_current_alarm_types(product.id)) |> assign(:metrics_keys, Metrics.default_metrics()) + |> assign(:deployments, product.deployments) + |> assign(:target_deployment, nil) |> subscribe_and_refresh_device_list_timer() |> ok() end @@ -272,7 +275,18 @@ defmodule NervesHubWeb.Live.Devices.Index do {:noreply, assign(socket, target_product: target)} end - def handle_event("move-devices", _, socket) do + def handle_event("target-deployment", %{"deployment" => ""}, socket) do + {:noreply, assign(socket, target_deployment: nil)} + end + + def handle_event("target-deployment", %{"deployment" => deployment_id}, socket) do + deployment = + Enum.find(socket.assigns.deployments, &(&1.id == String.to_integer(deployment_id))) + + {:noreply, assign(socket, target_deployment: deployment)} + end + + def handle_event("move-devices-product", _, socket) do %{ok: successfuls} = Devices.get_devices_by_id(socket.assigns.selected_devices) |> Devices.move_many(socket.assigns.target_product, socket.assigns.user) @@ -289,6 +303,60 @@ defmodule NervesHubWeb.Live.Devices.Index do {:noreply, socket} end + def handle_event( + "move-devices-deployment", + _, + %{ + assigns: %{ + selected_devices: selected_devices, + target_deployment: target_deployment + } + } = socket + ) do + {devices_updated_count, _} = + Devices.move_many_to_deployment(selected_devices, target_deployment.id) + + devices_not_updated_count = length(selected_devices) - devices_updated_count + + maybe_pluralize = + &if &1 == 1 do + &2 + else + &2 <> "s" + end + + socket = + socket + |> assign(:target_deployment, nil) + |> assign_display_devices() + |> then(fn socket -> + case [devices_updated_count, devices_not_updated_count] do + [updated_count, 0] -> + put_flash( + socket, + :info, + "#{updated_count} #{maybe_pluralize.(updated_count, "device")} added to deployment #{target_deployment.name}" + ) + + [0, _not_updated_count] -> + put_flash( + socket, + :info, + "No devices selected could be added to deployment #{target_deployment.name} because of mismatched firmware" + ) + + [updated_count, not_updated_count] -> + put_flash( + socket, + :info, + "#{updated_count} #{maybe_pluralize.(updated_count, "device")} added to deployment #{target_deployment.name}. #{not_updated_count} #{maybe_pluralize.(not_updated_count, "device")} could not be added to deployment because of mismatched firmware" + ) + end + end) + + {:noreply, socket} + end + def handle_event("disable-updates-for-devices", _, socket) do %{ok: _successfuls} = Devices.get_devices_by_id(socket.assigns.selected_devices) diff --git a/lib/nerves_hub_web/live/devices/index.html.heex b/lib/nerves_hub_web/live/devices/index.html.heex index 3f707c2b5..81d87e67f 100644 --- a/lib/nerves_hub_web/live/devices/index.html.heex +++ b/lib/nerves_hub_web/live/devices/index.html.heex @@ -193,8 +193,8 @@