towerops/lib/towerops_web/live/org/gaiia_reconciliation_live.html.heex
Graham McIntire 5a7cdc7138 refactor: apply custom color palette across entire codebase
- Replace gray->cool-steel, blue/indigo->cerulean, red->sweet-salmon, yellow/amber->wheat
- Dark sidebar: sidebar/footer use cool-steel-800 bg, text lightened for contrast
- ~11,600 color replacements across ~99 files
- Update tests for new color class names
2026-06-23 10:58:48 -05:00

579 lines
29 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="settings"
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
>
<.breadcrumb items={[
%{label: "Dashboard", navigate: ~p"/dashboard"},
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
%{label: "Integrations", navigate: ~p"/orgs/#{@organization.slug}/settings/integrations"},
%{label: "Gaiia"}
]} />
<.header>
{t("Gaiia Inventory Reconciliation")}
<:subtitle>
{t(
"Compare Gaiia inventory against Towerops device discovery. Reconciliation runs nightly."
)}
</:subtitle>
</.header>
<%!-- Sub-navigation tabs --%>
<div class="mt-4 border-b border-cool-steel-200 dark:border-white/10">
<nav class="flex space-x-6">
<.link
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-white"
>
{t("Entity Mapping")}
</.link>
<.link
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"}
class="border-b-2 border-cerulean-500 px-1 pb-3 text-sm font-medium text-cerulean-600 dark:text-cerulean-400"
>
{t("Reconciliation")}
</.link>
</nav>
</div>
<div class="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-5">
<div class="rounded-lg border border-cool-steel-200 bg-white p-4 dark:border-white/10 dark:bg-cool-steel-800/50">
<div class="text-2xl font-bold text-cool-steel-900 dark:text-white">
{@report.summary.total_mapped}
</div>
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400">Mapped</div>
</div>
<div class="rounded-lg border border-wheat-200 bg-wheat-50 p-4 dark:border-wheat-600/30 dark:bg-wheat-900/20">
<div class="text-2xl font-bold text-wheat-800 dark:text-wheat-400">
{@report.summary.missing_mapping_count}
</div>
<div class="text-sm text-wheat-700 dark:text-wheat-500">Unmapped Items</div>
</div>
<div class="rounded-lg border border-cerulean-200 bg-cerulean-50 p-4 dark:border-cerulean-600/30 dark:bg-cerulean-900/20">
<div class="text-2xl font-bold text-cerulean-800 dark:text-cerulean-400">
{@report.summary.untracked_count}
</div>
<div class="text-sm text-cerulean-700 dark:text-cerulean-500">Untracked Devices</div>
</div>
<div class="rounded-lg border border-sweet-salmon-200 bg-sweet-salmon-50 p-4 dark:border-sweet-salmon-600/30 dark:bg-sweet-salmon-900/20">
<div class="text-2xl font-bold text-sweet-salmon-800 dark:text-sweet-salmon-400">
{@report.summary.mismatch_count}
</div>
<div class="text-sm text-sweet-salmon-700 dark:text-sweet-salmon-500">Data Mismatches</div>
</div>
<div class="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-600/30 dark:bg-purple-900/20">
<div class="text-2xl font-bold text-purple-800 dark:text-purple-400">
{@report.summary.ghost_count}
</div>
<div class="text-sm text-purple-700 dark:text-purple-500">Ghosts</div>
</div>
</div>
<div class="mt-6 mb-6 flex items-center justify-between">
<div class="inline-flex rounded-lg border border-cool-steel-200 dark:border-white/10">
<.link
patch={
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=summary"
}
class={[
"px-4 py-2 text-sm font-medium rounded-l-lg",
@active_tab == "summary" &&
"bg-cerulean-600 text-white dark:bg-cerulean-500",
@active_tab != "summary" &&
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
]}
>
{t("Summary")}
</.link>
<.link
patch={
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=untracked"
}
class={[
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
@active_tab == "untracked" &&
"bg-cerulean-600 text-white dark:bg-cerulean-500",
@active_tab != "untracked" &&
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
]}
>
Untracked ({@report.summary.untracked_count})
</.link>
<.link
patch={
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=mismatches"
}
class={[
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
@active_tab == "mismatches" &&
"bg-cerulean-600 text-white dark:bg-cerulean-500",
@active_tab != "mismatches" &&
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
]}
>
Mismatches ({@report.summary.mismatch_count})
</.link>
<.link
patch={
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=unmapped"
}
class={[
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
@active_tab == "unmapped" &&
"bg-cerulean-600 text-white dark:bg-cerulean-500",
@active_tab != "unmapped" &&
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
]}
>
Unmapped ({@report.summary.missing_mapping_count})
</.link>
<.link
patch={
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=ghosts"
}
class={[
"px-4 py-2 text-sm font-medium rounded-r-lg border-l border-cool-steel-200 dark:border-white/10",
@active_tab == "ghosts" &&
"bg-cerulean-600 text-white dark:bg-cerulean-500",
@active_tab != "ghosts" &&
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
]}
>
Ghosts ({@report.summary.ghost_count})
</.link>
</div>
<button
phx-click="refresh"
class="inline-flex items-center gap-1.5 rounded-lg border border-cool-steel-200 bg-white px-3 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
>
<.icon name="hero-arrow-path" class="h-4 w-4" />
{t("Refresh")}
</button>
</div>
<%= case @active_tab do %>
<% "untracked" -> %>
<div class="space-y-2">
<%= if @report.untracked_devices == [] do %>
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t("All Towerops devices have matching Gaiia inventory items.")}
</p>
<% else %>
<div class="mb-4 flex items-center gap-3">
<button
phx-click="auto_match"
class="inline-flex items-center gap-1.5 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-medium text-white hover:bg-cerulean-700"
>
<.icon name="hero-bolt" class="h-4 w-4" />
{t("Auto-Match All")}
</button>
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t("Links by MAC, IP, or site assignment.")}
</span>
</div>
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Device")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("IP Address")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Site")}
</th>
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Actions")}
</th>
</tr>
</thead>
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
<%= for entry <- @report.untracked_devices do %>
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
<.link
navigate={~p"/devices/#{entry.device.id}"}
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
>
{entry.device.name}
</.link>
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
<a
href={"http://#{entry.device.ip_address}"}
target="_blank"
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
>
{entry.device.ip_address}
</a>
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
<%= if entry.device.site do %>
{entry.device.site.name}
<% end %>
</td>
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
<%= cond do %>
<% @open_create_for == entry.device.id -> %>
<div class="flex items-center justify-end gap-2">
<form phx-change="select_manufacturer">
<select
name="manufacturer_id"
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
>
<option value="">{t("Select manufacturer")}</option>
<%= for mfr <- @manufacturers do %>
<option
value={mfr["id"]}
selected={@selected_manufacturer == mfr["id"]}
>
{mfr["name"]}
</option>
<% end %>
</select>
</form>
<%= if @selected_manufacturer do %>
<form
phx-submit="create_gaiia_item"
class="flex items-center gap-2"
>
<input type="hidden" name="device_id" value={entry.device.id} />
<select
name="model_id"
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
>
<option value="">{t("Select model")}</option>
<%= for model <- Map.get(@models, @selected_manufacturer, []) do %>
<option value={model["id"]}>{model["name"]}</option>
<% end %>
</select>
<button class="rounded bg-cerulean-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-cerulean-700">
{t("Create")}
</button>
</form>
<% end %>
<button
phx-click="cancel_create"
class="rounded px-2 py-1 text-xs text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
>
{t("Cancel")}
</button>
</div>
<% @open_link_for == entry.device.id -> %>
<div class="flex items-center justify-end gap-2">
<form phx-change="search_gaiia_items" class="flex items-center gap-2">
<input type="hidden" name="device_id" value={entry.device.id} />
<input
type="text"
name="query"
value={@link_search_query}
placeholder={t("Search by name or IP...")}
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
autocomplete="off"
/>
</form>
<button
phx-click="cancel_link_search"
class="rounded px-2 py-1 text-xs text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
>
{t("Cancel")}
</button>
</div>
<% true -> %>
<div class="flex items-center justify-end gap-2">
<button
phx-click="start_link_search"
phx-value-device_id={entry.device.id}
class="rounded bg-white px-2.5 py-1 text-xs font-medium text-cool-steel-700 border border-cool-steel-300 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
>
{t("Link")}
</button>
<button
phx-click="open_create_form"
phx-value-device_id={entry.device.id}
class="inline-flex items-center gap-1 rounded bg-cerulean-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-cerulean-700"
>
{t("Create in Gaiia")}
</button>
</div>
<% end %>
</td>
</tr>
<%!-- Search results row for link search --%>
<%= if @open_link_for == entry.device.id and @link_search_results != [] do %>
<tr>
<td colspan="4" class="px-4 py-2 bg-cool-steel-50 dark:bg-cool-steel-800/50">
<div class="space-y-1">
<%= for item <- @link_search_results do %>
<div class="flex items-center justify-between rounded border border-cool-steel-200 bg-white px-3 py-2 dark:border-white/10 dark:bg-cool-steel-800">
<div>
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
{item.name}
</span>
<span class="ml-2 text-xs text-cool-steel-500 dark:text-cool-steel-400">
{item.ip_address || "—"}
</span>
</div>
<button
phx-click="link_to_gaiia"
phx-value-device_id={entry.device.id}
phx-value-gaiia_id={item.gaiia_id}
class="rounded bg-cerulean-600 px-2 py-1 text-xs font-medium text-white hover:bg-cerulean-700"
>
{t("Link")}
</button>
</div>
<% end %>
</div>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
<% "mismatches" -> %>
<div class="space-y-2">
<%= if @report.data_mismatches == [] do %>
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t("No data mismatches found between mapped devices.")}
</p>
<% else %>
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Device")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Field")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Towerops")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Gaiia")}
</th>
</tr>
</thead>
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
<%= for mismatch <- @report.data_mismatches do %>
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
<.link
navigate={~p"/devices/#{mismatch.device.id}"}
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
>
{mismatch.device.name}
</.link>
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
{mismatch.field}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-900 dark:text-white">
{mismatch.towerops_value}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-sweet-salmon-600 dark:text-sweet-salmon-400">
{mismatch.gaiia_value}
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
<% "unmapped" -> %>
<div class="space-y-2">
<%= if @report.missing_mappings == [] do %>
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t("All Gaiia inventory items are mapped to Towerops devices.")}
</p>
<% else %>
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Gaiia Item")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("IP Address")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Category")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Status")}
</th>
</tr>
</thead>
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
<%= for entry <- @report.missing_mappings do %>
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
{entry.inventory_item.name}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-600 dark:text-cool-steel-400">
{entry.inventory_item.ip_address || "—"}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
{entry.inventory_item.category || "—"}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
{entry.inventory_item.status || "—"}
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
<% "ghosts" -> %>
<div class="space-y-2">
<%= if @report.ghost_devices == [] do %>
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t("No stale mappings — all linked Gaiia items reference existing Towerops devices.")}
</p>
<% else %>
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
{t(
"These Gaiia inventory items are linked to Towerops devices that no longer exist. Unlink them to restore them to the unmapped pool."
)}
</p>
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Gaiia Item")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("IP Address")}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Status")}
</th>
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
{t("Actions")}
</th>
</tr>
</thead>
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
<%= for entry <- @report.ghost_devices do %>
<tr>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
{entry.inventory_item.name}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-600 dark:text-cool-steel-400">
{entry.inventory_item.ip_address || "—"}
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
{entry.inventory_item.status || "—"}
</td>
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
<button
phx-click="unlink_ghost"
phx-value-gaiia_id={entry.inventory_item.gaiia_id}
class="rounded bg-purple-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-purple-700"
>
{t("Unlink")}
</button>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
<% _ -> %>
<div class="space-y-4">
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
Last reconciled: {ToweropsWeb.TimeHelpers.format_datetime(
@report.reconciled_at,
@current_scope.timezone,
@current_scope.time_format
)}
</p>
<%= if @report.summary.total_mapped == 0 && @report.summary.missing_mapping_count == 0 && @report.summary.untracked_count == 0 do %>
<div class="py-8 text-center">
<.icon
name="hero-check-circle"
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
/>
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
{t("No inventory data")}
</h3>
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
{t("Sync Gaiia data and map inventory items to see reconciliation results.")}
</p>
<.link
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
class="mt-4 inline-flex items-center gap-1 text-sm font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
>
Go to Entity Mapping <.icon name="hero-arrow-right" class="h-4 w-4" />
</.link>
</div>
<% else %>
<div class="rounded-lg border border-cool-steel-200 bg-white p-6 dark:border-white/10 dark:bg-cool-steel-800/50">
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
{t("Overview")}
</h3>
<dl class="mt-4 space-y-3">
<div class="flex justify-between text-sm">
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
Mapped devices (Gaiia ↔ Towerops)
</dt>
<dd class="font-medium text-cool-steel-900 dark:text-white">
{@report.summary.total_mapped}
</dd>
</div>
<div class="flex justify-between text-sm">
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
{t("Unmapped Gaiia items (no Towerops link)")}
</dt>
<dd class="font-medium text-wheat-600 dark:text-wheat-400">
{@report.summary.missing_mapping_count}
</dd>
</div>
<div class="flex justify-between text-sm">
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
{t("Untracked Towerops devices (not in Gaiia)")}
</dt>
<dd class="font-medium text-cerulean-600 dark:text-cerulean-400">
{@report.summary.untracked_count}
</dd>
</div>
<div class="flex justify-between text-sm">
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
{t("Data mismatches (IP, serial, etc.)")}
</dt>
<dd class="font-medium text-sweet-salmon-600 dark:text-sweet-salmon-400">
{@report.summary.mismatch_count}
</dd>
</div>
<div class="flex justify-between text-sm">
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
{t("Ghost mappings (stale device links)")}
</dt>
<dd class="font-medium text-purple-600 dark:text-purple-400">
{@report.summary.ghost_count}
</dd>
</div>
</dl>
</div>
<% end %>
</div>
<% end %>
</Layouts.authenticated>