Redesign beta Magent UI
Magent CI/CD / verify (push) Successful in 11m8s
Magent CI/CD / deploy-prod (push) Has been skipped
Magent CI/CD / deploy-beta (push) Successful in 18s

This commit is contained in:
2026-06-21 11:41:38 +12:00
parent e36da13264
commit e6b4f99ea7
12 changed files with 1891 additions and 40 deletions
+74 -2
View File
@@ -19,6 +19,12 @@ type ServiceOptions = {
qualityProfiles: { id: number; name: string; label: string }[]
}
type ServiceStatus = {
name: string
status: string
message?: string
}
const SECTION_LABELS: Record<string, string> = {
magent: 'Magent',
general: 'General',
@@ -414,6 +420,8 @@ export default function SettingsPage({ section }: SettingsPageProps) {
const [maintenanceStatus, setMaintenanceStatus] = useState<string | null>(null)
const [maintenanceBusy, setMaintenanceBusy] = useState(false)
const [liveStreamConnected, setLiveStreamConnected] = useState(false)
const [serviceStatuses, setServiceStatuses] = useState<ServiceStatus[]>([])
const [serviceStatusCheckedAt, setServiceStatusCheckedAt] = useState<string | null>(null)
const requestsSyncRef = useRef<any | null>(null)
const artworkPrefetchRef = useRef<any | null>(null)
const computeProgressPercent = (
@@ -543,6 +551,21 @@ export default function SettingsPage({ section }: SettingsPageProps) {
}
}, [])
const loadServiceStatuses = useCallback(async () => {
try {
const baseUrl = getApiBase()
const response = await authFetch(`${baseUrl}/status/services`)
if (!response.ok) {
return
}
const data = await response.json()
setServiceStatuses(Array.isArray(data?.services) ? data.services : [])
setServiceStatusCheckedAt(new Date().toISOString())
} catch (err) {
console.error(err)
}
}, [])
useEffect(() => {
const load = async () => {
if (!getToken()) {
@@ -550,7 +573,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
return
}
try {
await loadSettings()
await Promise.all([loadSettings(), loadServiceStatuses()])
if (section === 'cache' || section === 'artwork') {
await loadArtworkPrefetchStatus()
await loadArtworkSummary()
@@ -570,7 +593,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
if (section === 'radarr') {
void loadOptions('radarr')
}
}, [loadArtworkPrefetchStatus, loadArtworkSummary, loadOptions, loadSettings, router, section])
}, [loadArtworkPrefetchStatus, loadArtworkSummary, loadOptions, loadServiceStatuses, loadSettings, router, section])
const groupedSettings = useMemo(() => {
const groups: Record<string, AdminSetting[]> = {}
@@ -583,6 +606,22 @@ export default function SettingsPage({ section }: SettingsPageProps) {
}, [settings])
const settingsSection = SETTINGS_SECTION_MAP[section] ?? null
const statusNamesBySection: Record<string, string[]> = {
seerr: ['Seerr', 'Jellyseerr', 'Jellyseer'],
jellyseerr: ['Seerr', 'Jellyseerr', 'Jellyseer'],
jellyfin: ['Jellyfin'],
sonarr: ['Sonarr'],
radarr: ['Radarr'],
prowlarr: ['Prowlarr'],
qbittorrent: ['qBittorrent', 'Qbittorrent'],
}
const statusNames = statusNamesBySection[section] ?? statusNamesBySection[settingsSection ?? ''] ?? []
const currentServiceStatus = serviceStatuses.find((service) =>
statusNames.some((name) => name.toLowerCase() === service.name.toLowerCase())
)
const currentServiceConfigured = currentServiceStatus
? currentServiceStatus.status !== 'not_configured'
: null
const isMagentGroupedSection = section === 'magent' || section === 'general' || section === 'notifications'
const isSiteGroupedSection = section === 'site'
const visibleSections = settingsSection ? [settingsSection] : []
@@ -1681,6 +1720,39 @@ export default function SettingsPage({ section }: SettingsPageProps) {
}
>
{status && <div className="error-banner">{status}</div>}
{currentServiceStatus ? (
<section className="admin-section admin-zone service-status-panel">
<div className="service-status-summary">
<span className={`system-dot system-dot-${currentServiceStatus.status}`} aria-hidden="true" />
<div>
<span className="section-kicker">Connection status</span>
<h2>{currentServiceStatus.name}</h2>
<p className="section-subtitle">
{currentServiceStatus.message ?? 'No service message was returned.'}
</p>
</div>
</div>
<div className="service-status-grid">
<div>
<span>Status</span>
<strong>{currentServiceStatus.status.replaceAll('_', ' ')}</strong>
</div>
<div>
<span>Configuration</span>
<strong>{currentServiceConfigured ? 'Configured' : 'Not configured'}</strong>
</div>
<div>
<span>Last checked</span>
<strong>
{serviceStatusCheckedAt ? new Date(serviceStatusCheckedAt).toLocaleString() : 'Not checked yet'}
</strong>
</div>
<button type="button" className="ghost-button" onClick={() => void loadServiceStatuses()}>
Refresh status
</button>
</div>
</section>
) : null}
{settingsSections.length > 0 ? (
<div className="admin-form admin-zone-stack">
{settingsSections