Manage roles in application
This commit is contained in:
parent
3ef774d7e0
commit
1788ec9062
|
|
@ -9,6 +9,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
|||
import './styles/app.css';
|
||||
import './styles/navbar.css';
|
||||
import './styles/sidebar.css';
|
||||
import './styles/choices.css'
|
||||
|
||||
import 'bootstrap';
|
||||
import './js/template.js';
|
||||
|
|
|
|||
|
|
@ -1,77 +1,32 @@
|
|||
import {Controller} from '@hotwired/stimulus';
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import Choices from 'choices.js';
|
||||
/*
|
||||
* The following line makes this controller "lazy": it won't be downloaded until needed
|
||||
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
|
||||
*/
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
rolesArray: Array,
|
||||
selectedRoleIds: Array,
|
||||
applicationsArray: Array,
|
||||
selectedApplicationIds: Array
|
||||
}
|
||||
// {value: 'choice1', label: 'Choice 1'},
|
||||
// {value: 'choice2', label: 'Choice 2'},
|
||||
// {value: 'choice3', label: 'Choice 3'},
|
||||
|
||||
static targets = ["select"];
|
||||
|
||||
connect() {
|
||||
this.roleSelect();
|
||||
}
|
||||
|
||||
roleSelect() {
|
||||
const element = document.getElementById('roles');
|
||||
if (element) {
|
||||
if (this.hasSelectTarget) {
|
||||
const choicesData = this.rolesArrayValue.map(role => ({
|
||||
value: role.id,
|
||||
label: role.name,
|
||||
selected: this.selectedRoleIdsValue.includes(role.id)
|
||||
}));
|
||||
|
||||
const choices = new Choices(element, {
|
||||
new Choices(this.selectTarget, {
|
||||
choices: choicesData,
|
||||
removeItemButton: true,
|
||||
placeholder: true,
|
||||
placeholderValue: 'Ajouter un ou plusieurs rôles'
|
||||
placeholderValue: 'Ajouter un ou plusieurs rôles',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
appSelect() {
|
||||
const element = document.getElementById('applications');
|
||||
if (element) {
|
||||
|
||||
const choicesData = this.applicationsArrayValue.map(app => ({
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
customProperties: {icon: app.icon},
|
||||
selected: this.selectedApplicationIdsValue.includes(app.id)
|
||||
}));
|
||||
|
||||
const choices = new Choices(element, {
|
||||
choices: choicesData,
|
||||
removeItemButton: true,
|
||||
placeholder: true,
|
||||
placeholderValue: 'Ajouter une ou plusieurs applications',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.roleSelect();
|
||||
this.appSelect();
|
||||
|
||||
// Set choices after initialization
|
||||
// choices.setValue(choicesData);
|
||||
}
|
||||
|
||||
|
||||
// Add custom controller actions here
|
||||
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
|
||||
|
||||
disconnect() {
|
||||
// Called anytime its element is disconnected from the DOM
|
||||
// (on page change, when it's removed from or moved in the DOM, etc.)
|
||||
|
||||
// Here you should remove all event listeners added in "connect()"
|
||||
// this.fooTarget.removeEventListener('click', this._fooBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
.choices {
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Input style */
|
||||
.choices__inner {
|
||||
background: #fff;
|
||||
border: 1px solid var(--primary-blue-light);
|
||||
border-radius: 0.375rem; /* same as Bootstrap `.form-control` */
|
||||
padding: 0.5rem;
|
||||
min-height: 2.5rem;
|
||||
box-shadow: none;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Placeholder */
|
||||
.choices__placeholder {
|
||||
color: #6c757d; /* Bootstrap muted */
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Selected items (tags) */
|
||||
.choices__list--multiple .choices__item {
|
||||
background-color: var(--primary-blue-light) !important;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.15rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Remove "x" button */
|
||||
.choices__list--multiple .choices__item .choices__button {
|
||||
border-left: 1px solid rgba(255,255,255,0.3);
|
||||
margin-left: 0.25rem;
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.choices__list--multiple .choices__item .choices__button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Dropdown list */
|
||||
.choices__list--dropdown {
|
||||
border: 1px solid var(--primary-blue-light);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.1);
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
/* Dropdown options */
|
||||
.choices__list--dropdown .choices__item {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Hover/active in dropdown */
|
||||
.choices__list--dropdown .choices__item--highlighted {
|
||||
background-color: var(--primary-blue-light);
|
||||
color: #fff;
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserOrganizatonApp;
|
||||
use App\Entity\UsersOrganizations;
|
||||
|
|
@ -91,7 +93,7 @@ class UserController extends AbstractController
|
|||
}
|
||||
}
|
||||
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $uo, 'isActive' => true]);
|
||||
$uoa = $this->userOrganizationAppService->groupUserOrganizationAppsByApplication($uoa);
|
||||
$uoas = $this->userOrganizationAppService->groupUserOrganizationAppsByApplication($uoa);
|
||||
$this->actionService->createAction("View user information", $actingUser, null, $user->getUserIdentifier());
|
||||
} catch (\Exception $e) {
|
||||
//ignore
|
||||
|
|
@ -99,10 +101,9 @@ class UserController extends AbstractController
|
|||
} else {
|
||||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||||
}
|
||||
|
||||
return $this->render('user/show.html.twig', [
|
||||
'user' => $user,
|
||||
'uoas' => $uoa ?? null,
|
||||
'uoas' => $uoas ?? null,
|
||||
'orgs' => $orgs ?? null,
|
||||
'organizationId' => $orgId ?? null,
|
||||
'uoActive' => $uoActive ?? null// specific for single organization context and deactivate user from said org
|
||||
|
|
@ -332,4 +333,46 @@ class UserController extends AbstractController
|
|||
$this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier());
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
|
||||
#[Route(path: '/application/roles/{id}', name: 'application_role', methods: ['GET', 'POST'])]
|
||||
public function applicationRole(int $id, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted("ROLE_ADMIN");
|
||||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||||
|
||||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||||
$uo = $this->userOrganizationService->getByIdOrFail($id);
|
||||
|
||||
$application = $this->entityManager->getRepository(Apps::class)->find($request->get('applicationId'));
|
||||
if (!$application) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
|
||||
$selectedRolesIds = $request->get('roles', []);
|
||||
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
|
||||
if (!$roleUser) {
|
||||
throw $this->createNotFoundException('Default role not found');
|
||||
}
|
||||
|
||||
if (in_array($roleUser->getId(), $selectedRolesIds)) {
|
||||
$this->userOrganizationAppService->syncRolesForUserOrganizationApp(
|
||||
$uo,
|
||||
$application,
|
||||
$selectedRolesIds,
|
||||
$actingUser
|
||||
);
|
||||
} else {
|
||||
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
|
||||
}
|
||||
|
||||
$user = $uo->getUsers();
|
||||
return $this->redirectToRoute('user_show', [
|
||||
'user' => $user,
|
||||
'id' => $user->getId(),
|
||||
'organizationId'=> $uo->getOrganization()->getId()
|
||||
]);
|
||||
}
|
||||
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserOrganizatonApp;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use App\Service\ActionService;
|
||||
|
|
@ -14,7 +17,8 @@ class UserOrganizationAppService
|
|||
}
|
||||
|
||||
/**
|
||||
* Groups UserOrganizationApp entities by their associated Application.
|
||||
* Groups UserOrganizationApp entities by Application
|
||||
* and prepares data for Twig.
|
||||
*
|
||||
* @param UserOrganizatonApp[] $userOrgApps
|
||||
* @return array
|
||||
|
|
@ -30,19 +34,33 @@ class UserOrganizationAppService
|
|||
|
||||
if (!isset($grouped[$appId])) {
|
||||
$grouped[$appId] = [
|
||||
'userOrganization'=> $uoa->getUserOrganization(),
|
||||
'application' => $app,
|
||||
'roles' => [],
|
||||
'uoId' => $uoa->getUserOrganization()->getId(),
|
||||
'application' => $app, // you can still pass entity here
|
||||
'roles' => [], // selected roles for display
|
||||
'rolesArray' => [], // all possible roles
|
||||
'selectedRoleIds' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$grouped[$appId]['roles'][] = [
|
||||
'id' => $roleEntity->getId(),
|
||||
'name' => $roleEntity->getName(), // adjust to your Role entity fields
|
||||
'name' => $roleEntity->getName(),
|
||||
];
|
||||
$grouped[$appId]['selectedRoleIds'][] = $roleEntity->getId();
|
||||
}
|
||||
|
||||
// roles are the same for all apps → load once, inject into each appGroup
|
||||
$allRoles = $this->entityManager->getRepository(Roles::class)->findAll();
|
||||
|
||||
foreach ($grouped as &$appGroup) {
|
||||
foreach ($allRoles as $role) {
|
||||
$appGroup['rolesArray'][] = [
|
||||
'id' => $role->getId(),
|
||||
'name' => $role->getName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// if you want a simple indexed array instead of associative keyed by appId
|
||||
return array_values($grouped);
|
||||
}
|
||||
|
||||
|
|
@ -52,9 +70,13 @@ class UserOrganizationAppService
|
|||
* @param UsersOrganizations $userOrganization
|
||||
* @return void
|
||||
*/
|
||||
public function deactivateAllUserOrganizationsAppLinks(UsersOrganizations $userOrganization): void
|
||||
public function deactivateAllUserOrganizationsAppLinks(UsersOrganizations $userOrganization, Apps $app = null): void
|
||||
{
|
||||
$uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $userOrganization, 'isActive' => true]);
|
||||
if($app) {
|
||||
$uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $userOrganization, 'application' => $app, 'isActive' => true]);
|
||||
} else {
|
||||
$uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $userOrganization, 'isActive' => true]);
|
||||
}
|
||||
foreach ($uoas as $uoa) {
|
||||
$uoa->setIsActive(false);
|
||||
$this->actionService->createAction("Deactivate UOA link", $userOrganization->getUsers(),
|
||||
|
|
@ -62,4 +84,71 @@ class UserOrganizationAppService
|
|||
$this->entityManager->persist($uoa);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncRolesForUserOrganizationApp(
|
||||
UsersOrganizations $uo,
|
||||
Apps $application,
|
||||
array $selectedRoleIds,
|
||||
User $actingUser
|
||||
): void {
|
||||
$repo = $this->entityManager->getRepository(UserOrganizatonApp::class);
|
||||
$currentLinks = $repo->findBy([
|
||||
'userOrganization' => $uo,
|
||||
'application' => $application,
|
||||
]);
|
||||
|
||||
$currentRoleIds = [];
|
||||
foreach ($currentLinks as $uoa) {
|
||||
$roleId = $uoa->getRole()->getId();
|
||||
$currentRoleIds[] = $roleId;
|
||||
|
||||
if (in_array($roleId, $selectedRoleIds)) {
|
||||
if (!$uoa->isActive()) {
|
||||
$uoa->setIsActive(true);
|
||||
$this->entityManager->persist($uoa);
|
||||
$this->actionService->createAction(
|
||||
"Re-activate user role for application",
|
||||
$actingUser,
|
||||
$uo->getOrganization(),
|
||||
"App: {$application->getName()}, Role: {$uoa->getRole()->getName()} for user {$uo->getUsers()->getUserIdentifier()}"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ($uoa->isActive()) {
|
||||
$uoa->setIsActive(false);
|
||||
$this->entityManager->persist($uoa);
|
||||
|
||||
$this->actionService->createAction(
|
||||
"Deactivate user role for application",
|
||||
$actingUser,
|
||||
$uo->getOrganization(),
|
||||
"App: {$application->getName()}, Role: {$uoa->getRole()->getName()} for user {$uo->getUsers()->getUserIdentifier()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing roles
|
||||
foreach ($selectedRoleIds as $roleId) {
|
||||
if (!in_array($roleId, $currentRoleIds)) {
|
||||
$role = $this->entityManager->getRepository(Roles::class)->find($roleId);
|
||||
if ($role) {
|
||||
$newUoa = new UserOrganizatonApp();
|
||||
$newUoa->setUserOrganization($uo);
|
||||
$newUoa->setApplication($application);
|
||||
$newUoa->setRole($role);
|
||||
$newUoa->setIsActive(true);
|
||||
|
||||
$this->entityManager->persist($newUoa);
|
||||
$this->actionService->createAction("New user role for application",
|
||||
$actingUser,
|
||||
$uo->getOrganization(),
|
||||
"App: {$application->getName()}, Role: {$role->getName()} for user {$uo->getUsers()->getUserIdentifier()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use App\Entity\UsersOrganizations;
|
|||
use App\Service\ActionService;
|
||||
use \App\Service\UserOrganizationAppService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Service pour la gestion des organisations d'utilisateurs.
|
||||
|
|
@ -43,5 +44,14 @@ readonly class UserOrganizationService
|
|||
}
|
||||
}
|
||||
|
||||
public function getByIdOrFail(int $id): UsersOrganizations
|
||||
{
|
||||
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->find($id);
|
||||
if (!$uo) {
|
||||
throw new NotFoundHttpException("UserOrganization not found");
|
||||
}
|
||||
return $uo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,41 +14,30 @@
|
|||
<div class="card-body">
|
||||
<div class="row">
|
||||
<p><b> Description : </b>{{ uoa.application.description|default('Aucune description disponible.') }}</p>
|
||||
{% if roles|length is not null %}
|
||||
<div class="col">
|
||||
<p><b>Rôles :</b>
|
||||
|
||||
{% for role in roles %}
|
||||
{% if role.name == "SUPER ADMIN" %}
|
||||
<span class="badge bg-danger">{{ role.name|capitalize }}</span>
|
||||
{% elseif role.name == "ADMIN" %}
|
||||
<span class="badge bg-danger">{{ role.name|capitalize }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary">{{ role.name|capitalize }}</span>
|
||||
{% endif %}
|
||||
{% if not loop.last %} - {% endif %}
|
||||
{% else %}
|
||||
<p>Aucun rôle attribué.</p>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{# {% if is_granted('ROLE_ADMIN') %} #}
|
||||
{# <form method="POST" action="{{ path('user_organization_edit', {'id' : uo.uoId}) }}" #}
|
||||
{# data-controller="user" #}
|
||||
{# data-user-roles-array-value="{{ rolesArray|json_encode }}" #}
|
||||
{# data-user-selected-role-ids-value="{{ selectedRoleIds|json_encode }}"> #}
|
||||
{# <div class="form-group mb-3"> #}
|
||||
{# <label for="roles">Roles</label> #}
|
||||
{# <select class="choices" data-type="select-multiple" id="roles" name="roles[]" multiple> #}
|
||||
{# </select> #}
|
||||
{# </div> #}
|
||||
{# <button type="submit" class="btn btn-primary">Sauvegarder</button> #}
|
||||
{# </form> #}
|
||||
{# {% endif %} #}
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<form method="POST"
|
||||
action="{{ path('user_application_role', { id : uoa.uoId }) }}"
|
||||
onsubmit="return confirm('Attention, si le role utilisateur ' +
|
||||
'n\'est pas attribué, l\'utilisateur ne pourra plus accéder à l\'application. Êtes-vous sûr ?');"
|
||||
data-controller="user"
|
||||
data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}"
|
||||
data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}">
|
||||
<div class="form-group mb-3">
|
||||
<label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label>
|
||||
<select data-user-target="select"
|
||||
class="choices"
|
||||
id="roles-{{ uoa.application.id }}"
|
||||
name="roles[]"
|
||||
multiple>
|
||||
</select>
|
||||
</div>
|
||||
<input hidden type="text" value="{{ uoa.application.id }}" name="applicationId">
|
||||
<button type="submit" class="btn btn-primary">Sauvegarder</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue