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 %>
+
+
+ <%= 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