Skip to content

Commit 65ee8aa

Browse files
lawiknshoes
andauthored
Make device list refresh in a live but safe way (#2260)
For the purpose of making it possible to watch live updates more information should refresh on connection changes. But it should be throttled in a responsible way. We can even let fwup_progress events indicate that things should refresh if the pace is safe. One big addition to making this safe is to determine which tabs are not being watched and if they are not watche we don't update them. This improves on the 10 second timer updater. --------- Co-authored-by: Nate Shoemaker <[email protected]>
1 parent 2f0782f commit 65ee8aa

File tree

4 files changed

+109
-12
lines changed

4 files changed

+109
-12
lines changed

assets/ui-rework/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import HighlightCode from "./hooks/highlightCode.js"
1616
import LocalShell from "./hooks/localShell.js"
1717
import LocalTime from "./hooks/localTime.js"
1818
import LogLineLocalTime from "./hooks/logLineLocalTime.js"
19+
import PageVisible from "./hooks/pageVisible.js"
1920
import SharedSecretClipboardClick from "./hooks/sharedSecretClipboardClick.js"
2021
import SimpleDate from "./hooks/simpleDate.js"
2122
import SupportScriptOutput from "./hooks/supportScriptOutput.js"
@@ -48,6 +49,7 @@ let liveSocket = new LiveSocket("/live", Socket, {
4849
LocalShell,
4950
LocalTime,
5051
LogLineLocalTime,
52+
PageVisible,
5153
SharedSecretClipboardClick,
5254
SimpleDate,
5355
SupportScriptOutput,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
mounted() {
3+
this.init()
4+
},
5+
init() {
6+
document.addEventListener("visibilitychange", () => {
7+
this.pushEvent("page_visibility_change", {
8+
visible: document.visibilityState === "visible"
9+
})
10+
})
11+
}
12+
}

lib/nerves_hub_web/live/devices/index.ex

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ defmodule NervesHubWeb.Live.Devices.Index do
2525
alias NervesHubWeb.LayoutView.DateTimeFormat
2626

2727
@list_refresh_time 10_000
28+
# Delay frequent refresh triggers to this interval
29+
@refresh_delay 1000
2830

2931
@default_filters %{
3032
connection: "",
@@ -107,6 +109,10 @@ defmodule NervesHubWeb.Live.Devices.Index do
107109
|> assign(:valid_tags, true)
108110
|> assign(:device_tags, "")
109111
|> assign(:total_entries, 0)
112+
|> assign(:visible?, true)
113+
|> assign(:live_refresh_timer, nil)
114+
|> assign(:live_refresh_pending?, false)
115+
|> assign(:received_connection_change_identifiers, [])
110116
|> assign(:current_alarms, [])
111117
|> assign(:metrics_keys, [])
112118
|> assign(:deployment_groups, [])
@@ -415,42 +421,83 @@ defmodule NervesHubWeb.Live.Devices.Index do
415421
|> noreply()
416422
end
417423

424+
def handle_event("page_visibility_change", %{"visible" => visible?}, socket) do
425+
socket
426+
|> then(fn socket ->
427+
# refresh if switching to visible from non-visible
428+
if not socket.assigns.visible? and visible? do
429+
safe_refresh(socket)
430+
else
431+
socket
432+
end
433+
end)
434+
|> assign(visible?: visible?)
435+
|> noreply()
436+
end
437+
418438
def handle_info(%Broadcast{event: "connection:status", payload: payload}, socket) do
419-
update_device_statuses(socket, payload)
439+
socket
440+
|> assign(:received_connection_change_identifiers, [payload | socket.assigns.received_connection_change_identifiers])
441+
|> safe_refresh()
442+
|> update_device_statuses(payload)
420443
end
421444

422445
def handle_info(%Broadcast{event: "connection:change", payload: payload}, socket) do
423-
update_device_statuses(socket, payload)
446+
socket
447+
|> assign(:received_connection_change_identifiers, [payload | socket.assigns.received_connection_change_identifiers])
448+
|> safe_refresh()
449+
|> update_device_statuses(payload)
424450
end
425451

426452
def handle_info(%Broadcast{event: "fwup_progress", payload: %{device_id: device_id, percent: percent}}, socket)
427453
when percent > 99 do
428454
socket
429455
|> assign(:progress, Map.delete(socket.assigns.progress, device_id))
456+
|> safe_refresh()
430457
|> noreply()
431458
end
432459

433460
def handle_info(%Broadcast{event: "fwup_progress", payload: %{device_id: device_id, percent: percent}}, socket) do
434461
socket
435462
|> assign(:progress, Map.put(socket.assigns.progress, device_id, percent))
463+
|> safe_refresh()
436464
|> noreply()
437465
end
438466

439467
# Unknown broadcasts get ignored, likely from the device:id:internal channel
440468
def handle_info(%Broadcast{}, socket) do
441-
{:noreply, socket}
469+
socket
470+
|> safe_refresh()
471+
|> noreply()
442472
end
443473

444474
def handle_info(:refresh_device_list, socket) do
445-
Tracer.with_span "NervesHubWeb.Live.Devices.Index.refresh_device_list" do
446-
Process.send_after(self(), :refresh_device_list, @list_refresh_time)
475+
if socket.assigns.visible? do
476+
Tracer.with_span "NervesHubWeb.Live.Devices.Index.refresh_device_list" do
477+
Process.send_after(self(), :refresh_device_list, @list_refresh_time)
447478

448-
if socket.assigns.paginate_opts.total_pages == 1 do
449-
{:noreply, assign_display_devices(socket)}
450-
else
451-
{:noreply, socket}
479+
socket
480+
|> safe_refresh()
481+
|> noreply()
482+
end
483+
484+
socket
485+
|> noreply()
486+
end
487+
end
488+
489+
def handle_info(:live_refresh, socket) do
490+
if socket.assigns.visible? and socket.assigns.live_refresh_pending? do
491+
Tracer.with_span "NervesHubWeb.Live.Devices.Index.live_refresh_device_list" do
492+
socket
493+
|> assign_display_devices()
452494
end
495+
else
496+
socket
453497
end
498+
|> assign(:live_refresh_timer, nil)
499+
|> assign(:live_refresh_pending?, false)
500+
|> noreply()
454501
end
455502

456503
defp assign_filter_data(%{assigns: %{product: product}} = socket) do
@@ -490,12 +537,22 @@ defmodule NervesHubWeb.Live.Devices.Index do
490537
Map.new(updated_devices, fn device ->
491538
socket.endpoint.subscribe("device:#{device.identifier}:internal")
492539

493-
{device.identifier, Tracker.connection_status(device)}
540+
payload =
541+
Enum.find(socket.assigns.received_connection_change_identifiers, fn %{device_id: identifier} ->
542+
identifier == device.identifier
543+
end)
544+
545+
if payload do
546+
{payload.device_id, payload.status}
547+
else
548+
{device.identifier, Tracker.connection_status(device)}
549+
end
494550
end)
495551

496552
socket
497553
|> assign(:devices, AsyncResult.ok(old_devices, updated_devices))
498554
|> assign(:device_statuses, AsyncResult.ok(old_device_statuses, updated_device_statuses))
555+
|> assign(:received_connection_change_identifiers, [])
499556
|> device_pagination_assigns(paginate_opts, pager)
500557
|> noreply()
501558
end
@@ -724,4 +781,17 @@ defmodule NervesHubWeb.Live.Devices.Index do
724781
defp has_results?(%AsyncResult{} = device_async, currently_filtering?) do
725782
device_async.ok? && (Enum.any?(device_async.result) || currently_filtering?)
726783
end
784+
785+
defp safe_refresh(socket) do
786+
if is_nil(socket.assigns.live_refresh_timer) and socket.assigns.visible? do
787+
# Nothing pending, we perform a refresh
788+
socket
789+
|> assign_display_devices()
790+
|> assign(:live_refresh_timer, Process.send_after(self(), :live_refresh, @refresh_delay))
791+
else
792+
# a timer is already pending, we flag the pending request
793+
socket
794+
|> assign(:live_refresh_pending?, true)
795+
end
796+
end
727797
end

lib/nerves_hub_web/live/devices/index.html.heex

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
<div class="h-[56px] flex justify-end bg-base-900 border-b border-base-700"></div>
1+
<div class="h-[56px] flex justify-end bg-base-900 border-b border-base-700">
2+
<div class="h-full border-l flex items-center justify-center border-base-700 bg-base-900">
3+
<a :if={Application.get_env(:nerves_hub, :new_ui)} href={"/ui/switch?return_to=#{@current_path}"} class="">
4+
<svg class="box-content px-5 h-5 w-5 stroke-zinc-500 hover:stroke-indigo-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5+
<path
6+
d="M21 14V19C21 20.1046 20.1046 21 19 21H16M3 16V19C3 20.1046 3.89543 21 5 21H16M3 16V5C3 3.89543 3.89543 3 5 3H11M3 16C4.40293 15.7662 6.63687 15.7073 8.94504 16.2427M16 21C14.2965 18.2317 11.5726 16.8522 8.94504 16.2427M8.94504 16.2427C9.87157 15.1698 11.1851 14.1585 13 13.3925M8.5 7C8 7 7 7.3 7 8.5C7 9.7 8 10 8.5 10C9 10 10 9.7 10 8.5C10 7.3 9 7 8.5 7ZM17.5 9.46262L14.7188 11L15.25 7.74377L13 5.43769L16.1094 4.96262L17.5 2L18.8906 4.96262L22 5.43769L19.75 7.74377L20.2812 11L17.5 9.46262Z"
7+
stroke-width="1.2"
8+
stroke-linecap="round"
9+
stroke-linejoin="round"
10+
/>
11+
</svg>
12+
</a>
13+
</div>
14+
</div>
215

3-
<div class="h-0 flex-1 overflow-y-auto">
16+
<div id="device-container" class="h-0 flex-1 overflow-y-auto" phx-hook="PageVisible">
417
<div class="flex items-center h-[90px] gap-4 px-6 py-7 border-b border-[#3F3F46] text-sm font-medium">
518
<h1 :if={!@devices.ok?} class="text-xl leading-[30px] font-semibold text-neutral-50">Loading...</h1>
619
<h1 :if={@devices.ok?} id="devices-header" class="text-xl leading-[30px] font-semibold text-neutral-50 hidden" phx-mounted={fade_in("#devices-header")}>

0 commit comments

Comments
 (0)