Packages

phoenix_kit

1.7.102
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules sitemap web settings.ex
Raw

lib/modules/sitemap/web/settings.ex

defmodule PhoenixKit.Modules.Sitemap.Web.Settings do
@moduledoc """
LiveView for sitemap configuration and management.
Provides admin interface for:
- Enabling/disabling sitemap module
- Per-module sitemap cards with stats and regeneration
- Auth pages configuration (login excluded, registration toggle)
- Publishing split by group toggle
- XSL styling configuration
- Scheduled generation
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Sitemap
alias PhoenixKit.Modules.Sitemap.FileStorage
alias PhoenixKit.Modules.Sitemap.Generator
alias PhoenixKit.Modules.Sitemap.SchedulerWorker
alias PhoenixKit.PubSub.Manager, as: PubSubManager
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
@impl true
def mount(params, _session, socket) do
if connected?(socket) do
PubSubManager.subscribe("sitemap:updates")
PubSubManager.subscribe("sitemap:settings")
end
locale = params["locale"] || "en"
project_title = Settings.get_project_title()
site_url = Settings.get_setting("site_url", "")
config = Sitemap.get_config()
sitemap_version = get_sitemap_version(config)
module_stats = Sitemap.get_module_stats()
module_files = FileStorage.list_module_files()
socket =
socket
|> assign(:page_title, "Sitemap Settings")
|> assign(:project_title, project_title)
|> assign(:current_locale, locale)
|> assign(:current_path, Routes.path("/admin/settings/sitemap", locale: locale))
|> assign(:config, config)
|> assign(:site_url, site_url)
|> assign(:generating, false)
|> assign(:preview_mode, nil)
|> assign(:preview_content, nil)
|> assign(:show_preview, false)
|> assign(:sitemap_version, sitemap_version)
|> assign(:module_stats, module_stats)
|> assign(:module_files, module_files)
|> assign(:include_registration, Sitemap.include_registration?())
|> assign(:publishing_split_by_group, Sitemap.publishing_split_by_group?())
|> assign(:module_enabled, get_module_enabled_status())
{:ok, socket}
end
@impl true
def handle_params(_params, _url, socket) do
{:noreply, socket}
end
@impl true
def handle_event("toggle_sitemap", _params, socket) do
new_enabled = !socket.assigns.config.enabled
result =
if new_enabled do
Sitemap.enable_system()
else
Sitemap.disable_system()
end
case result do
{:ok, _} ->
config = Sitemap.get_config()
message = if new_enabled, do: "Sitemap enabled", else: "Sitemap disabled"
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:sitemap_toggled, %{enabled: new_enabled, version: new_version})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)
|> put_flash(:info, message)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update sitemap status")}
end
end
# Router Discovery toggle = mode switch (flat vs index)
@impl true
def handle_event("toggle_source", %{"source" => "router_discovery"}, socket) do
key = "sitemap_router_discovery_enabled"
current = Settings.get_boolean_setting(key, false)
new_value = !current
case Settings.update_boolean_setting(key, new_value) do
{:ok, _} ->
Generator.invalidate_cache()
# Transitioning to flat mode: clean up per-module files
if new_value, do: FileStorage.delete_all_modules()
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:source_changed, %{
source: "router_discovery",
version: new_version
})
module_stats = if new_value, do: [], else: Sitemap.get_module_stats()
module_files = if new_value, do: [], else: FileStorage.list_module_files()
message =
if new_value,
do: "Router Discovery enabled — flat sitemap mode active",
else: "Router Discovery disabled — per-module sitemap mode"
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)
|> assign(:module_stats, module_stats)
|> assign(:module_files, module_files)
|> put_flash(:info, message)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update source setting")}
end
end
@impl true
def handle_event("toggle_source", %{"source" => source}, socket) do
key =
if source == "router_discovery" do
"sitemap_router_discovery_enabled"
else
"sitemap_include_#{source}"
end
current = Settings.get_boolean_setting(key, true)
case Settings.update_boolean_setting(key, !current) do
{:ok, _} ->
Generator.invalidate_cache()
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:source_changed, %{source: source, version: new_version})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update source setting")}
end
end
@impl true
def handle_event("toggle_registration", _params, socket) do
new_value = !socket.assigns.include_registration
case Settings.update_boolean_setting("sitemap_include_registration", new_value) do
{:ok, _} ->
Generator.invalidate_cache()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
{:noreply,
socket
|> assign(:include_registration, new_value)
|> assign(:sitemap_version, new_version)
|> put_flash(
:info,
"Registration page #{if new_value, do: "included", else: "excluded"}"
)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update registration setting")}
end
end
@impl true
def handle_event("toggle_publishing_split", _params, socket) do
new_value = !socket.assigns.publishing_split_by_group
case Settings.update_boolean_setting("sitemap_publishing_split_by_group", new_value) do
{:ok, _} ->
Generator.invalidate_cache()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
{:noreply,
socket
|> assign(:publishing_split_by_group, new_value)
|> assign(:sitemap_version, new_version)
|> put_flash(
:info,
"Publishing #{if new_value, do: "split by blog", else: "combined into one file"}"
)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update publishing split setting")}
end
end
@impl true
def handle_event("toggle_html", _params, socket) do
current = socket.assigns.config.html_enabled
case Settings.update_boolean_setting("sitemap_html_enabled", !current) do
{:ok, _} ->
Generator.invalidate_cache()
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:html_changed, %{enabled: !current, version: new_version})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update HTML sitemap setting")}
end
end
@impl true
def handle_event("toggle_schedule", _params, socket) do
current = socket.assigns.config.schedule_enabled
case Settings.update_boolean_setting("sitemap_schedule_enabled", !current) do
{:ok, _} ->
if current do
SchedulerWorker.cancel_scheduled()
else
SchedulerWorker.schedule()
end
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:schedule_changed, %{enabled: !current, version: new_version})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update schedule setting")}
end
end
@impl true
def handle_event("update_style", %{"style" => style}, socket) do
case Settings.update_setting("sitemap_html_style", style) do
{:ok, _} ->
Generator.invalidate_cache()
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:style_changed, %{style: style, version: new_version})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)
|> put_flash(:info, "Style updated. Sitemap will use new style on next load.")}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update style")}
end
end
@impl true
def handle_event("update_interval", %{"interval" => interval_str}, socket) do
case Integer.parse(interval_str) do
{interval, _} when interval > 0 ->
case Settings.update_setting("sitemap_schedule_interval_hours", interval_str) do
{:ok, _} ->
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
broadcast_settings_change(:interval_changed, %{
interval: interval,
version: new_version
})
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update interval")}
end
_ ->
{:noreply, put_flash(socket, :error, "Invalid interval value")}
end
end
@impl true
def handle_event("regenerate", _params, socket) do
base_url = Sitemap.get_base_url()
if base_url != "" do
case SchedulerWorker.regenerate_now() do
{:ok, _job} ->
{:noreply,
socket
|> assign(:generating, true)
|> put_flash(:info, "Sitemap generation queued. Stats will update automatically.")}
{:error, reason} ->
{:noreply,
socket
|> put_flash(:error, "Failed to queue generation: #{inspect(reason)}")}
end
else
{:noreply,
socket
|> put_flash(:error, "Please configure Base URL before generating")}
end
end
@impl true
def handle_event("preview", %{"type" => _type}, socket) do
base_url = Sitemap.get_base_url()
xsl_style = get_xsl_style(socket.assigns.config.html_style)
opts = [
base_url: base_url,
xsl_style: xsl_style,
xsl_enabled: socket.assigns.config.html_enabled
]
content =
case Generator.generate_all(opts) do
{:ok, %{index_xml: xml}} -> xml
_ -> "Error generating XML preview"
end
{:noreply,
socket
|> assign(:preview_mode, "xml")
|> assign(:preview_content, content)
|> assign(:show_preview, true)}
end
@impl true
def handle_event("close_preview", _params, socket) do
{:noreply,
socket
|> assign(:show_preview, false)
|> assign(:preview_mode, nil)
|> assign(:preview_content, nil)}
end
@impl true
def handle_event("invalidate_cache", _params, socket) do
case Generator.invalidate_and_regenerate() do
{:ok, _job} ->
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
{:noreply,
socket
|> assign(:sitemap_version, new_version)
|> assign(:generating, true)
|> assign(:module_stats, [])
|> assign(:module_files, [])
|> put_flash(:info, "All sitemaps cleared. Regeneration started...")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to regenerate: #{inspect(reason)}")}
end
end
# Handle PubSub message when sitemap generation completes
@impl true
def handle_info({:sitemap_generated, %{url_count: count}}, socket) do
config = Sitemap.get_config()
sitemap_version = get_sitemap_version(config)
module_stats = Sitemap.get_module_stats()
module_files = FileStorage.list_module_files()
{:noreply,
socket
|> assign(:generating, false)
|> assign(:config, config)
|> assign(:sitemap_version, sitemap_version)
|> assign(:module_stats, module_stats)
|> assign(:module_files, module_files)
|> put_flash(:info, "Sitemap generated successfully (#{count} URLs)")}
end
@impl true
def handle_info({:sitemap_settings_changed, %{type: :style_changed, version: version}}, socket) do
config = Sitemap.get_config()
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, version)}
end
@impl true
def handle_info({:sitemap_settings_changed, %{type: type}}, socket) do
config = Sitemap.get_config()
new_version = UtilsDate.utc_now() |> DateTime.to_unix()
{:noreply,
socket
|> assign(:config, config)
|> assign(:sitemap_version, new_version)
|> maybe_flash_for_setting(type)}
end
defp maybe_flash_for_setting(socket, :source_changed), do: socket
defp maybe_flash_for_setting(socket, :schedule_changed), do: socket
defp maybe_flash_for_setting(socket, :interval_changed), do: socket
defp maybe_flash_for_setting(socket, :html_changed), do: socket
defp maybe_flash_for_setting(socket, :sitemap_toggled), do: socket
defp maybe_flash_for_setting(socket, _), do: socket
defp broadcast_settings_change(type, data) do
PubSubManager.broadcast(
"sitemap:settings",
{:sitemap_settings_changed, Map.put(data, :type, type)}
)
rescue
_ -> :ok
end
# Maps old HTML style names to new XSL style names
def get_xsl_style(html_style) do
case html_style do
"grouped" -> "table"
"flat" -> "minimal"
style when style in ["table", "minimal"] -> style
_ -> "table"
end
end
# Helper: find stats for a specific module filename
def find_module_stat(module_stats, filename) do
Enum.find(module_stats, fn stat ->
stat["filename"] == filename or
String.starts_with?(stat["filename"] || "", filename)
end)
end
# Helper: count URLs for a source prefix across all module stats
def count_source_urls(module_stats, source_prefix) do
module_stats
|> Enum.filter(fn stat ->
String.starts_with?(stat["filename"] || "", source_prefix)
end)
|> Enum.reduce(0, fn stat, acc -> acc + (stat["url_count"] || 0) end)
end
# Helper: list files for a source prefix
def source_files(module_files, source_prefix) do
Enum.filter(module_files, &String.starts_with?(&1, source_prefix))
end
def format_timestamp(nil), do: "Never"
def format_timestamp(iso_string) when is_binary(iso_string) do
case DateTime.from_iso8601(iso_string) do
{:ok, dt, _} ->
fake_user = %{user_timezone: nil}
date_str = UtilsDate.format_date_with_user_timezone(dt, fake_user)
time_str = UtilsDate.format_time_with_user_timezone(dt, fake_user)
"#{date_str} #{time_str}"
_ ->
iso_string
end
end
def format_timestamp(_), do: "Never"
# Check which parent modules are actually enabled (not just sitemap toggles)
defp get_module_enabled_status do
%{
entities:
Code.ensure_loaded?(PhoenixKitEntities) and safe_module_enabled?(PhoenixKitEntities),
publishing: safe_module_available_and_enabled?(PhoenixKit.Modules.Publishing),
shop: safe_module_enabled?(PhoenixKitEcommerce),
posts: Code.ensure_loaded?(PhoenixKitPosts) and safe_module_enabled?(PhoenixKitPosts)
}
end
defp safe_module_enabled?(module) do
module.enabled?()
rescue
_ -> false
end
defp safe_module_available_and_enabled?(module) do
Code.ensure_loaded?(module) and safe_module_enabled?(module)
end
defp get_sitemap_version(config) do
case config.last_generated do
nil ->
UtilsDate.utc_now() |> DateTime.to_unix()
iso_string when is_binary(iso_string) ->
case DateTime.from_iso8601(iso_string) do
{:ok, dt, _} -> DateTime.to_unix(dt)
_ -> UtilsDate.utc_now() |> DateTime.to_unix()
end
end
end
end