Add firmware update indicator to device detail page

- Load available firmware in DeviceLive.Show
- Add firmware_update_available? and format_date helpers
- Display blue indicator banner when newer firmware available
- Show current vs latest version with release date
- Add download link and release notes link
- Support MikroTik initially, extensible to other vendors

Phase 6 (final) of firmware tracking complete. All phases implemented:
 Database schema (firmware_releases, device_firmware_history)
 Version comparison logic (VersionComparator)
 RSS fetching worker (daily Oban cron)
 Firmware context with logging and PubSub
 Version change detection in Discovery
 LiveView UI indicators
This commit is contained in:
Graham McIntire 2026-02-01 10:49:00 -06:00
parent 61a06fc11c
commit fcf3ce154f
No known key found for this signature in database
2 changed files with 92 additions and 0 deletions

View file

@ -4,6 +4,8 @@ defmodule ToweropsWeb.DeviceLive.Show do
alias Towerops.Agents
alias Towerops.Devices
alias Towerops.Devices.Firmware
alias Towerops.Devices.VersionComparator
alias Towerops.Monitoring
alias Towerops.Repo
alias Towerops.Snmp
@ -233,6 +235,9 @@ defmodule ToweropsWeb.DeviceLive.Show do
# Group interfaces by type for the ports tab
interfaces_by_type = group_interfaces_by_type(snmp_data.interfaces)
# Check for available firmware updates
available_firmware = get_available_firmware(snmp_data.device)
socket
|> assign(:page_title, device.name)
|> assign(:timezone, socket.assigns.current_scope.timezone)
@ -267,6 +272,42 @@ defmodule ToweropsWeb.DeviceLive.Show do
|> assign(:wireless_sensors, wireless_sensors)
|> assign(:grouped_transceivers, grouped_transceivers)
|> assign(:agent_info, agent_info)
|> assign(:available_firmware, available_firmware)
end
defp get_available_firmware(nil), do: nil
defp get_available_firmware(snmp_device) do
vendor = determine_vendor(snmp_device.manufacturer)
product_line = determine_product_line(snmp_device.manufacturer)
if vendor do
Firmware.get_latest_firmware_release(vendor, product_line)
end
end
defp determine_vendor("MikroTik"), do: "mikrotik"
defp determine_vendor("Cisco"), do: "cisco"
defp determine_vendor("Ubiquiti"), do: "ubiquiti"
defp determine_vendor(_), do: nil
defp determine_product_line("MikroTik"), do: "routeros"
defp determine_product_line(_), do: nil
defp firmware_update_available?(nil, _), do: false
defp firmware_update_available?(_, nil), do: false
defp firmware_update_available?(snmp_device, available_firmware) do
current = snmp_device.firmware_version
available = available_firmware.version
current && available && VersionComparator.newer?(current, available)
end
defp format_date(nil), do: ""
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
defp load_agent_info(nil, :none) do

View file

@ -205,6 +205,57 @@
<div class="mt-6">
<%= case @active_tab do %>
<% "overview" -> %>
<!-- Firmware Update Indicator -->
<%= if firmware_update_available?(@snmp_device, @available_firmware) do %>
<div class="mb-6 rounded-md bg-blue-50 dark:bg-blue-900/20 p-4 border border-blue-200 dark:border-blue-800">
<div class="flex">
<div class="flex-shrink-0">
<.icon name="hero-arrow-up-circle" class="h-5 w-5 text-blue-400" />
</div>
<div class="ml-3 flex-1">
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">
Firmware Update Available
</h3>
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
<p>
Current version:
<span class="font-mono font-semibold">{@snmp_device.firmware_version}</span>
<br /> Latest version:
<span class="font-mono font-semibold">{@available_firmware.version}</span>
<%= if @available_firmware.release_date do %>
<span class="text-xs">
(released {format_date(@available_firmware.release_date)})
</span>
<% end %>
</p>
</div>
<div class="mt-3">
<a
href={@available_firmware.download_url}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Download Firmware
<.icon name="hero-arrow-top-right-on-square" class="ml-1.5 h-4 w-4" />
</a>
<%= if @available_firmware.changelog_url do %>
<a
href={@available_firmware.changelog_url}
target="_blank"
rel="noopener noreferrer"
class="ml-3 inline-flex items-center text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500"
>
View Release Notes
<.icon name="hero-arrow-top-right-on-square" class="ml-1 h-4 w-4" />
</a>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
<!-- Left Column: Device Information & Traffic -->
<div class="space-y-6">