defmodule <%= inspect(@web_module) %>.Parapet.OperatorDetailLive do
@moduledoc false
use <%= inspect(@web_module) %>, :live_view
import <%= inspect(@web_module) %>.Parapet.OperatorComponents
def mount(%{"id" => id}, _session, socket) do
selected = Parapet.Operator.incident_detail(id)
{:ok, assign(socket, incident: selected)}
end
def handle_event("acknowledge", %{"id" => id}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, id)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Acknowledged via UI",
correlation_id: Ecto.UUID.generate(),
action_type: :acknowledge
}
case Parapet.Operator.acknowledge_incident(incident, payload) do
{:ok, _result} ->
{:noreply,
socket
|> put_flash(:info, "Incident acknowledged successfully")
|> push_navigate(to: "/parapet/#{id}")}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, "Failed to acknowledge")}
end
end
def handle_event("resolve", %{"id" => id}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, id)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Resolved via UI",
correlation_id: Ecto.UUID.generate(),
action_type: :resolve
}
case Parapet.Operator.resolve_incident(incident, payload) do
{:ok, _result} ->
{:noreply, push_navigate(socket, to: "/parapet/#{id}")}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, "Failed to resolve")}
end
end
def handle_event("trigger_next_escalation", %{"id" => id}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, id)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Requested next escalation from UI",
correlation_id: Ecto.UUID.generate(),
action_type: :trigger_next_escalation
}
case Parapet.Operator.trigger_next_escalation(incident, payload) do
{:ok, _result} ->
{:noreply,
socket
|> put_flash(:info, "Escalation request recorded")
|> assign(incident: Parapet.Operator.incident_detail(id))}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to request escalation: #{inspect(reason)}")}
end
end
def handle_event("suppress_pending_escalation", %{"id" => id, "minutes" => minutes}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, id)
with {minutes, ""} <- Integer.parse(minutes),
true <- minutes > 0 do
suppress_until =
DateTime.utc_now()
|> DateTime.add(minutes * 60, :second)
|> DateTime.truncate(:second)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Temporarily suppressed pending escalation from UI",
correlation_id: Ecto.UUID.generate(),
action_type: :suppress_pending_escalation
}
case Parapet.Operator.suppress_pending_escalation(incident, suppress_until, payload) do
{:ok, _result} ->
{:noreply,
socket
|> put_flash(:info, "Escalation suppression recorded")
|> assign(incident: Parapet.Operator.incident_detail(id))}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to suppress escalation: #{inspect(reason)}")}
end
else
_ ->
{:noreply, put_flash(socket, :error, "Invalid suppression window")}
end
end
def handle_event("preview_mitigation", %{"step" => step, "incident_id" => incident_id}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, incident_id)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Previewed mitigation from UI",
correlation_id: Ecto.UUID.generate(),
action_type: :preview_mitigation
}
case Parapet.Operator.preview_runbook_step(incident, step, payload) do
{:ok, _result} ->
{:noreply,
socket
|> put_flash(:info, "Preview generated")
|> assign(incident: Parapet.Operator.incident_detail(incident_id))}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Preview failed: #{inspect(reason)}")}
end
end
def handle_event("confirm_mitigation", %{"step" => step, "incident_id" => incident_id, "token" => token}, socket) do
incident = <%= inspect(@repo_module) %>.get!(Parapet.Spine.Incident, incident_id)
payload = %Parapet.Operator.ActionPayload{
actor: "operator_ui",
reason: "Confirmed mitigation from UI",
correlation_id: Ecto.UUID.generate(),
action_type: :execute_mitigation,
idempotency_key: Ecto.UUID.generate()
}
case Parapet.Operator.confirm_runbook_step(incident, step, token, payload) do
{:ok, _result} ->
{:noreply,
socket
|> put_flash(:info, "Mitigation confirmed and executed")
|> assign(incident: Parapet.Operator.incident_detail(incident_id))}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Confirmation failed: #{inspect(reason)}")}
end
end
def handle_event("cancel_preview", _params, socket) do
# Simply refresh without an active preview in local state if we had one
# Though it's derived from timeline, so we might need a "cancel" timeline event
# For now, just a UI refresh
{:noreply, socket}
end
def render(assigns) do
~H"""
<div class="flex flex-col min-h-screen bg-gray-50 md:hidden">
<div class="p-4 bg-white border-b border-gray-200 sticky top-0 z-10">
<.link navigate={"/parapet"} class="text-blue-600 hover:text-blue-800 flex items-center gap-2 mb-4">
<span>← Back to Queue</span>
</.link>
<.incident_summary detail={@incident} />
</div>
<div class="p-4 bg-white border-b border-gray-200">
<h3 class="text-lg font-semibold mb-4">Timeline</h3>
<.incident_timeline detail={@incident} />
</div>
<div class="p-4 bg-gray-50 pb-24">
<.suspect_changes_card entries={Enum.filter(@incident.entries, &(&1.type == "rulestead_flag_change"))} />
<%%= if @incident.incident.state == "resolved" && is_map(@incident.incident.runbook_data) && Map.get(@incident.incident.runbook_data, "retrospective") do %>
<div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-sm">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Automated Retrospective</h3>
<button
onclick="navigator.clipboard.writeText(this.dataset.content); alert('Copied to clipboard!')"
data-content={@incident.incident.runbook_data["retrospective"]}
class="px-3 py-1 bg-gray-100 hover:bg-gray-200 border border-gray-300 rounded text-sm text-gray-700 transition-colors"
type="button"
>
Copy to Clipboard
</button>
</div>
<pre class="bg-gray-800 text-gray-100 p-4 rounded text-sm overflow-x-auto whitespace-pre-wrap font-mono"><code><%%= @incident.incident.runbook_data["retrospective"] %></code></pre>
</div>
<%% end %>
<%%= if @incident.derived.runbook_steps != [] do %>
<.runbook_card detail={@incident} />
<%% end %>
<%%= if @incident.derived.active_preview do %>
<.preview_panel detail={@incident} />
<%% end %>
<h3 class="text-lg font-semibold mb-4">Actions</h3>
<.action_rail detail={@incident} />
</div>
</div>
"""
end
end