diff --git a/assets/js/app.ts b/assets/js/app.ts index 74d2cd86..f64be99a 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -1178,10 +1178,21 @@ const GlobalSearch = { } } +// Status Title - Updates page title with status emoji when alerts change +const StatusTitle = { + mounted(this: any) { + this.handleEvent("update_status_emoji", ({ emoji }: { emoji: string }) => { + const currentTitle = document.title + const titleWithoutEmoji = currentTitle.replace(/^[🔴🟡🟢]\s/, "") + document.title = emoji ? `${emoji} ${titleWithoutEmoji}` : titleWithoutEmoji + }) + } +} + const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: { _csrf_token: csrfToken, timezone: userTimezone }, - hooks: { ...colocatedHooks, SensorChart, CopyToClipboard, ScrollToTop, AutoDismissFlash, BetaBannerDismiss, NetworkMap, SitesMap, LeafletMap, DeviceListReorder, MikrotikPortSync, GlobalSearch, GlobalSearchTrigger, DynamicFavicon }, + hooks: { ...colocatedHooks, SensorChart, CopyToClipboard, ScrollToTop, AutoDismissFlash, BetaBannerDismiss, NetworkMap, SitesMap, LeafletMap, DeviceListReorder, MikrotikPortSync, GlobalSearch, GlobalSearchTrigger, DynamicFavicon, StatusTitle }, }) // Show progress bar on live navigation and form submits diff --git a/lib/towerops/alerts.ex b/lib/towerops/alerts.ex index 84435d56..a772d487 100644 --- a/lib/towerops/alerts.ex +++ b/lib/towerops/alerts.ex @@ -10,6 +10,16 @@ defmodule Towerops.Alerts do alias Towerops.Repo alias Towerops.Workers.AlertNotificationWorker + defp broadcast_alert_change(%Alert{organization_id: org_id}) when not is_nil(org_id) do + Phoenix.PubSub.broadcast( + Towerops.PubSub, + "organization:#{org_id}:alerts", + {:alert_changed, org_id} + ) + end + + defp broadcast_alert_change(_alert), do: :ok + @doc """ Creates an alert for device status change. @@ -27,6 +37,7 @@ defmodule Towerops.Alerts do with {:ok, alert} <- Repo.insert(Alert.changeset(%Alert{}, attrs)) do maybe_compute_gaiia_impact(alert) + broadcast_alert_change(alert) {:ok, alert} end end @@ -295,6 +306,7 @@ defmodule Towerops.Alerts do case result do {:ok, updated_alert} -> AlertNotificationWorker.enqueue_resolve(updated_alert.id) + broadcast_alert_change(updated_alert) {:ok, updated_alert} error -> @@ -306,9 +318,19 @@ defmodule Towerops.Alerts do Resolves an alert without notifying PagerDuty (used when PagerDuty is the source). """ def resolve_alert_silent(alert) do - alert - |> Alert.changeset(%{resolved_at: DateTime.truncate(DateTime.utc_now(), :second)}) - |> Repo.update() + result = + alert + |> Alert.changeset(%{resolved_at: DateTime.truncate(DateTime.utc_now(), :second)}) + |> Repo.update() + + case result do + {:ok, updated_alert} -> + broadcast_alert_change(updated_alert) + {:ok, updated_alert} + + error -> + error + end end @doc """ diff --git a/lib/towerops_web/components/layouts.ex b/lib/towerops_web/components/layouts.ex index c1bec5a7..374daf5e 100644 --- a/lib/towerops_web/components/layouts.ex +++ b/lib/towerops_web/components/layouts.ex @@ -699,6 +699,11 @@ defmodule ToweropsWeb.Layouts do id="global-search" organization_id={@current_organization.id} /> + <.live_component + module={ToweropsWeb.Live.Components.StatusTitleComponent} + id="status-title" + organization_id={@current_organization.id} + /> <% end %> """ end diff --git a/lib/towerops_web/live/components/status_title_component.ex b/lib/towerops_web/live/components/status_title_component.ex new file mode 100644 index 00000000..fbd47c5a --- /dev/null +++ b/lib/towerops_web/live/components/status_title_component.ex @@ -0,0 +1,41 @@ +defmodule ToweropsWeb.Live.Components.StatusTitleComponent do + @moduledoc """ + LiveComponent that subscribes to organization alert changes and updates the page title emoji. + """ + use ToweropsWeb, :live_component + + alias ToweropsWeb.Helpers.StatusHelpers + + def mount(socket) do + {:ok, socket} + end + + def update(%{organization_id: org_id}, socket) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Towerops.PubSub, "organization:#{org_id}:alerts") + end + + {:ok, assign(socket, :organization_id, org_id)} + end + + def handle_info({:alert_changed, _org_id}, socket) do + # Get the organization from the parent socket + org_id = socket.assigns.organization_id + organization = Towerops.Organizations.get_organization!(org_id) + + if organization do + emoji = StatusHelpers.status_emoji(organization) + # Push event to JavaScript hook to update the browser title + {:noreply, push_event(socket, "update_status_emoji", %{emoji: emoji})} + else + {:noreply, socket} + end + end + + def render(assigns) do + ~H""" + + """ + end +end diff --git a/lib/towerops_web/user_auth.ex b/lib/towerops_web/user_auth.ex index 0b2be73d..d6b3b0a3 100644 --- a/lib/towerops_web/user_auth.ex +++ b/lib/towerops_web/user_auth.ex @@ -542,6 +542,16 @@ defmodule ToweropsWeb.UserAuth do {:cont, socket} end + def on_mount(:subscribe_to_status_updates, _params, _session, socket) do + organization = socket.assigns[:current_scope] && socket.assigns.current_scope.organization + + if organization && LiveView.connected?(socket) do + Phoenix.PubSub.subscribe(Towerops.PubSub, "organization:#{organization.id}:alerts") + end + + {:cont, socket} + end + def on_mount(:load_default_organization, _params, session, socket) do user = socket.assigns.current_scope && socket.assigns.current_scope.user