✨ Ajout gestion des utilisateurs
This commit is contained in:
parent
89e2ccce02
commit
2fb1414228
@ -47,6 +47,7 @@ class CreateUserCommand extends Command
|
||||
$user->setEmail($email);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
|
||||
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
|
||||
$user->initId();
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
91
src/Controller/AdminController.php
Normal file
91
src/Controller/AdminController.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\UserAdminType;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[IsGranted('ROLE_ADMIN')]
|
||||
#[Route('/admin', name: 'app_admin_')]
|
||||
class AdminController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserPasswordHasherInterface $passwordEncoder,
|
||||
private readonly UserRepository $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/', name: 'index')]
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('admin/index.html.twig');
|
||||
}
|
||||
|
||||
#[Route('/users', name: 'user_index')]
|
||||
public function indexUsers(): Response
|
||||
{
|
||||
$users = $this->userRepository->findAll();
|
||||
return $this->render('admin/user_index.html.twig', [
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/users/create', name: 'user_create')]
|
||||
#[Route('/users/edit/{user}', name: 'user_edit')]
|
||||
public function editUsers(#[MapEntity(id: 'user')] ?User $user, Request $request): Response
|
||||
{
|
||||
$isNew = false;
|
||||
if (!$user) {
|
||||
$user = new User();
|
||||
$isNew = true;
|
||||
}
|
||||
|
||||
$form = $this->createForm(UserAdminType::class, $user);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$role = $form->get('role')->getData();
|
||||
$user->setRoles([$role]);
|
||||
$user->initId();
|
||||
|
||||
if ($form->has('plainPassword')) {
|
||||
$plainPassword = $form->get('plainPassword')->getData();
|
||||
$user->setPassword($this->passwordEncoder->hashPassword($user, $plainPassword));
|
||||
}
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'L\'utilisateur a bien été enregistré !');
|
||||
return $this->redirectToRoute('app_admin_user_index');
|
||||
}
|
||||
|
||||
return $this->render('admin/user_edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'user' => $user,
|
||||
'isNew' => $isNew,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/users/delete/{user}', name: 'user_delete')]
|
||||
public function deleteUser(#[MapEntity(id: 'user')] User $user): Response
|
||||
{
|
||||
$this->entityManager->remove($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'L\'utilisateur a bien été supprimé !');
|
||||
return $this->redirectToRoute('app_admin_user_index');
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[Route('/files', 'app_files_')]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
class DashboardController extends AbstractController
|
||||
class FilesController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @throws FilesystemException
|
||||
@ -62,7 +62,7 @@ class DashboardController extends AbstractController
|
||||
}
|
||||
});
|
||||
|
||||
return $this->render('dashboard/index.html.twig', [
|
||||
return $this->render('files/index.html.twig', [
|
||||
'files' => $realFiles,
|
||||
'path' => $path,
|
||||
]);
|
@ -17,7 +17,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'uuid', length: 180)]
|
||||
private ?Uuid $id;
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $email = null;
|
||||
@ -34,8 +34,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
public function __construct()
|
||||
|
||||
public function initId(): void
|
||||
{
|
||||
if ($this->id !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->id = Uuid::v4();
|
||||
}
|
||||
|
||||
|
56
src/Form/UserAdminType.php
Normal file
56
src/Form/UserAdminType.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserAdminType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'Adresse email',
|
||||
])
|
||||
->add('role', ChoiceType::class, [
|
||||
'label' => 'Rôle',
|
||||
'choices' => [
|
||||
'Utilisateur' => 'ROLE_USER',
|
||||
'Administrateur' => 'ROLE_ADMIN',
|
||||
],
|
||||
'mapped' => false,
|
||||
])
|
||||
;
|
||||
|
||||
// Si l'utilisateur est nouveau, on ajoute le champ de mot de passe
|
||||
if (!$options['data']->getId()) {
|
||||
$builder->add('plainPassword', PasswordType::class, [
|
||||
'label' => 'Mot de passe',
|
||||
'required' => true,
|
||||
'mapped' => false,
|
||||
]);
|
||||
} else {
|
||||
// On set le rôle actuel de l'utilisateur
|
||||
$builder->get('role')->setData($options['data']->getRoles()[0]);
|
||||
}
|
||||
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
'label' => 'Enregistrer',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
15
templates/admin/index.html.twig
Normal file
15
templates/admin/index.html.twig
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base-admin.html.twig' %}
|
||||
|
||||
{% block title %}Le cloud de Camélia-Studio{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-96 flex items-center flex-col justify-center">
|
||||
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur l'administration</h1>
|
||||
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Gérez facilement les accès des membres de Camélia-Studio à l'espace de stockage partagé Kumora. Ajoutez, modifiez ou retirez les utilisateurs en quelques clics.</p>
|
||||
<div class="mb-16">
|
||||
<a href="{{ path('app_files_index') }}" class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:bg-gray-800 dark:text-white dark:border-gray-600 font-medium rounded-lg px-8 py-3">
|
||||
Gérer les utilisateurs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
30
templates/admin/user_edit.html.twig
Normal file
30
templates/admin/user_edit.html.twig
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends 'base-admin.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||
{% if not isNew %}
|
||||
Edition de l'utilisateur {{ user.email }}
|
||||
{% else %}
|
||||
Création d'un nouvel utilisateur
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div class="mt-4">
|
||||
{{ include('partials/alerts.html.twig') }}
|
||||
</div>
|
||||
|
||||
{{ form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
{% if not isNew %}
|
||||
Edition de l'utilisateur {{ user.email }}
|
||||
{% else %}
|
||||
Création d'un nouvel utilisateur
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
63
templates/admin/user_index.html.twig
Normal file
63
templates/admin/user_index.html.twig
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends 'base-admin.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-16 mt-4">
|
||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Liste des utilisateurs</h5>
|
||||
<div class="flex justify-end">
|
||||
<a href="{{ path('app_admin_user_create') }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Créer un utilisateur</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
{{ include('partials/alerts.html.twig') }}
|
||||
</div>
|
||||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-4">
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Id
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Email
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Rôle
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700">
|
||||
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ user.id }}
|
||||
</th>
|
||||
<td class="px-6 py-4">
|
||||
{{ user.email }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ user.roles[0] == 'ROLE_ADMIN' ? 'Administrateur' : 'Utilisateur' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 flex gap-2 light:text-black">
|
||||
<a href="{{ path('app_admin_user_edit', {
|
||||
user: user.id
|
||||
}) }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
||||
<a href="{{ path('app_admin_user_delete', {
|
||||
user: user.id
|
||||
}) }}" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
Liste des utilisateurs
|
||||
{% endblock %}
|
||||
|
18
templates/base-admin.html.twig
Normal file
18
templates/base-admin.html.twig
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" class="bg-white dark:bg-gray-800 dark:text-white">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kumora - {% block title %}Accueil{% endblock %}</title>
|
||||
<link rel="icon" href="{{ asset('images/favicon.ico') }}">
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body data-turbo="false">
|
||||
{% include "partials/navbar-admin.html.twig" %}
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
@ -5,11 +5,13 @@
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-16">
|
||||
<h1 class="text-center text-2xl font-bold mt-4">Liste des fichiers</h1>
|
||||
<div class="mt-4">
|
||||
{{ include('partials/alerts.html.twig') }}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
{% include 'partials/breadbrumb.html.twig' %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="relative overflow-x-auto mt-4">
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
@ -49,13 +51,11 @@
|
||||
</td>
|
||||
<td class="px-6 py-4 flex gap-2 light:text-black">
|
||||
{% if file.type == 'file' %}
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:eye" /></a>
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
|
||||
<a href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
||||
<a href="#" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
||||
{% else %}
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
|
||||
<a href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:link" /></a>
|
||||
<a href="#" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
||||
<a href="#" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
||||
{% endif %}
|
21
templates/partials/alerts.html.twig
Normal file
21
templates/partials/alerts.html.twig
Normal file
@ -0,0 +1,21 @@
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
{% if label == 'success' %}
|
||||
<div class="border border-green-300 dark:border-green-800 p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'info' %}
|
||||
<div class="border border-blue-300 dark:border-blue-800 p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'error' %}
|
||||
<div class="border border-red-300 dark:border-red-800 p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% elseif label == 'warning' %}
|
||||
<div class="border border-yellow-300 dark:border-yellow-800 p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
28
templates/partials/navbar-admin.html.twig
Normal file
28
templates/partials/navbar-admin.html.twig
Normal file
@ -0,0 +1,28 @@
|
||||
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<img src="{{ asset('images/logo.png') }}" class="h-8" alt="Logo Camélia-Studio" />
|
||||
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
|
||||
</a>
|
||||
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
||||
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||
<li>
|
||||
<a href="{{ path('app_admin_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('app_admin_user_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Gestion des utilisateurs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('app_home') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Retour à l'accueil</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -25,7 +25,7 @@
|
||||
{% else %}
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<li>
|
||||
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
||||
<a href="{{ path('app_admin_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
|
Loading…
x
Reference in New Issue
Block a user