Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of [CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md) for the full list. Highlights: client proofing links (#683), custom content pages (#738), asset picker search (#740), concurrent-edit detection (#742), env-locked settings (#739), an accessibility baseline (#741), three security fixes with advisories (#737, #755, #759), and a pre-release quality pass (#780–#797). --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com> Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, type ReactNode } from 'react';
|
|
import AdminLogin from './components/AdminLogin';
|
|
import AdminDashboard from './components/AdminDashboard';
|
|
import { SESSION_EXPIRED_EVENT } from './components/sessionExpiry';
|
|
import { NotificationProvider } from './components/Notifications';
|
|
import './admin.css';
|
|
import { ConfirmProvider } from './components/ConfirmDialog';
|
|
|
|
/**
|
|
* Auth gate and panel chrome for every /admin route. Lives in the admin layout
|
|
* rather than in each page, so navigating between tabs keeps the session check
|
|
* and the header mounted instead of re-running them per route.
|
|
*/
|
|
export default function AdminShell({ children }: { children: ReactNode }) {
|
|
const [authenticated, setAuthenticated] = useState<boolean | null>(null);
|
|
const [enabled, setEnabled] = useState(true);
|
|
// Sessions last 24 hours (lib/admin/auth.ts), but nothing re-checked
|
|
// authentication after the mount-time check below — leave a tab open
|
|
// overnight and every admin request answered 401 while the UI still looked
|
|
// signed in (#596). SESSION_EXPIRED_EVENT is raised by the fetch call sites
|
|
// that noticed; this is what turns that into dropping back to the login
|
|
// screen with an explanation instead of each of them failing quietly.
|
|
const [sessionExpired, setSessionExpired] = useState(false);
|
|
|
|
useEffect(() => {
|
|
async function checkAuth() {
|
|
try {
|
|
const res = await fetch('/api/admin/auth');
|
|
const data = await res.json();
|
|
setAuthenticated(data.authenticated);
|
|
setEnabled(data.enabled);
|
|
} catch {
|
|
setAuthenticated(false);
|
|
}
|
|
}
|
|
checkAuth();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
function onSessionExpired() {
|
|
setAuthenticated(false);
|
|
setSessionExpired(true);
|
|
}
|
|
window.addEventListener(SESSION_EXPIRED_EVENT, onSessionExpired);
|
|
return () => window.removeEventListener(SESSION_EXPIRED_EVENT, onSessionExpired);
|
|
}, []);
|
|
|
|
if (authenticated === null) {
|
|
return (
|
|
<div className="admin-loading" role="status">
|
|
<div className="admin-spinner" aria-hidden="true" />
|
|
<span className="sr-only">Loading admin panel</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!enabled) {
|
|
return (
|
|
<div className="admin-disabled">
|
|
<div className="admin-disabled-card">
|
|
<h1>Admin Panel</h1>
|
|
<p>
|
|
The admin panel is not enabled. Set <code>ADMIN_PASSWORD</code> in your environment
|
|
variables to activate it.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authenticated) {
|
|
return (
|
|
<AdminLogin
|
|
notice={sessionExpired ? 'Your session expired. Sign in again.' : undefined}
|
|
onSuccess={() => {
|
|
setSessionExpired(false);
|
|
setAuthenticated(true);
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<NotificationProvider>
|
|
<ConfirmProvider>
|
|
<AdminDashboard onLogout={() => setAuthenticated(false)}>{children}</AdminDashboard>
|
|
</ConfirmProvider>
|
|
</NotificationProvider>
|
|
);
|
|
}
|