towerops/lib/towerops_web/live/network_map_live.html.heex
Graham McIntie 6321a974a9 TOW-44: Hide RF Links UI elements from network map (#45)
## Summary

Hide RF link-related UI elements from the network map page while keeping all backend code intact.

### Changes
- Commented out "View RF Links" button in network map node detail panel
- Commented out RF Link health legend (Good signal / Degraded / Critical) from map legend
- Backend topology.ex code untouched — `compute_rf_link_stats/1` and `enrich_edges_with_rf/2` remain
- No RF tab exists on the device show page, so no changes needed there

### Verification
- `mix compile --warnings-as-errors` — zero warnings
- `mix test` — 8490 tests pass, 0 failures

Paperclip issue: TOW-44

Reviewed-on: graham/towerops-web#45
2026-03-16 15:12:09 -05:00

628 lines
28 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="network-map"
>
<.header>
<span class="flex items-center gap-2">
{t("Network Map")}
<span class="inline-flex items-center rounded-full bg-blue-50 px-2 py-0.5 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
{t("Experimental")}
</span>
</span>
<:subtitle>{t("Visual topology of your network infrastructure")}</:subtitle>
<:actions>
<div class="flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-500 font-mono">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
</span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
<span>Live</span>
</div>
</:actions>
</.header>
<!-- Tab Navigation -->
<div class="border-b border-gray-200 dark:border-white/10 mb-6">
<nav class="-mb-px flex space-x-8">
<.link
patch={~p"/network-map?tab=added"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
if @active_tab == "added" do
"border-blue-500 text-blue-600 dark:text-blue-400"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
end
]}
>
{t("Added Devices")}
</.link>
<.link
patch={~p"/network-map?tab=all"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
if @active_tab == "all" do
"border-blue-500 text-blue-600 dark:text-blue-400"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
end
]}
>
{t("All Devices")}
<span class="ml-2 inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700 dark:bg-gray-700 dark:text-gray-300">
{@topology.stats.discovered_devices}
</span>
</.link>
</nav>
</div>
<%= if @loading do %>
<div class="flex items-center justify-center h-96">
<div class="text-center">
<.icon name="hero-arrow-path" class="h-12 w-12 text-gray-400 animate-spin mx-auto" />
<p class="mt-4 text-sm text-gray-600 dark:text-gray-400">Loading network topology...</p>
</div>
</div>
<% else %>
<!-- Stats Bar -->
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-6">
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<.icon name="hero-server" class="h-6 w-6 text-gray-400" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
{t("Total Devices")}
</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">
{@topology.stats.total_devices}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<.icon name="hero-check-circle" class="h-6 w-6 text-green-400" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
{t("Added Devices")}
</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">
{@topology.stats.added_devices}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<.icon name="hero-magnifying-glass" class="h-6 w-6 text-blue-400" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
{t("Discovered")}
</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">
{@topology.stats.discovered_devices}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<.icon name="hero-link" class="h-6 w-6 text-purple-400" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
{t("Connections")}
</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-white">
{@topology.stats.total_links}
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Filter Bar -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 mb-4 p-3">
<div class="flex items-center justify-between gap-4 flex-wrap">
<div class="flex items-center gap-2">
<button
phx-click="set_filter"
phx-value-filter="all"
class={[
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
if(@filter == "all",
do: "bg-blue-100 text-blue-700 dark:bg-blue-400/20 dark:text-blue-400",
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
)
]}
>
All Links
</button>
<button
phx-click="set_filter"
phx-value-filter="degraded"
class={[
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
if(@filter == "degraded",
do: "bg-yellow-100 text-yellow-700 dark:bg-yellow-400/20 dark:text-yellow-400",
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
)
]}
>
Degraded Only
<%= if @topology.stats[:degraded_links] && @topology.stats.degraded_links > 0 do %>
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-yellow-800 bg-yellow-200 rounded-full dark:bg-yellow-900 dark:text-yellow-300">
{@topology.stats.degraded_links}
</span>
<% end %>
</button>
<button
phx-click="set_filter"
phx-value-filter="alerts"
class={[
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
if(@filter == "alerts",
do: "bg-red-100 text-red-700 dark:bg-red-400/20 dark:text-red-400",
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
)
]}
>
Sites with Alerts
<%= if @topology.stats[:sites_with_alerts] && @topology.stats.sites_with_alerts > 0 do %>
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-red-800 bg-red-200 rounded-full dark:bg-red-900 dark:text-red-300">
{@topology.stats.sites_with_alerts}
</span>
<% end %>
</button>
</div>
<div class="flex items-center gap-3">
<div class="relative">
<.icon
name="hero-magnifying-glass"
class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400"
/>
<input
type="text"
placeholder="Search devices..."
phx-keyup="search"
phx-key="Enter"
phx-debounce="300"
value={@search_query}
class="pl-8 pr-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 w-48"
/>
</div>
<%= if @topology[:has_geo] do %>
<div class="flex items-center gap-1 border-l border-gray-200 dark:border-gray-600 pl-3">
<button
phx-click="toggle_layout"
phx-value-mode="force"
class={[
"px-2 py-1 text-xs rounded transition-colors",
if(@layout_mode == "force",
do: "bg-gray-200 dark:bg-gray-600 text-gray-900 dark:text-white font-medium",
else: "text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700"
)
]}
title="Force-directed layout"
>
<.icon name="hero-share" class="h-4 w-4" />
</button>
<button
phx-click="toggle_layout"
phx-value-mode="geo"
class={[
"px-2 py-1 text-xs rounded transition-colors",
if(@layout_mode == "geo",
do: "bg-gray-200 dark:bg-gray-600 text-gray-900 dark:text-white font-medium",
else: "text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700"
)
]}
title="Geographic layout"
>
<.icon name="hero-map" class="h-4 w-4" />
</button>
</div>
<% end %>
</div>
</div>
</div>
<!-- Network Map Container -->
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
<div class="p-4 border-b border-gray-200 dark:border-white/10">
<div class="flex items-center justify-between">
<div>
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
{t("Topology Visualization")}
</h3>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{t("Click and drag to pan, scroll to zoom, click nodes for details")}
</p>
</div>
<div class="flex items-center space-x-2">
<span class="text-xs text-gray-500 dark:text-gray-400">
Last updated: <.timestamp datetime={@topology.last_updated} timezone={@timezone} />
</span>
</div>
</div>
</div>
<!-- Cytoscape Container with Detail Panel -->
<div class="relative">
<div
id="cy-container"
phx-hook="NetworkMap"
class="w-full"
style="height: 600px;"
data-topology={Jason.encode!(@topology)}
>
</div>
<!-- Zoom Controls -->
<div class="absolute bottom-4 left-4 flex flex-col gap-1 z-10">
<button
id="cy-zoom-in"
type="button"
title="Zoom in"
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<.icon name="hero-plus" class="h-4 w-4" />
</button>
<button
id="cy-zoom-out"
type="button"
title="Zoom out"
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<.icon name="hero-minus" class="h-4 w-4" />
</button>
<button
id="cy-fit"
type="button"
title="Fit to screen"
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<.icon name="hero-arrows-pointing-out" class="h-4 w-4" />
</button>
</div>
<!-- Node Detail Panel (slide-out) -->
<%= if @selected_node_detail do %>
<div
id="node-detail-panel"
class="absolute top-0 right-0 h-full w-80 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-white/10 shadow-lg overflow-y-auto transition-transform duration-200"
>
<div class="p-4">
<!-- Header -->
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
{t("Node Details")}
</h3>
<button
phx-click="close_detail_panel"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<.icon name="hero-x-mark" class="h-5 w-5" />
</button>
</div>
<!-- Device Info -->
<div class="space-y-3">
<div>
<h4 class="text-base font-medium text-gray-900 dark:text-white">
{@selected_node_detail.name}
</h4>
<%= if @selected_node_detail.type == :discovered do %>
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300 mt-1">
{t("Discovered")}
</span>
<% end %>
</div>
<%= if @selected_node_detail.ip_address do %>
<div class="flex items-center text-sm">
<.icon name="hero-globe-alt" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
<span class="text-gray-700 dark:text-gray-300">
{@selected_node_detail.ip_address}
</span>
</div>
<% end %>
<%= if @selected_node_detail.site_name do %>
<div class="flex items-center text-sm">
<.icon name="hero-map-pin" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
<span class="text-gray-700 dark:text-gray-300">
{@selected_node_detail.site_name}
</span>
</div>
<% end %>
<%= if @selected_node_detail.manufacturer do %>
<div class="flex items-center text-sm">
<.icon
name="hero-building-office"
class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0"
/>
<span class="text-gray-700 dark:text-gray-300">
{@selected_node_detail.manufacturer}
</span>
</div>
<% end %>
<div class="flex items-center text-sm">
<.icon name="hero-tag" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
<span class="text-gray-700 dark:text-gray-300 capitalize">
{@selected_node_detail.role}
</span>
</div>
<%= if @selected_node_detail.status && @selected_node_detail.status != :unknown do %>
<div class="flex items-center text-sm">
<span class={[
"inline-block h-2 w-2 rounded-full mr-2",
if(@selected_node_detail.status == :up,
do: "bg-green-400",
else: "bg-red-400"
)
]}>
</span>
<span class="text-gray-700 dark:text-gray-300 capitalize">
{@selected_node_detail.status}
</span>
</div>
<% end %>
</div>
<!-- Wireless Stats (for APs, CPEs, backhaul radios) -->
<%= if @selected_node_detail[:is_wireless] do %>
<div class="mt-4 pt-3 border-t border-gray-200 dark:border-white/10">
<h4 class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
RF Stats
</h4>
<div class="grid grid-cols-2 gap-2">
<!-- Client count -->
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2">
<div class="text-xs text-gray-500 dark:text-gray-400">Clients</div>
<div class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-1">
<.icon name="hero-wifi" class="h-4 w-4 text-purple-500" />
{@selected_node_detail[:client_count] || 0}
</div>
</div>
<!-- Signal health -->
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2">
<div class="text-xs text-gray-500 dark:text-gray-400">Signal</div>
<div class={[
"text-sm font-semibold capitalize",
case @selected_node_detail[:signal_health] do
"good" -> "text-green-600 dark:text-green-400"
"degraded" -> "text-yellow-600 dark:text-yellow-400"
"critical" -> "text-red-600 dark:text-red-400"
_ -> "text-gray-500 dark:text-gray-400"
end
]}>
{@selected_node_detail[:signal_health] || "N/A"}
</div>
</div>
<!-- SNR -->
<%= if @selected_node_detail[:snr] do %>
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2 col-span-2">
<div class="text-xs text-gray-500 dark:text-gray-400">SNR</div>
<div class={[
"text-sm font-semibold",
cond do
@selected_node_detail.snr >= 25 ->
"text-green-600 dark:text-green-400"
@selected_node_detail.snr >= 15 ->
"text-yellow-600 dark:text-yellow-400"
true ->
"text-red-600 dark:text-red-400"
end
]}>
{@selected_node_detail.snr} dB
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<!-- Connections -->
<%= if length(@selected_node_detail.connections) > 0 do %>
<div class="mt-5 pt-4 border-t border-gray-200 dark:border-white/10">
<h4 class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
{t("Connections")} ({length(@selected_node_detail.connections)})
</h4>
<div class="space-y-2">
<%= for conn <- @selected_node_detail.connections do %>
<div class="flex items-center justify-between py-1.5 px-2 rounded bg-gray-50 dark:bg-gray-700/50 text-sm">
<div class="flex-1 min-w-0">
<p class="text-gray-900 dark:text-white truncate font-medium text-xs">
{conn.device_name}
</p>
<%= if conn.interface do %>
<p class="text-gray-500 dark:text-gray-400 text-xs">
{conn.interface}
</p>
<% end %>
</div>
<div class="flex items-center space-x-2 ml-2 flex-shrink-0">
<span class="text-xs text-gray-500 dark:text-gray-400 uppercase">
{conn.link_type}
</span>
<span class={[
"text-xs font-medium",
cond do
conn.confidence > 0.9 -> "text-green-600 dark:text-green-400"
conn.confidence >= 0.5 -> "text-yellow-600 dark:text-yellow-400"
true -> "text-gray-500 dark:text-gray-400"
end
]}>
{trunc(conn.confidence * 100)}%
</span>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<!-- View Device Link (managed only) -->
<%= if @selected_node_detail.type == :managed do %>
<div class="mt-4 space-y-2">
<.link
navigate={~p"/devices/#{@selected_node_detail.id}"}
class="flex items-center justify-center w-full px-3 py-2 text-sm font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-400/10 rounded-md hover:bg-blue-100 dark:hover:bg-blue-400/20"
>
<.icon name="hero-arrow-top-right-on-square" class="h-4 w-4 mr-1.5" />
{t("View Device")}
</.link>
<%!-- RF Links button hidden (TOW-44) — keep code intact
<%= if @selected_node_detail[:is_wireless] do %>
<.link
navigate={~p"/devices/#{@selected_node_detail.id}?tab=rf"}
class="flex items-center justify-center w-full px-3 py-2 text-sm font-medium text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-400/10 rounded-md hover:bg-purple-100 dark:hover:bg-purple-400/20"
>
<.icon name="hero-signal" class="h-4 w-4 mr-1.5" />
{t("View RF Links")}
</.link>
<% end %>
--%>
</div>
<% end %>
</div>
</div>
<% end %>
</div>
<!-- Legend -->
<div class="p-4 border-t border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-gray-900">
<div class="flex flex-col gap-3">
<%!-- Device roles --%>
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
<span class="font-medium text-gray-600 dark:text-gray-400">Roles:</span>
<div class="flex items-center space-x-1.5">
<div class="w-3.5 h-3 rounded-sm" style="background-color: #3B82F6;"></div>
<span class="text-gray-700 dark:text-gray-300">Router</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-3 h-3 rotate-45" style="background-color: #10B981;"></div>
<span class="text-gray-700 dark:text-gray-300">Switch</span>
</div>
<div class="flex items-center space-x-1.5">
<div
class="w-0 h-0 border-l-[6px] border-r-[6px] border-b-[10px] border-l-transparent border-r-transparent"
style="border-bottom-color: #8B5CF6;"
>
</div>
<span class="text-gray-700 dark:text-gray-300">AP / Wireless</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-3 h-3 rounded-full" style="background-color: #EF4444;"></div>
<span class="text-gray-700 dark:text-gray-300">Firewall</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-3 h-3" style="background-color: #F59E0B;"></div>
<span class="text-gray-700 dark:text-gray-300">Server</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-3 h-3 rounded-full bg-gray-400"></div>
<span class="text-gray-700 dark:text-gray-300">Unknown</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-3 h-3 rounded-full border-2 border-dashed border-gray-400 opacity-60">
</div>
<span class="text-gray-700 dark:text-gray-300">Discovered</span>
</div>
</div>
<%!-- RF Link health legend hidden (TOW-44) — keep code intact
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
<span class="font-medium text-gray-600 dark:text-gray-400">RF Links:</span>
<div class="flex items-center space-x-1.5">
<div class="w-6 h-1 rounded" style="background-color: #10B981;"></div>
<span class="text-gray-700 dark:text-gray-300">Good signal</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-6 h-1 rounded" style="background-color: #F59E0B;"></div>
<span class="text-gray-700 dark:text-gray-300">Degraded</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-6 h-1 rounded" style="background-color: #EF4444;"></div>
<span class="text-gray-700 dark:text-gray-300">Critical</span>
</div>
</div>
--%>
<%!-- Link types --%>
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
<span class="font-medium text-gray-600 dark:text-gray-400">Link types:</span>
<div class="flex items-center space-x-1.5">
<div class="w-6 h-0.5" style="background-color: #6B7280;"></div>
<span class="text-gray-700 dark:text-gray-300">LLDP/CDP</span>
</div>
<div class="flex items-center space-x-1.5">
<div class="w-6 h-0.5 border-t-2 border-dashed" style="border-color: #6B7280;">
</div>
<span class="text-gray-700 dark:text-gray-300">MAC/ARP inferred</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="text-gray-500 dark:text-gray-400">
Line thickness = bandwidth capacity
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Empty State -->
<%= if @topology.stats.total_devices == 0 do %>
<div class="text-center py-16">
<.icon name="hero-map" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
{t("No network topology available")}
</h3>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
{t("Add devices with SNMP enabled to start building your network map.")}
</p>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{t("The topology is automatically discovered via LLDP and CDP protocols.")}
</p>
<div class="mt-6">
<.button navigate={~p"/devices/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Add Your First Device
</.button>
</div>
</div>
<% end %>
<% end %>
</Layouts.authenticated>