feat: implement multi-tenancy and super admin impersonation with security banner

This commit is contained in:
jeremy bayse
2026-02-21 20:15:47 +01:00
parent a0e904d69d
commit 63e448ef22
31 changed files with 819 additions and 51 deletions

View File

@@ -13,6 +13,21 @@ const showingNavigationDropdown = ref(false);
<template>
<div>
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
<!-- Impersonation Banner -->
<div v-if="$page.props.tenant.is_impersonating" class="bg-amber-500 text-white py-2 px-4 shadow-md">
<div class="max-w-7xl mx-auto flex justify-between items-center text-sm font-bold">
<div class="flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<span>MODE SIMULATION ACTIF : Vous modifiez actuellement le locataire "{{ $page.props.tenant.current ? $page.props.tenant.current.name : 'VUE GLOBALE' }}"</span>
</div>
<Link :href="route('superadmin.reset')" method="post" as="button" class="bg-white text-amber-600 px-3 py-1 rounded-md hover:bg-amber-50 transition-colors">
Arrêter la simulation
</Link>
</div>
</div>
<nav
class="border-b border-gray-100 bg-white dark:border-gray-700 dark:bg-gray-800"
>
@@ -20,13 +35,16 @@ const showingNavigationDropdown = ref(false);
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 justify-between">
<div class="flex">
<!-- Logo -->
<div class="flex shrink-0 items-center">
<!-- Logo and Structure Name -->
<div class="flex shrink-0 items-center space-x-3">
<Link :href="route('dashboard')">
<ApplicationLogo
class="block h-9 w-auto"
/>
</Link>
<span class="text-lg font-bold text-gray-800 dark:text-gray-200">
{{ $page.props.tenant.current ? $page.props.tenant.current.name : ($page.props.auth.user.structure ? $page.props.auth.user.structure.name : '') }}
</span>
</div>
<!-- Navigation Links -->
@@ -40,40 +58,48 @@ const showingNavigationDropdown = ref(false);
Tableau de Bord
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('users.index')"
:active="route().current('users.*')"
>
Utilisateurs
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('roles.index')"
:active="route().current('roles.*')"
>
Rôles
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('permissions.index')"
:active="route().current('permissions.*')"
>
Permissions
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('services.index')"
:active="route().current('services.*')"
>
Services
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('templates.index')"
:active="route().current('templates.*')"
>
Modèles
</NavLink>
<NavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'SuperAdmin')"
:href="route('superadmin.index')"
:active="route().current('superadmin.*')"
class="text-indigo-600 dark:text-indigo-400 font-bold"
>
🛠 SaaS Admin
</NavLink>
</div>
</div>
@@ -182,40 +208,48 @@ const showingNavigationDropdown = ref(false);
Tableau de Bord
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('users.index')"
:active="route().current('users.*')"
>
Utilisateurs
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('roles.index')"
:active="route().current('roles.*')"
>
Rôles
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('permissions.index')"
:active="route().current('permissions.*')"
>
Permissions
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('services.index')"
:active="route().current('services.*')"
>
Services
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin')"
v-if="$page.props.auth.user.roles.some(r => r.name === 'Admin' || r.name === 'SuperAdmin')"
:href="route('templates.index')"
:active="route().current('templates.*')"
>
Modèles
</ResponsiveNavLink>
<ResponsiveNavLink
v-if="$page.props.auth.user.roles.some(r => r.name === 'SuperAdmin')"
:href="route('superadmin.index')"
:active="route().current('superadmin.*')"
class="text-indigo-600 dark:text-indigo-400 font-bold"
>
🛠 SaaS Admin
</ResponsiveNavLink>
</div>
<!-- Responsive Settings Options -->
@@ -261,6 +295,18 @@ const showingNavigationDropdown = ref(false);
<!-- Page Content -->
<main>
<!-- Flash Messages -->
<div v-if="$page.props.flash.success" class="max-w-7xl mx-auto mt-4 px-4 sm:px-6 lg:px-8">
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $page.props.flash.success }}</span>
</div>
</div>
<div v-if="$page.props.flash.error" class="max-w-7xl mx-auto mt-4 px-4 sm:px-6 lg:px-8">
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $page.props.flash.error }}</span>
</div>
</div>
<slot />
</main>
</div>