From 478e7b19366d994a142fe95d60405dc8f1ec8ded Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 28 Jul 2025 13:19:38 -0500 Subject: [PATCH] Add deployment notification system to update timestamp in LiveViews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a new deployment occurs in k8s, all connected LiveViews now receive a notification to update their deployment timestamp display. Changes: - Added DeploymentNotifier GenServer that broadcasts deployment events - LiveViews subscribe to "deployment_events" PubSub topic - When DEPLOYED_AT env var is set (k8s deployments), a notification is sent after app startup - LiveViews handle the deployment event and update their timestamp This ensures that when a new version is deployed, users see the deployment timestamp update in real-time without needing to refresh their browser. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/aprsme/application.ex | 2 + lib/aprsme/deployment_notifier.ex | 62 +++++++++++++++++++++++++++ lib/aprsme/release.ex | 21 +++++++++ lib/aprsme_web/live/map_live/index.ex | 13 +++++- 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 lib/aprsme/deployment_notifier.ex diff --git a/lib/aprsme/application.ex b/lib/aprsme/application.ex index 3d360ae..f73ea0c 100644 --- a/lib/aprsme/application.ex +++ b/lib/aprsme/application.ex @@ -49,6 +49,8 @@ defmodule Aprsme.Application do Aprsme.CleanupScheduler, Aprsme.Presence, Aprsme.PostgresNotifier, + # Start deployment notifier + Aprsme.DeploymentNotifier, # Start the packet processing pipeline Aprsme.PacketPipelineSupervisor ] diff --git a/lib/aprsme/deployment_notifier.ex b/lib/aprsme/deployment_notifier.ex new file mode 100644 index 0000000..d1d947e --- /dev/null +++ b/lib/aprsme/deployment_notifier.ex @@ -0,0 +1,62 @@ +defmodule Aprsme.DeploymentNotifier do + @moduledoc """ + Monitors for deployment changes and notifies connected clients. + In k8s, this detects when the DEPLOYED_AT environment variable changes. + """ + use GenServer + + require Logger + + @check_interval :timer.seconds(30) + + def start_link(opts \\ []) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(_opts) do + # Schedule first check + Process.send_after(self(), :check_deployment, @check_interval) + + # Get initial deployment timestamp + deployed_at = Aprsme.Release.deployed_at() + + {:ok, %{deployed_at: deployed_at}} + end + + def handle_info(:check_deployment, state) do + # Schedule next check + Process.send_after(self(), :check_deployment, @check_interval) + + # Get current deployment timestamp + current_deployed_at = Aprsme.Release.deployed_at() + + # Check if deployment timestamp changed (shouldn't happen in same process, but useful for monitoring) + if current_deployed_at == state.deployed_at do + {:noreply, state} + + # Broadcast the new deployment + else + Logger.info("Deployment timestamp changed from #{state.deployed_at} to #{current_deployed_at}") + + Phoenix.PubSub.broadcast( + Aprsme.PubSub, + "deployment_events", + {:new_deployment, %{deployed_at: current_deployed_at}} + ) + + {:noreply, %{state | deployed_at: current_deployed_at}} + end + end + + @doc """ + Notify about a new deployment immediately. + This can be called from the release module when deployment is detected. + """ + def notify_deployment(deployed_at) do + Phoenix.PubSub.broadcast( + Aprsme.PubSub, + "deployment_events", + {:new_deployment, %{deployed_at: deployed_at}} + ) + end +end diff --git a/lib/aprsme/release.ex b/lib/aprsme/release.ex index 294a4bd..93b5429 100644 --- a/lib/aprsme/release.ex +++ b/lib/aprsme/release.ex @@ -100,6 +100,27 @@ defmodule Aprsme.Release do # Store in application config Application.put_env(:aprsme, :deployed_at, deployed_at) + # Notify about deployment after a short delay to ensure PubSub is started + # In k8s, this will notify all connected clients about the new deployment + if System.get_env("DEPLOYED_AT") do + spawn(fn -> + # Wait for application to start + Process.sleep(10_000) + + try do + require Logger + + Aprsme.DeploymentNotifier.notify_deployment(deployed_at) + Logger.info("Deployment notification sent for timestamp: #{deployed_at}") + rescue + error -> + require Logger + + Logger.warning("Failed to send deployment notification: #{inspect(error)}") + end + end) + end + deployed_at end diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index 68da3c1..587b1c5 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -28,6 +28,7 @@ defmodule AprsmeWeb.MapLive.Index do alias AprsmeWeb.MapLive.UrlParams alias AprsmeWeb.TimeUtils alias Phoenix.LiveView.Socket + alias Phoenix.Socket.Broadcast @impl true def mount(params, session, socket) do @@ -107,6 +108,9 @@ defmodule AprsmeWeb.MapLive.Index do # Still subscribe to bad packets (they don't have location) Phoenix.PubSub.subscribe(Aprsme.PubSub, "bad_packets") + # Subscribe to deployment events + Phoenix.PubSub.subscribe(Aprsme.PubSub, "deployment_events") + # Subscribe to StreamingPacketsPubSub with initial bounds Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), default_bounds) @@ -671,6 +675,13 @@ defmodule AprsmeWeb.MapLive.Index do def handle_info({:streaming_packet, packet}, socket), do: handle_info_postgres_packet(packet, socket) + def handle_info( + %Broadcast{topic: "deployment_events", payload: {:new_deployment, %{deployed_at: deployed_at}}}, + socket + ) do + {:noreply, assign(socket, :deployed_at, deployed_at)} + end + def handle_info({:load_rf_path_station_packets, stations}, socket) do # Load the most recent packet for each RF path station station_packets = @@ -722,7 +733,7 @@ defmodule AprsmeWeb.MapLive.Index do end end - def handle_info(%Phoenix.Socket.Broadcast{topic: "aprs_messages", event: "packet", payload: packet}, socket), + def handle_info(%Broadcast{topic: "aprs_messages", event: "packet", payload: packet}, socket), do: handle_info({:postgres_packet, packet}, socket) def handle_info({:show_error, message}, socket) do