Two issues were preventing impersonation from working correctly:
1. Templates were not passing current_scope to Layouts.authenticated,
so the impersonation banner never displayed.
2. fetch_current_scope_for_user was looking up the user from the
session token, which always pointed to the superuser. This caused
the superuser to see their own organizations and equipment instead
of the impersonated user's data.
Changes:
- Pass current_scope={@current_scope} to all Layouts.authenticated calls
- Store target_user_id in session during impersonation
- Fetch target user directly from database using target_user_id
- Update both fetch_current_scope_for_user (for controllers) and
mount_current_scope (for LiveViews) to properly handle impersonation
- Clean up target_user_id from session when impersonation ends
Now when a superuser impersonates a user, they correctly see:
- The impersonation banner at the top with exit link
- The target user's organizations and equipment
- All data scoped to the impersonated user
157 lines
6.3 KiB
Text
157 lines
6.3 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_scope={@current_scope}
|
|
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>
|