Skip to main content

priv/templates/parapet.gen.ui/operator_detail_live.ex.eex

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>&larr; 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