feat: service mode (maintenance mode)

Admins can toggle maintenance mode from the admin dashboard (new
"Wartung" tab). When active, all non-admin users see a full-page
maintenance screen instead of the app.

- Backend: GET /api/config/service-mode endpoint (authenticated)
- Backend: stores state in app_settings key 'service_mode'
- Frontend: ServiceModeGuard wraps all ProtectedRoutes
- Frontend: ServiceModePage full-screen maintenance UI
- Frontend: ServiceModeTab in admin dashboard with toggle + message
- Admins (dashboard_admin group) always bypass the guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-03-12 13:21:49 +01:00
parent 6c1cbb0ef3
commit 21b7be22db
9 changed files with 197 additions and 3 deletions

View File

@@ -3,6 +3,16 @@ import environment from '../config/environment';
import settingsService from '../services/settings.service';
class ConfigController {
async getServiceMode(_req: Request, res: Response): Promise<void> {
try {
const setting = await settingsService.get('service_mode');
const value = setting?.value ?? { active: false, message: '' };
res.json({ success: true, data: value });
} catch {
res.json({ success: true, data: { active: false, message: '' } });
}
}
async getExternalLinks(_req: Request, res: Response): Promise<void> {
const envLinks: Record<string, string> = {};
if (environment.nextcloudUrl) envLinks.nextcloud = environment.nextcloudUrl;

View File

@@ -4,5 +4,6 @@ import { authenticate } from '../middleware/auth.middleware';
const router = Router();
router.get('/external-links', authenticate, configController.getExternalLinks.bind(configController));
router.get('/service-mode', authenticate, configController.getServiceMode.bind(configController));
export default router;