diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 706e76d7a..d817ddc54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - main tags: - - 'v*' + - "v*" workflow_dispatch: pull_request: @@ -26,7 +26,7 @@ jobs: services: db: image: postgres:15 - ports: ['5432:5432'] + ports: ["5432:5432"] env: POSTGRES_PASSWORD: postgres options: >- @@ -121,6 +121,8 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + env: + DOCKER_METADATA_PR_HEAD_SHA: true with: images: ghcr.io/nerves-hub/nerves-hub tags: | @@ -132,14 +134,15 @@ jobs: type=raw,enable={{is_default_branch}},value=latest # tag event (eg. "v1.2.3") type=ref,event=tag - + - name: Check if PR publish + continue-on-error: true if: ${{ github.event_name == 'pull_request' }} id: pr_publish_check env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - commits=$(gh pr view ${{ github.head_ref }} --json commits --jq '.commits[] | .messageHeadline + " " + .messageBody') + commits=$(gh pr view ${{ github.event.pull_request.number }} --json commits --jq '.commits[] | .messageHeadline + " " + .messageBody') if [[ $commits =~ \[publish\] ]]; then echo "true" diff --git a/assets/css/_custom.scss b/assets/css/_custom.scss index 25a78d3c6..1d4a11d61 100644 --- a/assets/css/_custom.scss +++ b/assets/css/_custom.scss @@ -13,7 +13,18 @@ right: 0; margin: auto; z-index: 999; - // text-align: center; +} + +.alarms-banner { + display: flex; + flex-direction: row; + justify-content: right; + padding-top: 2px; + padding-bottom: 2px; +} + +.alarms-banner a { + color: #f78080; } .opacity-1 { @@ -244,19 +255,28 @@ } } -.device-limit-indicator { - background-image: url("/images/icons/device.svg"); - background-position: right center; +.navbar-indicator { + background-position: left center; background-repeat: no-repeat; background-size: 1.5rem; - padding-right: 2rem; - letter-spacing: 4px; + padding-left: 1.8rem; + letter-spacing: 2px; @media(max-width: 860px) { display: none; } } +.device-limit-indicator { + background-image: url("/images/icons/device.svg"); +} + +.alarms-indicator { + background-image: url("/images/icons/bell.svg"); + margin-left: 1.3rem; + font-weight: 400; +} + html { .btn-group>form:not(:first-child) .btn { margin-left: -1px; @@ -345,6 +365,7 @@ html { cursor: pointer; display: flex; gap: 4px; + label { font-size: 14px; border-radius: 3.2px; @@ -360,6 +381,7 @@ html { padding-right: 8px; justify-content: center; flex-grow: 1; + input[type=radio] { appearance: none; } diff --git a/assets/css/_form.scss b/assets/css/_form.scss index c24d01dfa..65af37270 100644 --- a/assets/css/_form.scss +++ b/assets/css/_form.scss @@ -267,7 +267,7 @@ html { .filter-form { &.device-filters { display: grid; - grid-template-columns: 3fr 3fr 3fr 2fr 2fr; + grid-template-columns: 2fr 2fr 2fr 2fr 3fr; grid-column-gap: 1rem; @media (max-width: 860px) { diff --git a/assets/static/images/icons/bell.svg b/assets/static/images/icons/bell.svg new file mode 100644 index 000000000..66b56a90e --- /dev/null +++ b/assets/static/images/icons/bell.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/lib/nerves_hub/devices.ex b/lib/nerves_hub/devices.ex index 3280e085b..cf683f2e8 100644 --- a/lib/nerves_hub/devices.ex +++ b/lib/nerves_hub/devices.ex @@ -13,6 +13,7 @@ defmodule NervesHub.Devices do alias NervesHub.Deployments.Deployment alias NervesHub.Deployments.Orchestrator alias NervesHub.Devices.CACertificate + alias NervesHub.Devices.Alarms alias NervesHub.Devices.Device alias NervesHub.Devices.DeviceCertificate alias NervesHub.Devices.DeviceHealth @@ -163,6 +164,15 @@ defmodule NervesHub.Devices do {_, ""} -> query + {:alarm_status, "with"} -> + where(query, [d], d.id in subquery(Alarms.query_devices_with_alarms())) + + {:alarm_status, "without"} -> + where(query, [d], d.id not in subquery(Alarms.query_devices_with_alarms())) + + {:alarm, value} -> + where(query, [d], d.id in subquery(Alarms.query_devices_with_alarm(value))) + {:connection, _value} -> where(query, [d], d.connection_status == ^String.to_atom(value)) diff --git a/lib/nerves_hub/devices/alarms.ex b/lib/nerves_hub/devices/alarms.ex new file mode 100644 index 000000000..13f7b0aa5 --- /dev/null +++ b/lib/nerves_hub/devices/alarms.ex @@ -0,0 +1,90 @@ +defmodule NervesHub.Devices.Alarms do + import Ecto.Query + alias NervesHub.Repo + alias NervesHub.Devices.Device + alias NervesHub.Devices.DeviceHealth + + @doc """ + Selects device id:s for devices that has alarm(s) in it's latest health record. + Used when filtering devices. + """ + def query_devices_with_alarms() do + (lr in subquery(latest_row_query())) + |> from() + |> where([lr], lr.rn == 1) + |> where([lr], fragment("?->'alarms' != '{}'", lr.data)) + |> join(:inner, [lr], d in Device, on: lr.device_id == d.id) + |> select([lr, o], o.id) + end + + @doc """ + Selects device id:s for devices that has provided alarm in it's latest health record. + Used when filtering devices. + """ + def query_devices_with_alarm(alarm) do + (lr in subquery(latest_row_query())) + |> from() + |> where([lr], lr.rn == 1) + |> where( + [lr], + fragment( + "EXISTS (SELECT 1 FROM jsonb_each_text(?) WHERE value ILIKE ?)", + lr.data, + ^"%#{alarm}%" + ) + ) + |> join(:inner, [lr], d in Device, on: lr.device_id == d.id) + |> select([lr, o], o.id) + end + + @doc """ + Creates a list with all current alarm types for a product. + """ + def get_current_alarm_types(product_id) do + query_current_alarms(product_id) + |> Repo.all() + |> Enum.map(fn %{data: data} -> + Map.keys(data["alarms"]) + end) + |> List.flatten() + |> Enum.uniq() + |> Enum.map(&String.trim_leading(&1, "Elixir.")) + end + + @doc """ + Counts number of devices currently alarming, within a product. + """ + def current_alarms_count(product_id) do + product_id + |> query_current_alarms() + |> select([a], count(a)) + |> Repo.one!() + end + + @doc """ + Selects latest health per device if alarms is populated and device belongs to product. + """ + def query_current_alarms(product_id) do + (lr in subquery(latest_row_query())) + |> from() + |> where([lr], lr.rn == 1) + |> where([lr], fragment("?->'alarms' != '{}'", lr.data)) + |> where([lr], lr.device_id in subquery(device_product_query(product_id))) + end + + defp latest_row_query() do + DeviceHealth + |> select([dh], %{ + device_id: dh.device_id, + data: dh.data, + inserted_at: dh.inserted_at, + rn: row_number() |> over(partition_by: dh.device_id, order_by: [desc: dh.inserted_at]) + }) + end + + defp device_product_query(product_id) do + Device + |> select([:id]) + |> where(product_id: ^product_id) + end +end diff --git a/lib/nerves_hub_web/channels/device_channel.ex b/lib/nerves_hub_web/channels/device_channel.ex index eeb15596a..04b20004d 100644 --- a/lib/nerves_hub_web/channels/device_channel.ex +++ b/lib/nerves_hub_web/channels/device_channel.ex @@ -340,7 +340,7 @@ defmodule NervesHubWeb.DeviceChannel do end def handle_in("fwup_progress", %{"value" => percent}, %{assigns: %{device: device}} = socket) do - device_internal_broadcast!(device, "fwup_progress", %{percent: percent}) + device_internal_broadcast!(socket, device, "fwup_progress", %{percent: percent}) # if this is the first fwup we see, then mark it as an update attempt if socket.assigns[:update_started?] do @@ -370,9 +370,11 @@ defmodule NervesHubWeb.DeviceChannel do {:ok, device} = Devices.update_device(device, %{connection_metadata: metadata}) - device_internal_broadcast!(device, "location:updated", location) + socket = assign(socket, :device, device) - {:reply, :ok, assign(socket, :device, device)} + device_internal_broadcast!(socket, device, "location:updated", location) + + {:reply, :ok, socket} end def handle_in("connection_types", %{"values" => types}, %{assigns: %{device: device}} = socket) do @@ -434,7 +436,7 @@ defmodule NervesHubWeb.DeviceChannel do {:health_report, Devices.save_device_health(device_health)}, {:metrics_report, {_, _}} <- {:metrics_report, Metrics.save_metrics(socket.assigns.device.id, metrics)} do - device_internal_broadcast!(socket.assigns.device, "health_check_report", %{}) + device_internal_broadcast!(socket, socket.assigns.device, "health_check_report", %{}) else {:health_report, {:error, err}} -> Logger.warning("Failed to save health check data: #{inspect(err)}") @@ -513,9 +515,9 @@ defmodule NervesHubWeb.DeviceChannel do Phoenix.PubSub.unsubscribe(NervesHub.PubSub, topic) end - defp device_internal_broadcast!(device, event, payload) do + defp device_internal_broadcast!(socket, device, event, payload) do topic = "device:#{device.identifier}:internal" - NervesHubWeb.DeviceEndpoint.broadcast_from!(self(), topic, event, payload) + socket.endpoint.broadcast_from!(self(), topic, event, payload) end defp maybe_send_public_keys(device, socket, params) do diff --git a/lib/nerves_hub_web/components/navigation.ex b/lib/nerves_hub_web/components/navigation.ex index 03d709dbe..bf283901b 100644 --- a/lib/nerves_hub_web/components/navigation.ex +++ b/lib/nerves_hub_web/components/navigation.ex @@ -2,6 +2,7 @@ defmodule NervesHubWeb.Components.Navigation do use NervesHubWeb, :component alias NervesHub.Devices + alias NervesHub.Devices.Alarms alias NervesHub.Products.Product import NervesHubWeb.Components.SimpleActiveLink @@ -115,6 +116,7 @@ defmodule NervesHubWeb.Components.Navigation do if Enum.any?(links) do assigns = %{ + org: assigns.org, product: assigns.product, links: links } @@ -129,8 +131,19 @@ defmodule NervesHubWeb.Components.Navigation do -
- <%= device_count %> +
+ + <.link + :if={alarms_count = alarms_count(@product)} + navigate={~p"/org/#{@org.name}/#{@product.name}/devices?alarm_status=with"} + class="navbar-indicator alarms-indicator" + title="Devices alarming" + aria-label="Devices alarming" + > + <%= alarms_count %> +
@@ -308,4 +321,7 @@ defmodule NervesHubWeb.Components.Navigation do def device_count(_conn) do nil end + + def alarms_count(%Product{} = product), do: Alarms.current_alarms_count(product.id) + def alarms_count(_conn), do: nil end diff --git a/lib/nerves_hub_web/live/devices/index.ex b/lib/nerves_hub_web/live/devices/index.ex index 5854b2b55..12067787d 100644 --- a/lib/nerves_hub_web/live/devices/index.ex +++ b/lib/nerves_hub_web/live/devices/index.ex @@ -7,6 +7,7 @@ defmodule NervesHubWeb.Live.Devices.Index do alias NervesHub.AuditLogs alias NervesHub.Devices + alias NervesHub.Devices.Alarms alias NervesHub.Firmwares alias NervesHub.Products.Product alias NervesHub.Tracker @@ -29,7 +30,9 @@ defmodule NervesHubWeb.Live.Devices.Index do device_id: "", tag: "", updates: "", - has_no_tags: false + has_no_tags: false, + alarm_status: "", + alarm: "" } @filter_types %{ @@ -41,7 +44,9 @@ defmodule NervesHubWeb.Live.Devices.Index do device_id: :string, tag: :string, updates: :string, - has_no_tags: :boolean + has_no_tags: :boolean, + alarm_status: :string, + alarm: :string } @default_page 1 @@ -79,6 +84,7 @@ defmodule NervesHubWeb.Live.Devices.Index do |> assign(:valid_tags, true) |> assign(:device_tags, "") |> assign(:total_entries, 0) + |> assign(:current_alarms, Alarms.get_current_alarm_types(product.id)) |> subscribe_and_refresh_device_list_timer() |> ok() end diff --git a/lib/nerves_hub_web/live/devices/index.html.heex b/lib/nerves_hub_web/live/devices/index.html.heex index e2d7b4e41..df705b32a 100644 --- a/lib/nerves_hub_web/live/devices/index.html.heex +++ b/lib/nerves_hub_web/live/devices/index.html.heex @@ -132,6 +132,29 @@
+
+ +
+ +
+
+
+
+ +
+ +
+
+
diff --git a/mix.lock b/mix.lock index 4a553ed23..a9ad027f2 100644 --- a/mix.lock +++ b/mix.lock @@ -4,7 +4,7 @@ "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, - "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, + "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"}, @@ -31,7 +31,7 @@ "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, - "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, @@ -84,7 +84,7 @@ "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "scrivener": {:hex, :scrivener, "2.7.2", "1d913c965ec352650a7f864ad7fd8d80462f76a32f33d57d1e48bc5e9d40aba2", [:mix], [], "hexpm", "7866a0ec4d40274efbee1db8bead13a995ea4926ecd8203345af8f90d2b620d9"}, @@ -94,7 +94,7 @@ "slipstream": {:hex, :slipstream, "1.1.2", "368d84dab2aed061aaa83762c28a0fe1fcb42a7eb8a7019a67c58de06d486fb9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint_web_socket, "~> 0.2 or ~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.1 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "585abc367f522efd1946f539129849f09b65be46a166c8de67db392861bedcb9"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, - "swoosh": {:hex, :swoosh, "1.17.2", "73611f08fc7cb9fa15f4909db36eeb12b70727d5c8b6a7fa0d4a31c6575db29e", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"}, + "swoosh": {:hex, :swoosh, "1.17.3", "5cda7bff6bc1121cc5b58db8ed90ef33261b373425ae3e32dd599688037a0482", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "14ad57cfbb70af57323e17f569f5840a33c01f8ebc531dd3846beef3c9c95e55"}, "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, diff --git a/test/nerves_hub_web/live/devices/index_test.exs b/test/nerves_hub_web/live/devices/index_test.exs index afd86f388..29d77cd34 100644 --- a/test/nerves_hub_web/live/devices/index_test.exs +++ b/test/nerves_hub_web/live/devices/index_test.exs @@ -131,6 +131,64 @@ defmodule NervesHubWeb.Live.Devices.IndexTest do refute change =~ device3.identifier end + test "filters devices with alarms", %{conn: conn, fixture: fixture} do + %{device: device, firmware: firmware, org: org, product: product} = fixture + + device2 = Fixtures.device_fixture(org, product, firmware) + + {:ok, view, html} = live(conn, device_index_path(fixture)) + assert html =~ device.identifier + assert html =~ device2.identifier + assert html =~ "2 devices found" + + device_health = %{"device_id" => device.id, "data" => %{"alarms" => %{"SomeAlarm" => []}}} + assert {:ok, _} = NervesHub.Devices.save_device_health(device_health) + + change = render_change(view, "update-filters", %{"alarm_status" => "with"}) + assert change =~ device.identifier + refute change =~ device2.identifier + assert change =~ "1 devices found" + end + + test "filters devices without alarms", %{conn: conn, fixture: fixture} do + %{device: device, firmware: firmware, org: org, product: product} = fixture + + device2 = Fixtures.device_fixture(org, product, firmware) + + {:ok, view, html} = live(conn, device_index_path(fixture)) + assert html =~ device.identifier + assert html =~ device2.identifier + assert html =~ "2 devices found" + + device_health = %{"device_id" => device.id, "data" => %{"alarms" => %{"SomeAlarm" => []}}} + assert {:ok, _} = NervesHub.Devices.save_device_health(device_health) + + change = render_change(view, "update-filters", %{"alarm_status" => "without"}) + refute change =~ device.identifier + assert change =~ device2.identifier + assert change =~ "1 devices found" + end + + test "filters devices with specific alarm", %{conn: conn, fixture: fixture} do + %{device: device, firmware: firmware, org: org, product: product} = fixture + + device2 = Fixtures.device_fixture(org, product, firmware) + + {:ok, view, html} = live(conn, device_index_path(fixture)) + assert html =~ device.identifier + assert html =~ device2.identifier + assert html =~ "2 devices found" + + alarm = "SomeAlarm" + device_health = %{"device_id" => device.id, "data" => %{"alarms" => %{alarm => []}}} + assert {:ok, _} = NervesHub.Devices.save_device_health(device_health) + + change = render_change(view, "update-filters", %{"alarm" => alarm}) + assert change =~ device.identifier + refute change =~ device2.identifier + assert change =~ "1 devices found" + end + test "select device", %{conn: conn, fixture: fixture} do %{device: _device, firmware: firmware, org: org, product: product} = fixture