From baaf5ccb791071725793201b8d4889ffb44579af Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 11:22:06 +0200 Subject: [PATCH 01/14] Add Phoenix LiveDashboard on a separate port Serve the dashboard on a dedicated Bandit instance configured via ELECTRIC_LIVE_DASHBOARD_PORT to avoid interfering with the existing Plug-based HTTP API. When the env var is unset the dashboard is not started. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/sync-service/config/runtime.exs | 20 ++++++++++ .../sync-service/lib/electric/application.ex | 12 +++++- .../sync-service/lib/electric/error_view.ex | 10 +++++ .../lib/electric/favicon_controller.ex | 20 ++++++++++ .../lib/electric/live_dashboard_endpoint.ex | 39 +++++++++++++++++++ .../lib/electric/live_dashboard_metrics.ex | 26 +++++++++++++ .../lib/electric/live_dashboard_router.ex | 28 +++++++++++++ packages/sync-service/mix.exs | 3 +- packages/sync-service/mix.lock | 7 ++++ 9 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 packages/sync-service/lib/electric/error_view.ex create mode 100644 packages/sync-service/lib/electric/favicon_controller.ex create mode 100644 packages/sync-service/lib/electric/live_dashboard_endpoint.ex create mode 100644 packages/sync-service/lib/electric/live_dashboard_metrics.ex create mode 100644 packages/sync-service/lib/electric/live_dashboard_router.ex diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index 892b55cafd..296a009bfa 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -150,6 +150,7 @@ replication_stream_id = ) prometheus_port = env!("ELECTRIC_PROMETHEUS_PORT", :integer, nil) +live_dashboard_port = env!("ELECTRIC_LIVE_DASHBOARD_PORT", :integer, nil) call_home_telemetry_url = env!( @@ -240,6 +241,7 @@ config :electric, env!("ELECTRIC_TELEMETRY_LONG_MESSAGE_QUEUE_DISABLE_THRESHOLD", :integer, nil), telemetry_statsd_host: statsd_host, prometheus_port: prometheus_port, + live_dashboard_port: live_dashboard_port, db_pool_size: env!("ELECTRIC_DB_POOL_SIZE", :integer, nil), replication_stream_id: replication_stream_id, replication_slot_temporary?: env!("CLEANUP_REPLICATION_SLOTS_ON_SHUTDOWN", :boolean, nil), @@ -387,3 +389,21 @@ if Electric.telemetry_enabled?() do config :opentelemetry, processors: [] end end + +# Phoenix LiveDashboard Endpoint Configuration +if live_dashboard_port do + config :electric, Electric.LiveDashboardEndpoint, + adapter: Bandit.PhoenixAdapter, + http: [ + port: live_dashboard_port, + ip: {0, 0, 0, 0} + ], + server: true, + render_errors: [ + formats: [html: Electric.ErrorView], + layout: false + ], + live_view: [signing_salt: "live_dashboard_salt"], + secret_key_base: String.duplicate("a", 64), + pubsub_server: Electric.PubSub +end diff --git a/packages/sync-service/lib/electric/application.ex b/packages/sync-service/lib/electric/application.ex index 2d22006f25..9c7b2d8a76 100644 --- a/packages/sync-service/lib/electric/application.ex +++ b/packages/sync-service/lib/electric/application.ex @@ -53,7 +53,8 @@ defmodule Electric.Application do application_telemetry(config), [{Electric.StackSupervisor, Keyword.put(config, :name, Electric.StackSupervisor)}], api_server_children(config), - prometheus_endpoint(Electric.Config.get_env(:prometheus_port)) + prometheus_endpoint(Electric.Config.get_env(:prometheus_port)), + live_dashboard_children(Application.get_env(:electric, :live_dashboard_port)) ]) end @@ -270,6 +271,15 @@ defmodule Electric.Application do ] end + defp live_dashboard_children(nil), do: [] + + defp live_dashboard_children(_port) do + [ + {Phoenix.PubSub, name: Electric.PubSub}, + Electric.LiveDashboardEndpoint + ] + end + @doc false # REQUIRED (but undocumented) public API for Phoenix.Sync def api_server do diff --git a/packages/sync-service/lib/electric/error_view.ex b/packages/sync-service/lib/electric/error_view.ex new file mode 100644 index 0000000000..c9dabd7a82 --- /dev/null +++ b/packages/sync-service/lib/electric/error_view.ex @@ -0,0 +1,10 @@ +defmodule Electric.ErrorView do + @moduledoc """ + Error view for the LiveDashboard endpoint. + Handles rendering of HTTP errors. + """ + + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/packages/sync-service/lib/electric/favicon_controller.ex b/packages/sync-service/lib/electric/favicon_controller.ex new file mode 100644 index 0000000000..84fb97cb34 --- /dev/null +++ b/packages/sync-service/lib/electric/favicon_controller.ex @@ -0,0 +1,20 @@ +defmodule Electric.FaviconController do + @moduledoc """ + Simple controller to handle favicon requests. + Returns a 204 No Content response to avoid 500 errors. + """ + + import Plug.Conn + + def init(opts), do: opts + + def call(conn, _opts) do + show(conn, %{}) + end + + def show(conn, _params) do + conn + |> send_resp(204, "") + |> halt() + end +end diff --git a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex new file mode 100644 index 0000000000..ee99adea3e --- /dev/null +++ b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex @@ -0,0 +1,39 @@ +defmodule Electric.LiveDashboardEndpoint do + @moduledoc """ + Phoenix Endpoint for serving LiveDashboard on a separate port. + This runs independently from the main Plug-based HTTP server. + """ + + use Phoenix.Endpoint, otp_app: :electric + + # LiveView socket for dashboard interactions + socket "/live", Phoenix.LiveView.Socket, + websocket: true, + longpoll: true + + # Serve static assets for LiveDashboard + plug Plug.Static, + at: "/", + from: :phoenix_live_dashboard, + gzip: false, + only: ~w(assets fonts images priv) + + # Session configuration for LiveView + plug Plug.Session, + store: :cookie, + key: "_live_dashboard_key", + signing_salt: "live_dashboard", + same_site: "Lax" + + # Parse request body for LiveView + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Jason + + plug Plug.MethodOverride + plug Plug.Head + + # Route all requests to the LiveDashboard router + plug Electric.LiveDashboardRouter +end diff --git a/packages/sync-service/lib/electric/live_dashboard_metrics.ex b/packages/sync-service/lib/electric/live_dashboard_metrics.ex new file mode 100644 index 0000000000..dcc9286437 --- /dev/null +++ b/packages/sync-service/lib/electric/live_dashboard_metrics.ex @@ -0,0 +1,26 @@ +defmodule Electric.LiveDashboardMetrics do + @moduledoc """ + Telemetry metrics for Phoenix LiveDashboard. + """ + + import Telemetry.Metrics + + def metrics do + [ + # VM Metrics + last_value("vm.memory.total", unit: {:byte, :megabyte}), + last_value("vm.total_run_queue_lengths.total"), + last_value("vm.total_run_queue_lengths.cpu"), + last_value("vm.total_run_queue_lengths.io"), + + # HTTP Metrics + summary("electric.routing.stop.duration", + unit: {:native, :millisecond}, + tags: [:method, :path_info] + ), + counter("electric.routing.stop.count", + tags: [:method, :status] + ) + ] + end +end diff --git a/packages/sync-service/lib/electric/live_dashboard_router.ex b/packages/sync-service/lib/electric/live_dashboard_router.ex new file mode 100644 index 0000000000..4e1c1149e7 --- /dev/null +++ b/packages/sync-service/lib/electric/live_dashboard_router.ex @@ -0,0 +1,28 @@ +defmodule Electric.LiveDashboardRouter do + @moduledoc """ + Phoenix Router for LiveDashboard. + Mounts the dashboard at the root path. + """ + + use Phoenix.Router + import Phoenix.LiveDashboard.Router + + pipeline :browser do + plug :accepts, ["html", "json"] + plug :fetch_session + plug :put_root_layout, html: {Phoenix.LiveDashboard.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + scope "/" do + pipe_through :browser + + # Handle favicon requests gracefully + get "/favicon.ico", Electric.FaviconController, :show + + live_dashboard "/", + metrics: {Electric.LiveDashboardMetrics, :metrics}, + ecto_repos: [] + end +end diff --git a/packages/sync-service/mix.exs b/packages/sync-service/mix.exs index db6e2b1170..c06759cb94 100644 --- a/packages/sync-service/mix.exs +++ b/packages/sync-service/mix.exs @@ -105,7 +105,8 @@ defmodule Electric.MixProject do {:remote_ip, "~> 1.2"}, {:req, "~> 0.5"}, {:stream_split, "~> 0.1"}, - {:tz, "~> 0.28"} + {:tz, "~> 0.28"}, + {:phoenix_live_dashboard, "~> 0.8"} ], dev_and_test_deps(), telemetry_deps(Mix.target()) diff --git a/packages/sync-service/mix.lock b/packages/sync-service/mix.lock index 4ba0f43d99..ff28515260 100644 --- a/packages/sync-service/mix.lock +++ b/packages/sync-service/mix.lock @@ -41,6 +41,12 @@ "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, "otel_metric_exporter": {:hex, :otel_metric_exporter, "0.4.3", "c8e47eae9f222e100b590eb95246e49ea4a376bf3595067ee089ddfc7169405a", [:mix], [{:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.15", [hex: :protobuf, repo: "hexpm", optional: false]}, {:retry, "~> 0.19", [hex: :retry, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "6cdb0a9bcd6f4bee3e70b7b8664af8b6d23156e978ccedde47782f56b6636531"}, "pg_query_ex": {:hex, :pg_query_ex, "0.10.0", "3dc3a20f90173bd6e893db58062a6bbeda583bd7962793235da0cde046ac52da", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:protox, "~> 2.0", [hex: :protox, repo: "hexpm", optional: false]}], "hexpm", "783f2082ff9534298eab5570a9317f510c425fcbe4ae88d502561207fdd1bdd9"}, + "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.28", "8a8e123d018025f756605a2fb02a4854f0d3cd7b207f710fef1fd5d9d72d0254", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24faad535b65089642c3a7d84088109dc58f49c1f1c5a978659855d643466353"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, @@ -63,4 +69,5 @@ "tls_certificate_check": {:hex, :tls_certificate_check, "1.31.0", "9a910b54d8cb96cc810cabf4c0129f21360f82022b20180849f1442a25ccbb04", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "9d2b41b128d5507bd8ad93e1a998e06d0ab2f9a772af343f4c00bf76c6be1532"}, "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, } From 05d1ebacda6a74bcde3ebd0a158d6a41fbae6dbd Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 11:24:54 +0200 Subject: [PATCH 02/14] Upgrade Bandit --- packages/sync-service/mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sync-service/mix.lock b/packages/sync-service/mix.lock index ff28515260..050dcf7c05 100644 --- a/packages/sync-service/mix.lock +++ b/packages/sync-service/mix.lock @@ -1,7 +1,7 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.1", "d88c2e8a0be9216cf513fbcd3e5a4beb36bee3ff4168e85d6152c6f899359cdb", [:rebar3], [], "hexpm", "f172f3d74513e8edd445c257d596fc84dbdd56d2c6fa287434269648ae5a421e"}, "backoff": {:hex, :backoff, "1.1.6", "83b72ed2108ba1ee8f7d1c22e0b4a00cfe3593a67dbc792799e8cce9f42f796b", [:rebar3], [], "hexpm", "cf0cfff8995fb20562f822e5cc47d8ccf664c5ecdc26a684cbe85c225f9d7c39"}, - "bandit": {:hex, :bandit, "1.10.3", "1e5d168fa79ec8de2860d1b4d878d97d4fbbe2fdbe7b0a7d9315a4359d1d4bb9", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "99a52d909c48db65ca598e1962797659e3c0f1d06e825a50c3d75b74a5e2db18"}, + "bandit": {:hex, :bandit, "1.10.4", "02b9734c67c5916a008e7eb7e2ba68aaea6f8177094a5f8d95f1fb99069aac17", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "a5faf501042ac1f31d736d9d4a813b3db4ef812e634583b6a457b0928798a51d"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -51,7 +51,7 @@ "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"}, "protobuf": {:hex, :protobuf, "0.13.0", "7a9d9aeb039f68a81717eb2efd6928fdf44f03d2c0dfdcedc7b560f5f5aae93d", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "21092a223e3c6c144c1a291ab082a7ead32821ba77073b72c68515aa51fef570"}, - "protox": {:hex, :protox, "2.0.4", "2a86ae3699696c5d92e15804968ce6a6827a8d9516d0bbabcf16584dec710ae1", [:mix], [], "hexpm", "8ac5a03bb84da4c75d76dc29cd46008081c2068ad0f6f0da4c051093d6e24c01"}, + "protox": {:hex, :protox, "2.0.7", "fa4639568a045f034fc756ad62b0edf59c934f6ad2aa163a8ba457209d54f8cb", [:mix], [], "hexpm", "42efb28326e7003f4a2d3a809176b8ba2f01a950e36f6470364b64c288919cb2"}, "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, "repatch": {:hex, :repatch, "1.6.1", "e2bc55d7358de5727a7d989886d9aebd6497ccab4232b41dcae54f8fd6539c19", [:mix], [{:ex2ms, "~> 1.7.0", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm", "486f80b503fad097ae9ff33f3c7ad3a24c2bad85e694d76ff214e15d13cf9d25"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, From 673bd848d0c25343b62305441fc27afe4990feb2 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 11:56:57 +0200 Subject: [PATCH 03/14] Remove LiveDashboardMetrics They tend to not load at all and the graph layout is barely informative --- .../lib/electric/live_dashboard_metrics.ex | 26 ------------------- .../lib/electric/live_dashboard_router.ex | 5 ++-- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 packages/sync-service/lib/electric/live_dashboard_metrics.ex diff --git a/packages/sync-service/lib/electric/live_dashboard_metrics.ex b/packages/sync-service/lib/electric/live_dashboard_metrics.ex deleted file mode 100644 index dcc9286437..0000000000 --- a/packages/sync-service/lib/electric/live_dashboard_metrics.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Electric.LiveDashboardMetrics do - @moduledoc """ - Telemetry metrics for Phoenix LiveDashboard. - """ - - import Telemetry.Metrics - - def metrics do - [ - # VM Metrics - last_value("vm.memory.total", unit: {:byte, :megabyte}), - last_value("vm.total_run_queue_lengths.total"), - last_value("vm.total_run_queue_lengths.cpu"), - last_value("vm.total_run_queue_lengths.io"), - - # HTTP Metrics - summary("electric.routing.stop.duration", - unit: {:native, :millisecond}, - tags: [:method, :path_info] - ), - counter("electric.routing.stop.count", - tags: [:method, :status] - ) - ] - end -end diff --git a/packages/sync-service/lib/electric/live_dashboard_router.ex b/packages/sync-service/lib/electric/live_dashboard_router.ex index 4e1c1149e7..1a9e951bfe 100644 --- a/packages/sync-service/lib/electric/live_dashboard_router.ex +++ b/packages/sync-service/lib/electric/live_dashboard_router.ex @@ -21,8 +21,7 @@ defmodule Electric.LiveDashboardRouter do # Handle favicon requests gracefully get "/favicon.ico", Electric.FaviconController, :show - live_dashboard "/", - metrics: {Electric.LiveDashboardMetrics, :metrics}, - ecto_repos: [] + live_dashboard "/", ecto_repos: [] + end end From e019c0e58b8861d2947c415a881afe1be54cbc8b Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 12:01:45 +0200 Subject: [PATCH 04/14] Configure a real secret key base for live dashboard --- packages/sync-service/config/runtime.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index 296a009bfa..c871299791 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -404,6 +404,6 @@ if live_dashboard_port do layout: false ], live_view: [signing_salt: "live_dashboard_salt"], - secret_key_base: String.duplicate("a", 64), + secret_key_base: Base.encode64(:crypto.strong_rand_bytes(48)), pubsub_server: Electric.PubSub end From 5a378dca2a90e3f19122aace0e3b9f4edf4744c5 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:03:33 +0200 Subject: [PATCH 05/14] Include formatting rules from phoenix --- packages/sync-service/.formatter.exs | 2 +- packages/sync-service/lib/electric/live_dashboard_router.ex | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sync-service/.formatter.exs b/packages/sync-service/.formatter.exs index 27e06df52f..e7f97f0185 100644 --- a/packages/sync-service/.formatter.exs +++ b/packages/sync-service/.formatter.exs @@ -1,5 +1,5 @@ # Used by "mix format" [ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], - import_deps: [:plug, :stream_data] + import_deps: [:plug, :stream_data, :phoenix] ] diff --git a/packages/sync-service/lib/electric/live_dashboard_router.ex b/packages/sync-service/lib/electric/live_dashboard_router.ex index 1a9e951bfe..bafeab0c65 100644 --- a/packages/sync-service/lib/electric/live_dashboard_router.ex +++ b/packages/sync-service/lib/electric/live_dashboard_router.ex @@ -22,6 +22,5 @@ defmodule Electric.LiveDashboardRouter do get "/favicon.ico", Electric.FaviconController, :show live_dashboard "/", ecto_repos: [] - end end From 83bd6362eea8197868111b93dcb7ffdf809e686d Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:11:52 +0200 Subject: [PATCH 06/14] Randomize lv salts --- packages/sync-service/config/runtime.exs | 2 +- packages/sync-service/lib/electric/live_dashboard_endpoint.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index c871299791..173f150835 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -403,7 +403,7 @@ if live_dashboard_port do formats: [html: Electric.ErrorView], layout: false ], - live_view: [signing_salt: "live_dashboard_salt"], + live_view: [signing_salt: "r5zw+GcXjt3wP3Z/snFRqQ5uH2cm8Vb7ldc8t0POZdo="], secret_key_base: Base.encode64(:crypto.strong_rand_bytes(48)), pubsub_server: Electric.PubSub end diff --git a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex index ee99adea3e..1cd71a96c0 100644 --- a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex +++ b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex @@ -22,7 +22,7 @@ defmodule Electric.LiveDashboardEndpoint do plug Plug.Session, store: :cookie, key: "_live_dashboard_key", - signing_salt: "live_dashboard", + signing_salt: "abc43s8Z", same_site: "Lax" # Parse request body for LiveView From 334d9840d8ed5d670e5eb5d32abd105357d5017d Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:12:03 +0200 Subject: [PATCH 07/14] Fix review feedback about naming consistency --- packages/sync-service/lib/electric/application.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sync-service/lib/electric/application.ex b/packages/sync-service/lib/electric/application.ex index 9c7b2d8a76..ab5661cee7 100644 --- a/packages/sync-service/lib/electric/application.ex +++ b/packages/sync-service/lib/electric/application.ex @@ -54,7 +54,7 @@ defmodule Electric.Application do [{Electric.StackSupervisor, Keyword.put(config, :name, Electric.StackSupervisor)}], api_server_children(config), prometheus_endpoint(Electric.Config.get_env(:prometheus_port)), - live_dashboard_children(Application.get_env(:electric, :live_dashboard_port)) + live_dashboard_endpoint(Application.get_env(:electric, :live_dashboard_port)) ]) end @@ -271,9 +271,9 @@ defmodule Electric.Application do ] end - defp live_dashboard_children(nil), do: [] + defp live_dashboard_endpoint(nil), do: [] - defp live_dashboard_children(_port) do + defp live_dashboard_endpoint(_port) do [ {Phoenix.PubSub, name: Electric.PubSub}, Electric.LiveDashboardEndpoint From ab11ebe4289e21c6fb14b746f04e5a166c71d0c8 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:14:08 +0200 Subject: [PATCH 08/14] Turn favicon controller into a Phoenix controller --- .../sync-service/lib/electric/favicon_controller.ex | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/sync-service/lib/electric/favicon_controller.ex b/packages/sync-service/lib/electric/favicon_controller.ex index 84fb97cb34..24e0e309b4 100644 --- a/packages/sync-service/lib/electric/favicon_controller.ex +++ b/packages/sync-service/lib/electric/favicon_controller.ex @@ -4,17 +4,9 @@ defmodule Electric.FaviconController do Returns a 204 No Content response to avoid 500 errors. """ - import Plug.Conn - - def init(opts), do: opts - - def call(conn, _opts) do - show(conn, %{}) - end + use Phoenix.Controller, formats: [:html] def show(conn, _params) do - conn - |> send_resp(204, "") - |> halt() + send_resp(conn, 204, "") end end From 623142b92dabe6d9ce98a9f9eefc3e619a83daa1 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:14:19 +0200 Subject: [PATCH 09/14] Document unauthenticated setup of live dashboard --- packages/sync-service/config/runtime.exs | 5 +++++ .../sync-service/lib/electric/live_dashboard_endpoint.ex | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index 173f150835..51fdecec15 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -391,6 +391,11 @@ if Electric.telemetry_enabled?() do end # Phoenix LiveDashboard Endpoint Configuration +# +# WARNING: The dashboard is completely unauthenticated and exposes internal +# system state (VM metrics, process info, ETS tables, etc.). In production, +# ensure the dashboard port is firewalled or otherwise restricted to trusted +# networks only. if live_dashboard_port do config :electric, Electric.LiveDashboardEndpoint, adapter: Bandit.PhoenixAdapter, diff --git a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex index 1cd71a96c0..426e9eadab 100644 --- a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex +++ b/packages/sync-service/lib/electric/live_dashboard_endpoint.ex @@ -2,6 +2,11 @@ defmodule Electric.LiveDashboardEndpoint do @moduledoc """ Phoenix Endpoint for serving LiveDashboard on a separate port. This runs independently from the main Plug-based HTTP server. + + **WARNING: This endpoint is completely unauthenticated.** It exposes internal + system state (VM metrics, process info, etc.). In production, ensure the + dashboard port is not publicly accessible — use firewall rules or network + policies to restrict access. """ use Phoenix.Endpoint, otp_app: :electric From 9bac2eb4411b8d3e22cd7d73a822ef89c018ac34 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:15:00 +0200 Subject: [PATCH 10/14] Add changeset --- .changeset/nice-taxis-shake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-taxis-shake.md diff --git a/.changeset/nice-taxis-shake.md b/.changeset/nice-taxis-shake.md new file mode 100644 index 0000000000..eb56e71683 --- /dev/null +++ b/.changeset/nice-taxis-shake.md @@ -0,0 +1,5 @@ +--- +'@core/sync-service': patch +--- + +Add optional PhoenixLiveDashboard with configurable port to listen on. From dfc78e811e74bd5578fa78a43d387bd042568e17 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:16:25 +0200 Subject: [PATCH 11/14] Document Phoenix LiveDashboard in repo README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 9e20fd25d7..dc1ea7741c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Real-time sync for Postgres. - [What is Electric?](#what-is-electric) - [Getting Started](#getting-started) - [HTTP API Docs](#http-api-docs) +- [Phoenix LiveDashboard](#phoenix-livedashboard) - [Developing Electric](#developing-electric) - [Mac setup](#mac-setup) - [Contributing](#contributing) @@ -98,6 +99,20 @@ Again, see the [Quickstart](https://electric-sql.com/docs/quickstart) and the [D The HTTP API is defined in an [OpenAPI spec](https://swagger.io/specification/) in [website/electric-api.yaml](./website/electric-api.yaml). +## Phoenix LiveDashboard + +Electric includes an optional [Phoenix LiveDashboard](https://github.com/phoenixframework/phoenix_live_dashboard) for real-time monitoring of the running system (VM metrics, process info, ETS tables, etc.). + +To enable it, set the `ELECTRIC_LIVE_DASHBOARD_PORT` environment variable: + +```sh +ELECTRIC_LIVE_DASHBOARD_PORT=4000 +``` + +The dashboard will be available at `http://localhost:4000` (or whichever port you choose). When the variable is not set, the dashboard is not started. + +> **WARNING: The LiveDashboard endpoint is completely unauthenticated.** Anyone with network access to the port can view internal system state. In production, you **must** restrict access to this port using firewall rules, network policies, or similar controls. Do not expose it to the public internet. + ## Developing Electric We use [asdf](https://asdf-vm.com/) to install Elixir, Erlang, and Node.js. Versions are defined in [.tool-versions](.tool-versions). From 46eead6beb9ec8b5cc9b32a2ae0f62d49b43be26 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Mon, 13 Apr 2026 13:18:06 +0200 Subject: [PATCH 12/14] Address review feedback on LiveDashboard PR - Respect ELECTRIC_LISTEN_ON_IPV6 when binding the dashboard socket - Make FaviconController a proper Phoenix controller - Add unauthenticated-endpoint warnings in moduledoc, config, and README Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/sync-service/config/runtime.exs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index 51fdecec15..191fa332e9 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -397,11 +397,16 @@ end # ensure the dashboard port is firewalled or otherwise restricted to trusted # networks only. if live_dashboard_port do + dashboard_ip = + if env!("ELECTRIC_LISTEN_ON_IPV6", :boolean, false), + do: {0, 0, 0, 0, 0, 0, 0, 0}, + else: {0, 0, 0, 0} + config :electric, Electric.LiveDashboardEndpoint, adapter: Bandit.PhoenixAdapter, http: [ port: live_dashboard_port, - ip: {0, 0, 0, 0} + ip: dashboard_ip ], server: true, render_errors: [ From de512b2ee36b3e460ec8739e4d747733ddbe31ff Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Tue, 14 Apr 2026 15:27:25 +0200 Subject: [PATCH 13/14] Move LiveDashboard modules under Electric.LiveDashboard namespace Addresses review feedback to avoid polluting the top-level namespace with an optional component. Also uses Electric.Config.get_env for consistency. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/sync-service/config/runtime.exs | 4 ++-- packages/sync-service/lib/electric/application.ex | 4 ++-- .../endpoint.ex} | 4 ++-- .../lib/electric/{ => live_dashboard}/error_view.ex | 2 +- .../lib/electric/{ => live_dashboard}/favicon_controller.ex | 2 +- .../{live_dashboard_router.ex => live_dashboard/router.ex} | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) rename packages/sync-service/lib/electric/{live_dashboard_endpoint.ex => live_dashboard/endpoint.ex} (93%) rename packages/sync-service/lib/electric/{ => live_dashboard}/error_view.ex (82%) rename packages/sync-service/lib/electric/{ => live_dashboard}/favicon_controller.ex (81%) rename packages/sync-service/lib/electric/{live_dashboard_router.ex => live_dashboard/router.ex} (82%) diff --git a/packages/sync-service/config/runtime.exs b/packages/sync-service/config/runtime.exs index 191fa332e9..554f83aed0 100644 --- a/packages/sync-service/config/runtime.exs +++ b/packages/sync-service/config/runtime.exs @@ -402,7 +402,7 @@ if live_dashboard_port do do: {0, 0, 0, 0, 0, 0, 0, 0}, else: {0, 0, 0, 0} - config :electric, Electric.LiveDashboardEndpoint, + config :electric, Electric.LiveDashboard.Endpoint, adapter: Bandit.PhoenixAdapter, http: [ port: live_dashboard_port, @@ -410,7 +410,7 @@ if live_dashboard_port do ], server: true, render_errors: [ - formats: [html: Electric.ErrorView], + formats: [html: Electric.LiveDashboard.ErrorView], layout: false ], live_view: [signing_salt: "r5zw+GcXjt3wP3Z/snFRqQ5uH2cm8Vb7ldc8t0POZdo="], diff --git a/packages/sync-service/lib/electric/application.ex b/packages/sync-service/lib/electric/application.ex index ab5661cee7..7771809364 100644 --- a/packages/sync-service/lib/electric/application.ex +++ b/packages/sync-service/lib/electric/application.ex @@ -54,7 +54,7 @@ defmodule Electric.Application do [{Electric.StackSupervisor, Keyword.put(config, :name, Electric.StackSupervisor)}], api_server_children(config), prometheus_endpoint(Electric.Config.get_env(:prometheus_port)), - live_dashboard_endpoint(Application.get_env(:electric, :live_dashboard_port)) + live_dashboard_endpoint(Electric.Config.get_env(:live_dashboard_port)) ]) end @@ -276,7 +276,7 @@ defmodule Electric.Application do defp live_dashboard_endpoint(_port) do [ {Phoenix.PubSub, name: Electric.PubSub}, - Electric.LiveDashboardEndpoint + Electric.LiveDashboard.Endpoint ] end diff --git a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex b/packages/sync-service/lib/electric/live_dashboard/endpoint.ex similarity index 93% rename from packages/sync-service/lib/electric/live_dashboard_endpoint.ex rename to packages/sync-service/lib/electric/live_dashboard/endpoint.ex index 426e9eadab..9475771a31 100644 --- a/packages/sync-service/lib/electric/live_dashboard_endpoint.ex +++ b/packages/sync-service/lib/electric/live_dashboard/endpoint.ex @@ -1,4 +1,4 @@ -defmodule Electric.LiveDashboardEndpoint do +defmodule Electric.LiveDashboard.Endpoint do @moduledoc """ Phoenix Endpoint for serving LiveDashboard on a separate port. This runs independently from the main Plug-based HTTP server. @@ -40,5 +40,5 @@ defmodule Electric.LiveDashboardEndpoint do plug Plug.Head # Route all requests to the LiveDashboard router - plug Electric.LiveDashboardRouter + plug Electric.LiveDashboard.Router end diff --git a/packages/sync-service/lib/electric/error_view.ex b/packages/sync-service/lib/electric/live_dashboard/error_view.ex similarity index 82% rename from packages/sync-service/lib/electric/error_view.ex rename to packages/sync-service/lib/electric/live_dashboard/error_view.ex index c9dabd7a82..620d9c9501 100644 --- a/packages/sync-service/lib/electric/error_view.ex +++ b/packages/sync-service/lib/electric/live_dashboard/error_view.ex @@ -1,4 +1,4 @@ -defmodule Electric.ErrorView do +defmodule Electric.LiveDashboard.ErrorView do @moduledoc """ Error view for the LiveDashboard endpoint. Handles rendering of HTTP errors. diff --git a/packages/sync-service/lib/electric/favicon_controller.ex b/packages/sync-service/lib/electric/live_dashboard/favicon_controller.ex similarity index 81% rename from packages/sync-service/lib/electric/favicon_controller.ex rename to packages/sync-service/lib/electric/live_dashboard/favicon_controller.ex index 24e0e309b4..d2f4e193fa 100644 --- a/packages/sync-service/lib/electric/favicon_controller.ex +++ b/packages/sync-service/lib/electric/live_dashboard/favicon_controller.ex @@ -1,4 +1,4 @@ -defmodule Electric.FaviconController do +defmodule Electric.LiveDashboard.FaviconController do @moduledoc """ Simple controller to handle favicon requests. Returns a 204 No Content response to avoid 500 errors. diff --git a/packages/sync-service/lib/electric/live_dashboard_router.ex b/packages/sync-service/lib/electric/live_dashboard/router.ex similarity index 82% rename from packages/sync-service/lib/electric/live_dashboard_router.ex rename to packages/sync-service/lib/electric/live_dashboard/router.ex index bafeab0c65..df467bec4c 100644 --- a/packages/sync-service/lib/electric/live_dashboard_router.ex +++ b/packages/sync-service/lib/electric/live_dashboard/router.ex @@ -1,4 +1,4 @@ -defmodule Electric.LiveDashboardRouter do +defmodule Electric.LiveDashboard.Router do @moduledoc """ Phoenix Router for LiveDashboard. Mounts the dashboard at the root path. @@ -19,7 +19,7 @@ defmodule Electric.LiveDashboardRouter do pipe_through :browser # Handle favicon requests gracefully - get "/favicon.ico", Electric.FaviconController, :show + get "/favicon.ico", Electric.LiveDashboard.FaviconController, :show live_dashboard "/", ecto_repos: [] end From 770e8a59f94bec7a0038f3e9c19a3743e4a262d3 Mon Sep 17 00:00:00 2001 From: Oleksii Sholik Date: Tue, 14 Apr 2026 15:32:50 +0200 Subject: [PATCH 14/14] fixup! Move LiveDashboard modules under Electric.LiveDashboard namespace --- packages/sync-service/lib/electric/config.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sync-service/lib/electric/config.ex b/packages/sync-service/lib/electric/config.ex index 83764bb332..9fd47cf729 100644 --- a/packages/sync-service/lib/electric/config.ex +++ b/packages/sync-service/lib/electric/config.ex @@ -121,7 +121,8 @@ defmodule Electric.Config do shape_db_synchronous: Electric.ShapeCache.ShapeStatus.ShapeDb.Connection.default!(:synchronous), shape_db_cache_size: Electric.ShapeCache.ShapeStatus.ShapeDb.Connection.default!(:cache_size), - exclude_spans: MapSet.new() + exclude_spans: MapSet.new(), + live_dashboard_port: nil ] @installation_id_key "electric_installation_id"