defmodule ToweropsWeb.ConfigTimelineLive do @moduledoc """ Timeline visualization showing config change events overlaid with QoE metrics and check results for a device. """ use ToweropsWeb, :live_view alias Towerops.ConfigChanges alias Towerops.Devices alias Towerops.Monitoring alias Towerops.Preseem alias ToweropsWeb.Live.Helpers.AccessControl @ranges %{ "24h" => 24, "7d" => 24 * 7, "30d" => 24 * 30 } @impl true def mount(%{"device_id" => device_id}, _session, socket) do organization = socket.assigns.current_scope.organization case AccessControl.verify_device_access(device_id, organization.id) do {:ok, _} -> device = case Devices.get_device(device_id) do nil -> raise Ecto.NoResultsError, queryable: Towerops.Devices.DeviceSchema device -> device end {:ok, assign(socket, device: device, page_title: "Config Timeline — #{device.name}")} {:error, _} -> {:ok, socket |> put_flash(:error, t("Device not found")) |> redirect(to: ~p"/devices")} end end @impl true def handle_params(params, _url, socket) do range = Map.get(params, "range", "7d") hours = Map.get(@ranges, range, 24 * 7) since = DateTime.add(DateTime.utc_now(), -hours * 3600, :second) device = socket.assigns.device change_events = ConfigChanges.list_device_changes(device.id, after: since, limit: 100) qoe_data = load_qoe_data(device.id, since) check_data = load_check_data(device.id, since) {:noreply, socket |> assign(:range, range) |> assign(:change_events, change_events) |> assign(:qoe_data, qoe_data) |> assign(:check_data, check_data) |> assign(:selected_event, nil)} end @impl true def handle_event("select_range", %{"range" => range}, socket) do {:noreply, push_patch(socket, to: ~p"/devices/#{socket.assigns.device.id}/config-timeline?range=#{range}")} end @impl true def handle_event("select_event", %{"id" => event_id}, socket) do event = ConfigChanges.get_change_event_with_preloads!(event_id) {:noreply, assign(socket, :selected_event, event)} end @impl true def handle_event("close_event", _, socket) do {:noreply, assign(socket, :selected_event, nil)} end @impl true def render(assigns) do ~H""" <.breadcrumb items={[ %{label: "Dashboard", navigate: ~p"/dashboard"}, %{label: "Devices", navigate: ~p"/devices"}, %{label: @device.name, navigate: ~p"/devices/#{@device.id}"}, %{label: "Config Timeline"} ]} />
<.link navigate={~p"/devices/#{@device.id}?tab=overview"} class="text-cerulean-600 hover:text-cerulean-700 hover:underline dark:text-cerulean-400 dark:hover:text-cerulean-300" > {@device.name} — Config changes vs performance metrics
No config changes in this period.
<% else %>| When | Sections | Size | |
|---|---|---|---|
| {Calendar.strftime(event.changed_at, "%b %d, %H:%M UTC")} | {section} | {event.change_size} lines | <.link navigate={ ~p"/devices/#{@device.id}/backups/compare?before=#{event.backup_before_id}&after=#{event.backup_after_id}" } class="btn text-xs !py-0.5 !px-2 bg-transparent hover:bg-cool-steel-200 dark:hover:bg-cool-steel-700" > View Diff |
{@selected_event.change_size}
{@selected_event.diff_summary}