towerops/lib/towerops_web/live/escalation_policy_live/show.html.heex

276 lines
12 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="schedules"
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-3">
<.link
navigate={~p"/schedules?tab=escalation-policies"}
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<.icon name="hero-arrow-left" class="h-5 w-5" />
</.link>
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{@policy.name}</h1>
</div>
<div class="flex items-center gap-2">
<.link
navigate={~p"/schedules/escalation-policies/#{@policy.id}/edit"}
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<.icon name="hero-pencil" class="h-4 w-4" />
{t("Edit")}
</.link>
<button
phx-click="delete"
data-confirm={t("Are you sure you want to delete this escalation policy?")}
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-gray-300 dark:border-gray-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
>
<.icon name="hero-trash" class="h-4 w-4" />
{t("Delete")}
</button>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
{t("Send On-Call Handoff Notifications:")}
<span class="font-medium text-gray-900 dark:text-white">
{handoff_mode_label(@policy.handoff_notifications_mode)}
</span>
</p>
<%= if @policy.description do %>
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">{@policy.description}</p>
<% end %>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- LEFT: Levels timeline --%>
<div class="lg:col-span-2 space-y-3">
<%= if Enum.empty?(@policy.rules) do %>
<div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-6 text-center">
<p class="text-sm text-gray-500 dark:text-gray-400">
{t("No levels configured. Add a level to define escalation steps.")}
</p>
</div>
<% else %>
<% sorted_rules = Enum.sort_by(@policy.rules, & &1.position) %>
<% rule_count = length(sorted_rules) %>
<div
id="level-list"
class="space-y-3"
phx-hook="SortableList"
data-event="reorder_rules"
>
<%= for {rule, idx} <- Enum.with_index(sorted_rules) do %>
<div
data-id={rule.id}
draggable="true"
class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4 cursor-move"
>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<.icon name="hero-bars-3" class="h-4 w-4 text-gray-400" />
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
{t("Level %{n}", n: idx + 1)}
</h3>
</div>
<div class="flex items-center gap-1">
<button
phx-click="move_rule"
phx-value-id={rule.id}
phx-value-direction="up"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
disabled={idx == 0}
title={t("Move up")}
>
<.icon name="hero-arrow-up" class="h-4 w-4" />
</button>
<button
phx-click="move_rule"
phx-value-id={rule.id}
phx-value-direction="down"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
disabled={idx == rule_count - 1}
title={t("Move down")}
>
<.icon name="hero-arrow-down" class="h-4 w-4" />
</button>
<button
phx-click="delete_rule"
phx-value-id={rule.id}
data-confirm={t("Delete this level?")}
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors ml-1"
title={t("Delete")}
>
<.icon name="hero-trash" class="h-4 w-4" />
</button>
</div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
{t("Notify the following users immediately and escalate after")}
<span class="font-semibold text-gray-900 dark:text-white">
{rule.timeout_minutes}
</span>
{t("minutes.")}
</p>
<%!-- Targets --%>
<div>
<%= if Enum.empty?(rule.targets) do %>
<p class="text-xs text-gray-400 dark:text-gray-500 mb-2">
{t("No targets yet")}
</p>
<% else %>
<div class="flex flex-wrap gap-2 mb-2">
<%= for target <- rule.targets do %>
<span class="inline-flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-800 pl-2.5 pr-1 py-0.5 text-xs font-medium text-gray-700 dark:text-gray-300">
<%= case target.target_type do %>
<% "user" -> %>
<.icon name="hero-user" class="h-3 w-3 mr-0.5" />
<% user = Enum.find(@org_users, &(&1.id == target.user_id)) %>
{if user, do: user.email, else: t("Unknown user")}
<% "schedule" -> %>
<.icon name="hero-calendar-days" class="h-3 w-3 mr-0.5" />
<% sched = Enum.find(@org_schedules, &(&1.id == target.schedule_id)) %>
{if sched, do: sched.name, else: t("Unknown schedule")}
<% end %>
<button
phx-click="delete_target"
phx-value-id={target.id}
class="ml-0.5 rounded-full p-0.5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<.icon name="hero-x-mark" class="h-3 w-3" />
</button>
</span>
<% end %>
</div>
<% end %>
<%!-- Add target form --%>
<form
id={"add-target-form-#{rule.id}"}
phx-submit="add_target"
class="flex items-center gap-2"
>
<input type="hidden" name="rule_id" value={rule.id} />
<select
name="target_type"
id={"target-type-#{rule.id}"}
class="block rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1.5"
>
<option value="user">{t("User")}</option>
<option value="schedule">{t("Schedule")}</option>
</select>
<select
name="target_id"
class="block w-full max-w-xs rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1.5"
>
<option value="">{t("Select...")}</option>
<optgroup label={t("Users")}>
<%= for user <- @org_users do %>
<option value={user.id}>{user.email}</option>
<% end %>
</optgroup>
<optgroup label={t("Schedules")}>
<%= for sched <- @org_schedules do %>
<option value={sched.id}>{sched.name}</option>
<% end %>
</optgroup>
</select>
<button
type="submit"
class="inline-flex items-center rounded-md bg-gray-100 dark:bg-gray-800 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<.icon name="hero-plus" class="h-3 w-3 mr-1" />
{t("Add")}
</button>
</form>
</div>
</div>
<% end %>
</div>
<% end %>
<%!-- Add Level button + form --%>
<%= if @show_add_rule do %>
<div class="rounded-lg border border-blue-200 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-900/10 p-4">
<form id="save-rule-form" phx-submit="save_rule">
<div class="flex items-end gap-4 mb-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t("Escalate after (minutes)")}
</label>
<input
type="number"
name="timeout_minutes"
value="30"
min="1"
max="1440"
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm"
/>
</div>
</div>
<div class="flex items-center gap-2">
<.button type="submit" phx-disable-with={t("Saving...")}>
{t("Add Level")}
</.button>
<button
type="button"
phx-click="toggle_add_rule"
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
>
{t("Cancel")}
</button>
</div>
</form>
</div>
<% else %>
<button
phx-click="toggle_add_rule"
class="w-full rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-3 text-sm text-gray-500 dark:text-gray-400 hover:border-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
>
<.icon name="hero-plus" class="h-4 w-4 inline mr-1" /> {t("Add Level")}
</button>
<% end %>
<%!-- REPEAT footer --%>
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-gray-800/40 p-4 flex items-center gap-3">
<.icon name="hero-arrow-path" class="h-5 w-5 text-gray-500" />
<p class="text-sm text-gray-700 dark:text-gray-300">
{t("If no one acknowledges, repeat this policy")}
<span class="font-semibold">{@policy.repeat_count}</span>
{t("time(s).")}
</p>
</div>
</div>
<%!-- RIGHT: Services sidebar --%>
<aside class="lg:col-span-1">
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
{t("Services (%{count})", count: length(@services))}
</h3>
<%= if @services == [] do %>
<p class="text-sm text-gray-500 dark:text-gray-400">
{t("No devices use this policy yet.")}
</p>
<% else %>
<ul class="space-y-2">
<%= for device <- @services do %>
<li>
<.link
navigate={~p"/devices/#{device.id}"}
class="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
{device.name}
</.link>
</li>
<% end %>
</ul>
<% end %>
</div>
</aside>
</div>
</Layouts.authenticated>