- Add active_page attribute to authenticated layout - Update nav_link component to show active state with border and text styling - Update all LiveView templates to pass active_page parameter - Fix datetime truncation issues in tests and schemas for :utc_datetime fields - Fix dashboard test assertions for equipment status text
156 lines
6.3 KiB
Text
156 lines
6.3 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_organization={@current_organization}
|
|
active_page="alerts"
|
|
>
|
|
<.header>
|
|
Alerts
|
|
<:subtitle>Monitor and acknowledge system alerts</:subtitle>
|
|
</.header>
|
|
|
|
<div class="mb-6">
|
|
<div class="inline-flex rounded-lg border border-zinc-200 dark:border-zinc-800">
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/alerts"}
|
|
class={[
|
|
"px-4 py-2 text-sm font-medium rounded-l-lg",
|
|
@filter == "all" &&
|
|
"bg-blue-600 text-white dark:bg-blue-500",
|
|
@filter != "all" &&
|
|
"bg-white text-zinc-700 hover:bg-zinc-50 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
]}
|
|
>
|
|
All Alerts
|
|
</.link>
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/alerts?filter=active"}
|
|
class={[
|
|
"px-4 py-2 text-sm font-medium rounded-r-lg border-l border-zinc-200 dark:border-zinc-800",
|
|
@filter == "active" &&
|
|
"bg-blue-600 text-white dark:bg-blue-500",
|
|
@filter != "active" &&
|
|
"bg-white text-zinc-700 hover:bg-zinc-50 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
]}
|
|
>
|
|
Active Alerts
|
|
</.link>
|
|
</div>
|
|
</div>
|
|
|
|
<%= if Enum.empty?(@alerts) do %>
|
|
<div class="text-center py-16">
|
|
<.icon name="hero-bell-slash" class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-600" />
|
|
<h3 class="mt-4 text-lg font-semibold text-zinc-900 dark:text-zinc-100">No alerts</h3>
|
|
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
|
|
<%= if @filter == "active" do %>
|
|
There are no active alerts for this organization.
|
|
<% else %>
|
|
No alerts have been triggered yet.
|
|
<% end %>
|
|
</p>
|
|
</div>
|
|
<% else %>
|
|
<div class="space-y-4">
|
|
<%= for alert <- @alerts do %>
|
|
<div class={[
|
|
"rounded-lg border p-6 shadow-sm",
|
|
alert.resolved_at && "opacity-60",
|
|
alert.alert_type == :equipment_down && is_nil(alert.resolved_at) &&
|
|
"border-l-4 border-red-500 bg-red-50 dark:bg-red-950 dark:border-red-700",
|
|
(alert.alert_type != :equipment_down || alert.resolved_at) &&
|
|
"border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900"
|
|
]}>
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-3 flex-wrap">
|
|
<span class={[
|
|
"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium",
|
|
alert.alert_type == :equipment_down &&
|
|
"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
|
|
alert.alert_type == :equipment_up &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
|
]}>
|
|
<%= case alert.alert_type do %>
|
|
<% :equipment_down -> %>
|
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4" /> Equipment Down
|
|
<% :equipment_up -> %>
|
|
<.icon name="hero-check-circle" class="h-4 w-4" /> Equipment Recovered
|
|
<% end %>
|
|
</span>
|
|
|
|
<%= if alert.resolved_at do %>
|
|
<span class="inline-flex items-center rounded-full bg-zinc-100 px-2.5 py-0.5 text-xs font-medium text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
|
Resolved
|
|
</span>
|
|
<% end %>
|
|
|
|
<%= if alert.acknowledged_at do %>
|
|
<span class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
|
Acknowledged
|
|
</span>
|
|
<% end %>
|
|
</div>
|
|
|
|
<h3 class="mt-3 font-semibold text-zinc-900 dark:text-zinc-100">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/equipment/#{alert.equipment.id}"
|
|
}
|
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{alert.equipment.name}
|
|
</.link>
|
|
</h3>
|
|
|
|
<p class="mt-1 text-sm text-zinc-700 dark:text-zinc-300">{alert.message}</p>
|
|
|
|
<div class="mt-3 space-y-1 text-xs text-zinc-600 dark:text-zinc-400">
|
|
<div>
|
|
<strong class="font-medium">Triggered:</strong>
|
|
{Calendar.strftime(alert.triggered_at, "%Y-%m-%d %H:%M:%S UTC")}
|
|
</div>
|
|
|
|
<%= if alert.acknowledged_at do %>
|
|
<div>
|
|
<strong class="font-medium">Acknowledged:</strong>
|
|
{Calendar.strftime(alert.acknowledged_at, "%Y-%m-%d %H:%M:%S UTC")}
|
|
<%= if alert.acknowledged_by do %>
|
|
by {alert.acknowledged_by.email}
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%= if alert.resolved_at do %>
|
|
<div>
|
|
<strong class="font-medium">Resolved:</strong>
|
|
{Calendar.strftime(alert.resolved_at, "%Y-%m-%d %H:%M:%S UTC")}
|
|
</div>
|
|
<% end %>
|
|
|
|
<div>
|
|
<strong class="font-medium">Site:</strong>
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/sites/#{alert.equipment.site.id}"
|
|
}
|
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{alert.equipment.site.name}
|
|
</.link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ml-4">
|
|
<%= if alert.alert_type == :equipment_down && is_nil(alert.acknowledged_at) && is_nil(alert.resolved_at) do %>
|
|
<.button phx-click="acknowledge" phx-value-id={alert.id} variant="primary">
|
|
<.icon name="hero-check" class="h-4 w-4" /> Acknowledge
|
|
</.button>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</Layouts.authenticated>
|