Initial commit: CABM-IT (ItGlue-style MSP documentation platform)
Laravel 13 + Livewire/Volt app for managing client organizations, documents, passwords, flexible assets, virtual machines, contacts, domains, todos, shortcuts, and role-based user administration.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? config('app.name') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="bg-slate-50 text-slate-900 antialiased">
|
||||
<div class="flex min-h-screen">
|
||||
@php
|
||||
$currentOrganization = request()->route('organization');
|
||||
@endphp
|
||||
<aside class="w-60 shrink-0 bg-slate-900 text-slate-200">
|
||||
<div class="px-4 py-4 text-lg font-semibold text-white">{{ config('app.name') }}</div>
|
||||
<nav class="mt-2 space-y-1 px-2 text-sm">
|
||||
<a href="{{ route('organizations.index') }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Organisations
|
||||
</a>
|
||||
|
||||
@if ($currentOrganization)
|
||||
@php
|
||||
$assetTypes = \App\Models\FlexibleAssetType::whereHas('assets', fn ($q) => $q->where('organization_id', $currentOrganization->id))
|
||||
->orderBy('name')
|
||||
->get();
|
||||
$shortcuts = \App\Models\Shortcut::where('organization_id', $currentOrganization->id)
|
||||
->orWhereNull('organization_id')
|
||||
->orderBy('label')
|
||||
->get();
|
||||
@endphp
|
||||
|
||||
<div class="mt-3 border-t border-slate-800 pt-3">
|
||||
<p class="px-3 pb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">{{ $currentOrganization->name }}</p>
|
||||
|
||||
<a href="{{ route('organizations.dashboard', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.dashboard') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Tableau de bord
|
||||
</a>
|
||||
<a href="{{ route('organizations.todos.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.todos.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
ToDo
|
||||
</a>
|
||||
<a href="{{ route('organizations.passwords.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.passwords.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Mots de passe
|
||||
</a>
|
||||
<a href="{{ route('organizations.contacts.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.contacts.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Contacts
|
||||
</a>
|
||||
<a href="{{ route('organizations.locations.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.locations.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Sites
|
||||
</a>
|
||||
<a href="{{ route('organizations.domains.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.domains.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Domaines
|
||||
</a>
|
||||
<a href="{{ route('organizations.documents.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.documents.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Documents
|
||||
</a>
|
||||
<a href="{{ route('organizations.virtual-machines.index', $currentOrganization) }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('organizations.virtual-machines.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Machines virtuelles
|
||||
</a>
|
||||
<div x-data="{ open: localStorage.getItem('nav-assets-open') === '1' }" x-init="$watch('open', value => localStorage.setItem('nav-assets-open', value ? '1' : '0'))">
|
||||
<div class="flex items-center rounded hover:bg-slate-800 {{ request()->routeIs('organizations.assets.picker') ? 'bg-slate-800 text-white' : '' }}">
|
||||
<a href="{{ route('organizations.assets.picker', $currentOrganization) }}"
|
||||
class="block flex-1 px-3 py-2">
|
||||
Actifs
|
||||
</a>
|
||||
@if ($assetTypes->isNotEmpty())
|
||||
<button type="button" x-on:click="open = !open" class="px-2 py-2 text-slate-400 hover:text-white" :aria-expanded="open" aria-label="Replier/déplier les actifs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-3.5 w-3.5 transition-transform" :class="open ? 'rotate-90' : ''">
|
||||
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@if ($assetTypes->isNotEmpty())
|
||||
<div x-show="open" class="ml-4 space-y-0.5 border-l border-slate-700 pl-2">
|
||||
@foreach ($assetTypes as $assetType)
|
||||
<a href="{{ route('organizations.assets.index', [$currentOrganization, $assetType]) }}"
|
||||
class="block rounded px-3 py-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-100 {{ request()->routeIs('organizations.assets.*') && request()->route('type')?->is($assetType) ? 'bg-slate-800 text-white' : '' }}">
|
||||
{{ $assetType->icon }} {{ \Illuminate\Support\Str::ucfirst(\Illuminate\Support\Str::lower($assetType->name)) }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div x-data="{ open: localStorage.getItem('nav-shortcuts-open') === '1' }" x-init="$watch('open', value => localStorage.setItem('nav-shortcuts-open', value ? '1' : '0'))">
|
||||
<div class="flex items-center rounded hover:bg-slate-800 {{ request()->routeIs('organizations.shortcuts.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
<a href="{{ route('organizations.shortcuts.index', $currentOrganization) }}"
|
||||
class="block flex-1 px-3 py-2">
|
||||
Raccourcis
|
||||
</a>
|
||||
@if ($shortcuts->isNotEmpty())
|
||||
<button type="button" x-on:click="open = !open" class="px-2 py-2 text-slate-400 hover:text-white" :aria-expanded="open" aria-label="Replier/déplier les raccourcis">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-3.5 w-3.5 transition-transform" :class="open ? 'rotate-90' : ''">
|
||||
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@if ($shortcuts->isNotEmpty())
|
||||
<div x-show="open" class="ml-4 space-y-0.5 border-l border-slate-700 pl-2">
|
||||
@foreach ($shortcuts as $shortcut)
|
||||
<a href="{{ $shortcut->url }}" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-1 rounded px-3 py-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-100">
|
||||
<span class="truncate">{{ $shortcut->icon ?: '🔗' }} {{ $shortcut->label }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@role('Administrateur')
|
||||
<div class="mt-3 border-t border-slate-800 pt-3">
|
||||
<p class="px-3 pb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">Administration</p>
|
||||
|
||||
<a href="{{ route('asset-types.index') }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('asset-types.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Types d'actifs
|
||||
</a>
|
||||
<a href="{{ route('users.index') }}"
|
||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('users.*') ? 'bg-slate-800 text-white' : '' }}">
|
||||
Utilisateurs
|
||||
</a>
|
||||
</div>
|
||||
@endrole
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div class="flex-1">
|
||||
<header class="flex items-center justify-between border-b border-slate-200 bg-white px-6 py-3">
|
||||
<div class="text-sm text-slate-500">{{ $header ?? '' }}</div>
|
||||
@auth
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm text-slate-500 hover:text-slate-900">
|
||||
{{ auth()->user()->name }} · Déconnexion
|
||||
</button>
|
||||
</form>
|
||||
@endauth
|
||||
</header>
|
||||
|
||||
<main class="p-6">
|
||||
@if (session('status'))
|
||||
<div class="mb-4 rounded border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm text-emerald-700">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? config('app.name') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="flex min-h-screen items-center justify-center bg-slate-100">
|
||||
<div class="w-full max-w-sm rounded-lg bg-white p-8 shadow">
|
||||
<div class="mb-6 text-center text-xl font-semibold text-slate-900">{{ config('app.name') }}</div>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user