From 5e7a744151ade5715e173e8972bbc28194a81f0b Mon Sep 17 00:00:00 2001 From: Matthieu Date: Wed, 1 Apr 2026 16:18:23 +0200 Subject: [PATCH] feat : add maintenance mode toggle in admin panel - Backend: MaintenanceModeListener blocks non-admin API requests when var/maintenance flag file exists. MaintenanceController provides toggle (PUT /api/admin/maintenance) and public check endpoint (GET /api/maintenance/check). - Frontend: Toggle button in admin page, maintenance.vue page for blocked users, middleware redirects non-admins to /maintenance. Co-Authored-By: Claude Opus 4.6 (1M context) --- config/packages/security.yaml | 1 + frontend/app/composables/useMaintenance.ts | 30 ++++++++++ frontend/app/middleware/profile.global.ts | 13 +++- frontend/app/pages/admin/index.vue | 32 +++++++++- frontend/app/pages/maintenance.vue | 21 +++++++ src/Controller/MaintenanceController.php | 58 ++++++++++++++++++ src/EventListener/MaintenanceModeListener.php | 60 +++++++++++++++++++ 7 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 frontend/app/composables/useMaintenance.ts create mode 100644 frontend/app/pages/maintenance.vue create mode 100644 src/Controller/MaintenanceController.php create mode 100644 src/EventListener/MaintenanceModeListener.php diff --git a/config/packages/security.yaml b/config/packages/security.yaml index d4fd595..9be4a08 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -55,6 +55,7 @@ security: - { path: ^/api/admin, roles: ROLE_ADMIN } - { path: ^/api/docs, roles: PUBLIC_ACCESS } - { path: ^/api/health$, roles: PUBLIC_ACCESS } + - { path: ^/api/maintenance/check$, roles: PUBLIC_ACCESS } - { path: ^/_mcp, roles: ROLE_USER } - { path: ^/docs, roles: PUBLIC_ACCESS } - { path: ^/contexts, roles: PUBLIC_ACCESS } diff --git a/frontend/app/composables/useMaintenance.ts b/frontend/app/composables/useMaintenance.ts new file mode 100644 index 0000000..2ce5ea0 --- /dev/null +++ b/frontend/app/composables/useMaintenance.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue' +import { useApi } from './useApi' + +const maintenanceEnabled = ref(false) + +export function useMaintenance() { + const { apiCall } = useApi() + const loading = ref(false) + + const fetchStatus = async () => { + const res = await apiCall<{ enabled: boolean }>('/admin/maintenance') + if (res.success && res.data) { + maintenanceEnabled.value = res.data.enabled + } + } + + const toggle = async () => { + loading.value = true + try { + const res = await apiCall<{ enabled: boolean }>('/admin/maintenance', { method: 'PUT' }) + if (res.success && res.data) { + maintenanceEnabled.value = res.data.enabled + } + } finally { + loading.value = false + } + } + + return { maintenanceEnabled, loading, fetchStatus, toggle } +} diff --git a/frontend/app/middleware/profile.global.ts b/frontend/app/middleware/profile.global.ts index c6ab767..e3c88ea 100644 --- a/frontend/app/middleware/profile.global.ts +++ b/frontend/app/middleware/profile.global.ts @@ -1,4 +1,4 @@ -import { useProfileSession, usePermissions } from "#imports"; +import { useProfileSession, usePermissions, useApi } from "#imports"; export default defineNuxtRouteMiddleware(async (to) => { const { ensureSession, activeProfile } = useProfileSession(); @@ -12,9 +12,10 @@ export default defineNuxtRouteMiddleware(async (to) => { normalizedPath.startsWith("/profiles") || fullPath.startsWith("/profiles") || routeName.startsWith("profiles"); + const isMaintenanceRoute = normalizedPath === "/maintenance"; // Redirect to login if no active profile - if (!activeProfile.value && !isProfilesRoute) { + if (!activeProfile.value && !isProfilesRoute && !isMaintenanceRoute) { return navigateTo("/profiles"); } @@ -29,5 +30,13 @@ export default defineNuxtRouteMiddleware(async (to) => { } } + // Maintenance mode check for non-admin users + if (!isAdmin.value && !isMaintenanceRoute && !isProfilesRoute) { + const { apiCall } = useApi(); + const res = await apiCall<{ enabled: boolean }>('/maintenance/check'); + if (res.success && res.data?.enabled) { + return navigateTo("/maintenance"); + } + } } }); diff --git a/frontend/app/pages/admin/index.vue b/frontend/app/pages/admin/index.vue index 645fe2e..e698bd9 100644 --- a/frontend/app/pages/admin/index.vue +++ b/frontend/app/pages/admin/index.vue @@ -1,5 +1,28 @@