diff --git a/.build_number b/.build_number index 324abdd..29a5d8a 100644 --- a/.build_number +++ b/.build_number @@ -1 +1 @@ -0303261323 +0303261413 diff --git a/backend/app/build_info.py b/backend/app/build_info.py index 4b893cd..fe6d761 100644 --- a/backend/app/build_info.py +++ b/backend/app/build_info.py @@ -1,2 +1,2 @@ -BUILD_NUMBER = "0303261323" -CHANGELOG = '2026-03-02|Hotfix: add logged-out password reset flow\n2026-03-02|Process 1 build 0203261953\n2026-03-02|Process 1 build 0203261610\n2026-03-02|Process 1 build 0203261608\n2026-03-02|Add dedicated profile invites page and fix mobile admin layout\n2026-03-01|Persist Seerr media failure suppression and reduce sync error noise\n2026-03-01|Add repository line ending policy\n2026-03-01|Finalize diagnostics, logging controls, and email test support\n2026-03-01|Add invite email templates and delivery workflow\n2026-02-28|Finalize dev-1.3 upgrades and Seerr updates\n2026-02-27|admin docs and layout refresh, build 2702261314\n2026-02-27|Build 2702261153: fix jellyfin sync user visibility\n2026-02-26|Build 2602262241: live request page updates\n2026-02-26|Build 2602262204\n2026-02-26|Build 2602262159: restore jellyfin-first user source\n2026-02-26|Build 2602262049: split magent settings and harden local login\n2026-02-26|Build 2602262030: add magent settings and hardening\n2026-02-26|Build 2602261731: fix user resync after nuclear wipe\n2026-02-26|Build 2602261717: master invite policy and self-service invite controls\n2026-02-26|Build 2602261636: self-service invites and count fixes\n2026-02-26|Build 2602261605: invite trace and cross-system user lifecycle\n2026-02-26|Build 2602261536: refine invite layouts and tighten UI\n2026-02-26|Build 2602261523: live updates, invite cleanup and nuclear resync\n2026-02-26|Build 2602261442: tidy users and invite layouts\n2026-02-26|Build 2602261409: unify invite management controls\n2026-02-26|Build 2602260214: invites profiles and expiry admin controls\n2026-02-26|Build 2602260022: enterprise UI refresh and users bulk auto-search\n2026-02-25|Build 2502262321: fix auto-search quality and per-user toggle\n2026-02-02|Build 0202261541: allow FQDN service URLs\n2026-01-30|Build 3001262148: single container\n2026-01-29|Build 2901262244: format changelog\n2026-01-29|Build 2901262240: cache users\n2026-01-29|Tidy full changelog\n2026-01-29|Update full changelog\n2026-01-29|Bake build number and changelog\n2026-01-29|Hardcode build number in backend\n2026-01-29|release: 2901262102\n2026-01-29|release: 2901262044\n2026-01-29|release: 2901262036\n2026-01-27|Hydrate missing artwork from Jellyseerr (build 271261539)\n2026-01-27|Fallback to TMDB when artwork cache fails (build 271261524)\n2026-01-27|Add service test buttons (build 271261335)\n2026-01-27|Bump build number (process 2) 271261322\n2026-01-27|Add cache load spinner (build 271261238)\n2026-01-27|Fix snapshot title fallback (build 271261228)\n2026-01-27|Fix request titles in snapshots (build 271261219)\n2026-01-27|Bump build number to 271261202\n2026-01-27|Clarify request sync settings (build 271261159)\n2026-01-27|Fix backend cache stats import (build 271261149)\n2026-01-27|Improve cache stats performance (build 271261145)\n2026-01-27|Add cache control artwork stats\n2026-01-26|Fix sync progress bar animation\n2026-01-26|Fix cache title hydration\n2026-01-25|Build 2501262041\n2026-01-25|Harden request cache titles and cache-only reads\n2026-01-25|Serve bundled branding assets by default\n2026-01-25|Seed branding logo from bundled assets\n2026-01-25|Tidy request sync controls\n2026-01-25|Add Jellyfin login cache and admin-only stats\n2026-01-25|Add user stats and activity tracking\n2026-01-25|Move account actions into avatar menu\n2026-01-25|Improve mobile header layout\n2026-01-25|Automate build number tagging and sync\n2026-01-25|Add site banner, build number, and changelog\n2026-01-24|Improve request handling and qBittorrent categories\n2026-01-24|Map Prowlarr releases to Arr indexers for manual grab\n2026-01-24|Clarify how-it-works steps and fixes\n2026-01-24|Document fix buttons in how-it-works\n2026-01-24|Route grabs through Sonarr/Radarr only\n2026-01-23|Use backend branding assets for logo and favicon\n2026-01-23|Copy public assets into frontend image\n2026-01-23|Fix backend Dockerfile paths for root context\n2026-01-23|Add Docker Hub compose override\n2026-01-23|Remove password fields from users page\n2026-01-23|Use bundled branding assets\n2026-01-23|Add default branding assets when missing\n2026-01-23|Show available status on landing when in Jellyfin\n2026-01-23|Fix cache titles and move feedback link\n2026-01-23|Add feedback form and webhook\n2026-01-23|Hide header actions when signed out\n2026-01-23|Fallback manual grab to qBittorrent\n2026-01-23|Split search actions and improve download options\n2026-01-23|Fix cache titles via Jellyseerr media lookup\n2026-01-22|Update README with Docker-first guide\n2026-01-22|Update README\n2026-01-22|Ignore build artifacts\n2026-01-22|Initial commit' +BUILD_NUMBER = "0303261413" +CHANGELOG = '2026-03-03|Hotfix: expand landing-page search to all requests\n2026-03-02|Hotfix: add logged-out password reset flow\n2026-03-02|Process 1 build 0203261953\n2026-03-02|Process 1 build 0203261610\n2026-03-02|Process 1 build 0203261608\n2026-03-02|Add dedicated profile invites page and fix mobile admin layout\n2026-03-01|Persist Seerr media failure suppression and reduce sync error noise\n2026-03-01|Add repository line ending policy\n2026-03-01|Finalize diagnostics, logging controls, and email test support\n2026-03-01|Add invite email templates and delivery workflow\n2026-02-28|Finalize dev-1.3 upgrades and Seerr updates\n2026-02-27|admin docs and layout refresh, build 2702261314\n2026-02-27|Build 2702261153: fix jellyfin sync user visibility\n2026-02-26|Build 2602262241: live request page updates\n2026-02-26|Build 2602262204\n2026-02-26|Build 2602262159: restore jellyfin-first user source\n2026-02-26|Build 2602262049: split magent settings and harden local login\n2026-02-26|Build 2602262030: add magent settings and hardening\n2026-02-26|Build 2602261731: fix user resync after nuclear wipe\n2026-02-26|Build 2602261717: master invite policy and self-service invite controls\n2026-02-26|Build 2602261636: self-service invites and count fixes\n2026-02-26|Build 2602261605: invite trace and cross-system user lifecycle\n2026-02-26|Build 2602261536: refine invite layouts and tighten UI\n2026-02-26|Build 2602261523: live updates, invite cleanup and nuclear resync\n2026-02-26|Build 2602261442: tidy users and invite layouts\n2026-02-26|Build 2602261409: unify invite management controls\n2026-02-26|Build 2602260214: invites profiles and expiry admin controls\n2026-02-26|Build 2602260022: enterprise UI refresh and users bulk auto-search\n2026-02-25|Build 2502262321: fix auto-search quality and per-user toggle\n2026-02-02|Build 0202261541: allow FQDN service URLs\n2026-01-30|Build 3001262148: single container\n2026-01-29|Build 2901262244: format changelog\n2026-01-29|Build 2901262240: cache users\n2026-01-29|Tidy full changelog\n2026-01-29|Update full changelog\n2026-01-29|Bake build number and changelog\n2026-01-29|Hardcode build number in backend\n2026-01-29|release: 2901262102\n2026-01-29|release: 2901262044\n2026-01-29|release: 2901262036\n2026-01-27|Hydrate missing artwork from Jellyseerr (build 271261539)\n2026-01-27|Fallback to TMDB when artwork cache fails (build 271261524)\n2026-01-27|Add service test buttons (build 271261335)\n2026-01-27|Bump build number (process 2) 271261322\n2026-01-27|Add cache load spinner (build 271261238)\n2026-01-27|Fix snapshot title fallback (build 271261228)\n2026-01-27|Fix request titles in snapshots (build 271261219)\n2026-01-27|Bump build number to 271261202\n2026-01-27|Clarify request sync settings (build 271261159)\n2026-01-27|Fix backend cache stats import (build 271261149)\n2026-01-27|Improve cache stats performance (build 271261145)\n2026-01-27|Add cache control artwork stats\n2026-01-26|Fix sync progress bar animation\n2026-01-26|Fix cache title hydration\n2026-01-25|Build 2501262041\n2026-01-25|Harden request cache titles and cache-only reads\n2026-01-25|Serve bundled branding assets by default\n2026-01-25|Seed branding logo from bundled assets\n2026-01-25|Tidy request sync controls\n2026-01-25|Add Jellyfin login cache and admin-only stats\n2026-01-25|Add user stats and activity tracking\n2026-01-25|Move account actions into avatar menu\n2026-01-25|Improve mobile header layout\n2026-01-25|Automate build number tagging and sync\n2026-01-25|Add site banner, build number, and changelog\n2026-01-24|Improve request handling and qBittorrent categories\n2026-01-24|Map Prowlarr releases to Arr indexers for manual grab\n2026-01-24|Clarify how-it-works steps and fixes\n2026-01-24|Document fix buttons in how-it-works\n2026-01-24|Route grabs through Sonarr/Radarr only\n2026-01-23|Use backend branding assets for logo and favicon\n2026-01-23|Copy public assets into frontend image\n2026-01-23|Fix backend Dockerfile paths for root context\n2026-01-23|Add Docker Hub compose override\n2026-01-23|Remove password fields from users page\n2026-01-23|Use bundled branding assets\n2026-01-23|Add default branding assets when missing\n2026-01-23|Show available status on landing when in Jellyfin\n2026-01-23|Fix cache titles and move feedback link\n2026-01-23|Add feedback form and webhook\n2026-01-23|Hide header actions when signed out\n2026-01-23|Fallback manual grab to qBittorrent\n2026-01-23|Split search actions and improve download options\n2026-01-23|Fix cache titles via Jellyseerr media lookup\n2026-01-22|Update README with Docker-first guide\n2026-01-22|Update README\n2026-01-22|Ignore build artifacts\n2026-01-22|Initial commit' diff --git a/backend/app/config.py b/backend/app/config.py index 9dfd546..0d623ab 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -71,6 +71,18 @@ class Settings(BaseSettings): site_banner_tone: str = Field( default="info", validation_alias=AliasChoices("SITE_BANNER_TONE") ) + site_login_show_jellyfin_login: bool = Field( + default=True, validation_alias=AliasChoices("SITE_LOGIN_SHOW_JELLYFIN_LOGIN") + ) + site_login_show_local_login: bool = Field( + default=True, validation_alias=AliasChoices("SITE_LOGIN_SHOW_LOCAL_LOGIN") + ) + site_login_show_forgot_password: bool = Field( + default=True, validation_alias=AliasChoices("SITE_LOGIN_SHOW_FORGOT_PASSWORD") + ) + site_login_show_signup_link: bool = Field( + default=True, validation_alias=AliasChoices("SITE_LOGIN_SHOW_SIGNUP_LINK") + ) site_changelog: Optional[str] = Field(default=CHANGELOG) magent_application_url: Optional[str] = Field( diff --git a/backend/app/routers/admin.py b/backend/app/routers/admin.py index 6bab8a7..83e4d36 100644 --- a/backend/app/routers/admin.py +++ b/backend/app/routers/admin.py @@ -215,6 +215,10 @@ SETTING_KEYS: List[str] = [ "site_banner_enabled", "site_banner_message", "site_banner_tone", + "site_login_show_jellyfin_login", + "site_login_show_local_login", + "site_login_show_forgot_password", + "site_login_show_signup_link", ] diff --git a/backend/app/routers/site.py b/backend/app/routers/site.py index 7d0cca8..72985ee 100644 --- a/backend/app/routers/site.py +++ b/backend/app/routers/site.py @@ -24,6 +24,12 @@ def _build_site_info(include_changelog: bool) -> Dict[str, Any]: "message": banner_message, "tone": tone, }, + "login": { + "showJellyfinLogin": bool(runtime.site_login_show_jellyfin_login), + "showLocalLogin": bool(runtime.site_login_show_local_login), + "showForgotPassword": bool(runtime.site_login_show_forgot_password), + "showSignupLink": bool(runtime.site_login_show_signup_link), + }, } if include_changelog: info["changelog"] = (CHANGELOG or "").strip() diff --git a/backend/app/runtime.py b/backend/app/runtime.py index 0353ed9..5f6be4d 100644 --- a/backend/app/runtime.py +++ b/backend/app/runtime.py @@ -29,6 +29,10 @@ _BOOL_FIELDS = { "magent_notify_webhook_enabled", "jellyfin_sync_to_arr", "site_banner_enabled", + "site_login_show_jellyfin_login", + "site_login_show_local_login", + "site_login_show_forgot_password", + "site_login_show_signup_link", } _SKIP_OVERRIDE_FIELDS = {"site_build_number", "site_changelog"} diff --git a/frontend/app/admin/SettingsPage.tsx b/frontend/app/admin/SettingsPage.tsx index 5e5e10a..27cb922 100644 --- a/frontend/app/admin/SettingsPage.tsx +++ b/frontend/app/admin/SettingsPage.tsx @@ -40,6 +40,10 @@ const SECTION_LABELS: Record = { const BOOL_SETTINGS = new Set([ 'jellyfin_sync_to_arr', 'site_banner_enabled', + 'site_login_show_jellyfin_login', + 'site_login_show_local_login', + 'site_login_show_forgot_password', + 'site_login_show_signup_link', 'magent_proxy_enabled', 'magent_proxy_trust_forwarded_headers', 'magent_ssl_bind_enabled', @@ -104,7 +108,7 @@ const SECTION_DESCRIPTIONS: Record = { qbittorrent: 'Downloader connection settings.', requests: 'Control how often requests are refreshed and cleaned up.', log: 'Activity log for troubleshooting.', - site: 'Sitewide banner and version details. The changelog is generated from git history during release builds.', + site: 'Sitewide banner, login page visibility, and version details. The changelog is generated from git history during release builds.', } const SETTINGS_SECTION_MAP: Record = { @@ -280,6 +284,10 @@ const SETTING_LABEL_OVERRIDES: Record = { magent_notify_push_device: 'Device / target', magent_notify_webhook_enabled: 'Generic webhook notifications enabled', magent_notify_webhook_url: 'Generic webhook URL', + site_login_show_jellyfin_login: 'Login page: Jellyfin sign-in', + site_login_show_local_login: 'Login page: local Magent sign-in', + site_login_show_forgot_password: 'Login page: forgot password', + site_login_show_signup_link: 'Login page: invite signup link', log_file_max_bytes: 'Log file max size (bytes)', log_file_backup_count: 'Rotated log files to keep', log_http_client_level: 'Service HTTP log level', @@ -564,6 +572,15 @@ export default function SettingsPage({ section }: SettingsPageProps) { 'requests_cleanup_time', 'requests_cleanup_days', ] + const siteSettingOrder = [ + 'site_banner_enabled', + 'site_banner_message', + 'site_banner_tone', + 'site_login_show_jellyfin_login', + 'site_login_show_local_login', + 'site_login_show_forgot_password', + 'site_login_show_signup_link', + ] const sortByOrder = (items: AdminSetting[], order: string[]) => { const position = new Map(order.map((key, index) => [key, index])) return [...items].sort((a, b) => { @@ -615,6 +632,9 @@ export default function SettingsPage({ section }: SettingsPageProps) { if (sectionKey === 'requests') { return sortByOrder(filtered, requestSettingOrder) } + if (sectionKey === 'site') { + return sortByOrder(filtered, siteSettingOrder) + } return filtered })(), })) @@ -748,6 +768,10 @@ export default function SettingsPage({ section }: SettingsPageProps) { site_banner_enabled: 'Enable a sitewide banner for announcements.', site_banner_message: 'Short banner message for maintenance or updates.', site_banner_tone: 'Visual tone for the banner.', + site_login_show_jellyfin_login: 'Show the Jellyfin login button on the login page.', + site_login_show_local_login: 'Show the local Magent login button on the login page.', + site_login_show_forgot_password: 'Show the forgot-password link on the login page.', + site_login_show_signup_link: 'Show the invite signup link on the login page.', site_changelog: 'One update per line for the public changelog.', } diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index 638f0aa..95acf27 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -1,19 +1,36 @@ 'use client' import { useRouter } from 'next/navigation' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { getApiBase, setToken, clearToken } from '../lib/auth' import BrandingLogo from '../ui/BrandingLogo' +const DEFAULT_LOGIN_OPTIONS = { + showJellyfinLogin: true, + showLocalLogin: true, + showForgotPassword: true, + showSignupLink: true, +} + export default function LoginPage() { const router = useRouter() const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const [loading, setLoading] = useState(false) + const [loginOptions, setLoginOptions] = useState(DEFAULT_LOGIN_OPTIONS) + const primaryMode: 'jellyfin' | 'local' | null = loginOptions.showJellyfinLogin + ? 'jellyfin' + : loginOptions.showLocalLogin + ? 'local' + : null const submit = async (event: React.FormEvent, mode: 'local' | 'jellyfin') => { event.preventDefault() + if (!primaryMode) { + setError('Login is currently disabled. Contact an administrator.') + return + } setError(null) setLoading(true) try { @@ -48,12 +65,63 @@ export default function LoginPage() { } } + useEffect(() => { + let active = true + const loadLoginOptions = async () => { + try { + const baseUrl = getApiBase() + const response = await fetch(`${baseUrl}/site/public`) + if (!response.ok) { + return + } + const data = await response.json() + const login = data?.login ?? {} + if (!active) return + setLoginOptions({ + showJellyfinLogin: login.showJellyfinLogin !== false, + showLocalLogin: login.showLocalLogin !== false, + showForgotPassword: login.showForgotPassword !== false, + showSignupLink: login.showSignupLink !== false, + }) + } catch (err) { + console.error(err) + } + } + void loadLoginOptions() + return () => { + active = false + } + }, []) + + const loginHelpText = (() => { + if (loginOptions.showJellyfinLogin && loginOptions.showLocalLogin) { + return 'Use your Jellyfin account, or sign in with a local Magent admin account.' + } + if (loginOptions.showJellyfinLogin) { + return 'Use your Jellyfin account to sign in.' + } + if (loginOptions.showLocalLogin) { + return 'Use your local Magent admin account to sign in.' + } + return 'No sign-in methods are currently available. Contact an administrator.' + })() + return (

Sign in

-

Use your Jellyfin account, or sign in with a local Magent admin account.

-
submit(event, 'jellyfin')} className="auth-form"> +

{loginHelpText}

+ { + if (!primaryMode) { + event.preventDefault() + setError('Login is currently disabled. Contact an administrator.') + return + } + void submit(event, primaryMode) + }} + className="auth-form" + >
) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 17465d0..8c2b519 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "magent-frontend", - "version": "0303261323", + "version": "0303261413", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "magent-frontend", - "version": "0303261323", + "version": "0303261413", "dependencies": { "next": "16.1.6", "react": "19.2.4", diff --git a/frontend/package.json b/frontend/package.json index 86f5337..439cd52 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "magent-frontend", "private": true, - "version": "0303261323", + "version": "0303261413", "scripts": { "dev": "next dev", "build": "next build",