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 @@