diff --git a/.sobelow-conf b/.sobelow-conf
index 0c4088d..bf1db34 100644
--- a/.sobelow-conf
+++ b/.sobelow-conf
@@ -16,7 +16,8 @@
"Config.HTTPS",
"Config.CSP",
"XSS.ContentType",
- "Traversal.SendFile"
+ "Traversal.SendFile",
+ "Traversal.SendDownload"
],
ignore_files: [],
version: false
diff --git a/config/runtime.exs b/config/runtime.exs
index 6cb062f..8e47790 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -53,7 +53,7 @@ if config_env() == :prod do
# For testing alternate journal modes (see issue #137)
journal_mode = String.to_existing_atom(System.get_env("JOURNAL_MODE", "wal"))
- config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL", "info"))
+ config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL", "debug"))
config :pinchflat,
yt_dlp_executable: System.find_executable("yt-dlp"),
@@ -64,7 +64,8 @@ if config_env() == :prod do
tmpfile_directory: Path.join([System.tmp_dir!(), "pinchflat", "data"]),
dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY"),
expose_feed_endpoints: expose_feed_endpoints,
- timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC"
+ timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC",
+ log_path: log_path
config :tzdata, :data_dir, System.get_env("TZ_DATA_DIR", "/etc/elixir_tzdata_data")
diff --git a/lib/pinchflat_web/components/layouts.ex b/lib/pinchflat_web/components/layouts.ex
index 0efadc1..9d85898 100644
--- a/lib/pinchflat_web/components/layouts.ex
+++ b/lib/pinchflat_web/components/layouts.ex
@@ -4,28 +4,107 @@ defmodule PinchflatWeb.Layouts do
embed_templates "layouts/*"
embed_templates "layouts/partials/*"
+ @doc """
+ Renders a sidebar menu item link
+
+ ## Examples
+
+ <.sidebar_link icon="hero-home" text="Home" href="/" />
+ """
attr :icon, :string, required: true
attr :text, :string, required: true
attr :href, :any, required: true
attr :target, :any, default: "_self"
def sidebar_item(assigns) do
- # I'm testing out grouping classes here. Tentative order: font, layout, color, animation, state-modifiers
~H"""
-
- <.link
- href={@href}
- target={@target}
- class={[
- "font-medium text-bodydark1",
- "group relative flex items-center gap-2.5 rounded-sm px-4 py-2 duration-300 ease-in-out",
- "duration-300 ease-in-out",
- "hover:bg-graydark dark:hover:bg-meta-4"
- ]}
- >
- <.icon name={@icon} /> <%= @text %>
-
+
+ <.sidebar_link icon={@icon} text={@text} href={@href} target={@target} />
"""
end
+
+ @doc """
+ Renders a sidebar menu item with a submenu
+
+ ## Examples
+
+ <.sidebar_submenu icon="hero-home" text="Home" current_path="/">
+ <:submenu icon="hero-home" text="Home" href="/" />
+
+ """
+
+ attr :icon, :string, required: true
+ attr :text, :string, required: true
+ attr :current_path, :string, required: true
+
+ slot :submenu do
+ attr :icon, :string
+ attr :text, :string
+ attr :href, :any
+ attr :target, :any
+ end
+
+ def sidebar_submenu(assigns) do
+ initially_selected = Enum.any?(assigns[:submenu], &(&1[:href] == assigns[:current_path]))
+ assigns = Map.put(assigns, :initially_selected, initially_selected)
+
+ ~H"""
+
+
+
+ <.icon name={@icon} /> <%= @text %>
+
+
+ <.icon name="hero-chevron-up" x-bind:class="{ 'rotate-180': selected }" />
+
+
+
+
+ -
+ <.sidebar_link icon={menu[:icon]} text={menu[:text]} href={menu[:href]} target={menu[:target]} class="pl-10" />
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a sidebar menu item link
+
+ ## Examples
+
+ <.sidebar_link icon="hero-home" text="Home" href="/" />
+ """
+ attr :icon, :string
+ attr :text, :string, required: true
+ attr :href, :any, required: true
+ attr :target, :any, default: "_self"
+ attr :class, :string, default: ""
+
+ def sidebar_link(assigns) do
+ ~H"""
+ <.link
+ href={@href}
+ target={@target}
+ class={[
+ "font-medium",
+ "group relative flex items-center gap-2.5 rounded-sm px-4 py-2 duration-300 ease-in-out",
+ "duration-300 ease-in-out",
+ "hover:bg-meta-4",
+ @class
+ ]}
+ >
+ <.icon :if={@icon} name={@icon} /> <%= @text %>
+
+ """
+ end
end
diff --git a/lib/pinchflat_web/components/layouts/app.html.heex b/lib/pinchflat_web/components/layouts/app.html.heex
index ffb79a6..63762ff 100644
--- a/lib/pinchflat_web/components/layouts/app.html.heex
+++ b/lib/pinchflat_web/components/layouts/app.html.heex
@@ -1,5 +1,5 @@
- <.sidebar />
+ <.sidebar conn={@conn} />
<.header params={@conn.params} />
diff --git a/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex b/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex
index d8b8de9..2d7ae0d 100644
--- a/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex
+++ b/lib/pinchflat_web/components/layouts/partials/sidebar.html.heex
@@ -26,7 +26,14 @@
<.sidebar_item icon="hero-home" text="Home" href={~p"/"} />
<.sidebar_item icon="hero-tv" text="Sources" href={~p"/sources"} />
<.sidebar_item icon="hero-adjustments-vertical" text="Media Profiles" href={~p"/media_profiles"} />
- <.sidebar_item icon="hero-cog-6-tooth" text="Settings" href={~p"/settings"} />
+ <.sidebar_submenu
+ icon="hero-cog-6-tooth"
+ text="Config"
+ current_path={Phoenix.Controller.current_path(@conn)}
+ >
+ <:submenu text="Settings" href={~p"/settings"} />
+ <:submenu text="App Info" href={~p"/app_info"} />
+
diff --git a/lib/pinchflat_web/controllers/settings/setting_controller.ex b/lib/pinchflat_web/controllers/settings/setting_controller.ex
index c87d51f..5e009c1 100644
--- a/lib/pinchflat_web/controllers/settings/setting_controller.ex
+++ b/lib/pinchflat_web/controllers/settings/setting_controller.ex
@@ -23,4 +23,20 @@ defmodule PinchflatWeb.Settings.SettingController do
render(conn, "show.html", changeset: changeset)
end
end
+
+ def app_info(conn, _params) do
+ render(conn, "app_info.html")
+ end
+
+ def download_logs(conn, _params) do
+ log_path = Application.get_env(:pinchflat, :log_path)
+
+ if log_path && File.exists?(log_path) do
+ send_download(conn, {:file, log_path}, filename: "pinchflat-logs-#{Date.utc_today()}.txt")
+ else
+ conn
+ |> put_flash(:error, "Log file couldn't be found")
+ |> redirect(to: ~p"/app_info")
+ end
+ end
end
diff --git a/lib/pinchflat_web/controllers/settings/setting_html.ex b/lib/pinchflat_web/controllers/settings/setting_html.ex
index 392975d..bb5e255 100644
--- a/lib/pinchflat_web/controllers/settings/setting_html.ex
+++ b/lib/pinchflat_web/controllers/settings/setting_html.ex
@@ -18,4 +18,14 @@ defmodule PinchflatWeb.Settings.SettingHTML do
~s(Server endpoint for Apprise notifications when new media is found. See
Apprise docs for more information)
end
+
+ def diagnostic_info_string do
+ """
+ App Version: #{Application.spec(:pinchflat)[:vsn]}
+ yt-dlp Version: #{Settings.get!(:yt_dlp_version)}
+ Apprise Version: #{Settings.get!(:apprise_version)}
+ System Architecture: #{to_string(:erlang.system_info(:system_architecture))}
+ Timezone: #{Application.get_env(:pinchflat, :timezone)}
+ """
+ end
end
diff --git a/lib/pinchflat_web/controllers/settings/setting_html/app_info.html.heex b/lib/pinchflat_web/controllers/settings/setting_html/app_info.html.heex
new file mode 100644
index 0000000..d1628c3
--- /dev/null
+++ b/lib/pinchflat_web/controllers/settings/setting_html/app_info.html.heex
@@ -0,0 +1,26 @@
+
+
+
+ <.button color="bg-primary" rounding="rounded-lg" x-data="{ copied: false }" x-on:click={~s"
+ copyWithCallbacks(
+ `#{diagnostic_info_string()}`,
+ () => copied = true,
+ () => copied = false
+ )
+ "}>
+ Copy Diagnotic Info
+ <.icon name="hero-check" class="ml-2 h-4 w-4" />
+
+ <.link href={~p"/download_logs"}>
+ <.button color="bg-primary" rounding="rounded-lg" class="ml-4">
+ Download Logs
+
+
+
+
diff --git a/lib/pinchflat_web/router.ex b/lib/pinchflat_web/router.ex
index e3dd14e..1e1ee7d 100644
--- a/lib/pinchflat_web/router.ex
+++ b/lib/pinchflat_web/router.ex
@@ -30,7 +30,10 @@ defmodule PinchflatWeb.Router do
resources "/media_profiles", MediaProfiles.MediaProfileController
resources "/search", Searches.SearchController, only: [:show], singleton: true
+
resources "/settings", Settings.SettingController, only: [:show, :update], singleton: true
+ get "/app_info", Settings.SettingController, :app_info
+ get "/download_logs", Settings.SettingController, :download_logs
resources "/sources", Sources.SourceController do
post "/force_download_pending", Sources.SourceController, :force_download_pending
diff --git a/test/pinchflat_web/controllers/setting_controller_test.exs b/test/pinchflat_web/controllers/setting_controller_test.exs
index 0063ebc..24dea44 100644
--- a/test/pinchflat_web/controllers/setting_controller_test.exs
+++ b/test/pinchflat_web/controllers/setting_controller_test.exs
@@ -1,6 +1,8 @@
defmodule PinchflatWeb.SettingControllerTest do
use PinchflatWeb.ConnCase
+ alias Pinchflat.Utils.FilesystemUtils
+
describe "show settings" do
test "renders the page", %{conn: conn} do
conn = get(conn, ~p"/settings")
@@ -20,4 +22,33 @@ defmodule PinchflatWeb.SettingControllerTest do
assert html_response(conn, 200) =~ update_attrs[:apprise_server]
end
end
+
+ describe "app_info" do
+ test "renders the page", %{conn: conn} do
+ conn = get(conn, ~p"/app_info")
+
+ assert html_response(conn, 200) =~ "App Info"
+ end
+ end
+
+ describe "download_logs" do
+ test "downloads logs", %{conn: conn} do
+ log_path = Path.join([System.tmp_dir!(), "pinchflat", "data", "pinchflat.log"])
+ FilesystemUtils.write_p(log_path, "test log data")
+ Application.put_env(:pinchflat, :log_path, log_path)
+
+ conn = get(conn, ~p"/download_logs")
+
+ assert response(conn, 200) =~ "test log data"
+
+ Application.put_env(:pinchflat, :log_path, nil)
+ end
+
+ test "redirects when log file is not found", %{conn: conn} do
+ conn = get(conn, ~p"/download_logs")
+
+ assert redirected_to(conn) == ~p"/app_info"
+ assert conn.assigns[:flash]["error"] == "Log file couldn't be found"
+ end
+ end
end