Rôles, tableau de bord et tests unitaires

This commit is contained in:
Yasser Boulayoune 2026-04-07 16:26:00 +02:00
parent f508a2fcb5
commit 1799035236
51 changed files with 4350 additions and 0 deletions

View file

@ -0,0 +1,32 @@
{% extends 'base.html.twig' %}
{% block title %}Mot de passe oublié - ServerControl{% endblock %}
{% block body %}
<div class="row justify-content-center mt-5">
<div class="col-md-4">
<div class="card p-4">
<h2 class="text-center mb-2"><i class="bi bi-key me-2"></i>Mot de passe oublié</h2>
<p class="text-center mb-4" style="font-size:.85rem; color:var(--muted);">
Saisissez votre adresse email et on vous envoie un lien pour réinitialiser votre mot de passe.
</p>
{{ form_start(form) }}
<div class="mb-3">
{{ form_label(form.email) }}
{{ form_widget(form.email, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.email) }}
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-envelope me-1"></i>Envoyer le lien
</button>
{{ form_end(form) }}
<div class="text-center mt-3" style="font-size:.85rem;">
<a href="{{ path('app_login') }}" style="color:var(--muted);">
<i class="bi bi-arrow-left me-1"></i>Retour au login
</a>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,61 @@
{% extends 'base.html.twig' %}
{% block title %}Login - ServerControl{% endblock %}
{% block body %}
<div class="row justify-content-center mt-5">
<div class="col-md-4">
<div class="card p-4">
<h2 class="text-center mb-4"><i class="bi bi-server me-2"></i>ServerControl</h2>
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<div class="mb-3">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="_username" value="{{ last_username }}" autocomplete="email" required>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input class="form-control" type="password" name="_password" autocomplete="current-password" required>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" name="_remember_me" id="remember_me">
<label class="form-check-label" for="remember_me">Remember me</label>
</div>
<button class="btn btn-primary w-100">Login</button>
</form>
<div class="text-center mt-3 d-flex flex-column gap-1">
<a href="{{ path('app_forgot_password') }}" style="font-size:.85rem; color:var(--muted);">Mot de passe oublié ?</a>
<a href="{{ path('app_register') }}">Crée un compte</a>
</div>
</div>
<div class="mt-3 p-3 rounded" style="background:#1e2a3a; font-size:.85rem;">
<div class="text-white-50 mb-2"><i class="bi bi-info-circle me-1"></i>Comptes de test</div>
<div class="d-flex flex-column gap-2">
<button class="btn btn-sm btn-outline-light text-start" onclick="fillLogin('admin@servercontrol.local','Admin1234!')">
<span class="badge bg-danger me-1">ADMIN</span>
admin@servercontrol.local
</button>
<button class="btn btn-sm btn-outline-light text-start" onclick="fillLogin('operator@servercontrol.local','Operator1234!')">
<span class="badge bg-primary me-1">OPERATOR</span>
operator@servercontrol.local
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
function fillLogin(email, password) {
document.querySelector('input[name="_username"]').value = email;
document.querySelector('input[name="_password"]').value = password;
}
</script>
{% endblock %}

View file

@ -0,0 +1,30 @@
{% extends 'base.html.twig' %}
{% block title %}Pending Access - ServerControl{% endblock %}
{% block body %}
<div class="row justify-content-center mt-5">
<div class="col-md-5 col-lg-4 text-center">
<div class="card p-4">
<div class="mb-3">
<i class="bi bi-hourglass-split text-warning" style="font-size: 3rem;"></i>
</div>
<h4 class="mb-2">Access pending</h4>
<p class="text-muted mb-4">
Your account <strong>{{ app.user.email }}</strong> has been created but does not yet have access to ServerControl.
An administrator needs to assign you the <span class="badge bg-primary">OPERATOR</span> or
<span class="badge bg-danger">ADMIN</span> role.
</p>
<hr>
<p class="text-muted small mb-3">Already been granted access? Log out and back in to refresh your session.</p>
<div class="d-flex gap-2 justify-content-center">
<a href="{{ path('app_logout') }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-repeat me-1"></i>Refresh session
</a>
<a href="{{ path('app_logout') }}" class="btn btn-outline-danger btn-sm">
<i class="bi bi-box-arrow-right me-1"></i>Logout
</a>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,70 @@
{% extends 'base.html.twig' %}
{% block title %}Mon profil - ServerControl{% endblock %}
{% block body %}
<div class="row justify-content-center">
<div class="col-md-5">
<h2 class="h4 mb-4"><i class="bi bi-person-circle me-2"></i>Mon profil</h2>
<div class="card mb-3">
<div class="card-body">
<div class="d-flex align-items-center gap-3">
<div class="bg-secondary rounded-circle d-flex align-items-center justify-content-center"
style="width:52px;height:52px;flex-shrink:0;">
<i class="bi bi-person-fill text-white fs-4"></i>
</div>
<div>
<div class="fw-bold fs-5">{{ app.user.pseudo }}</div>
<div class="text-muted small">{{ app.user.email }}</div>
<div class="mt-1">
{% for role in app.user.roles %}
{% if role != 'ROLE_USER' %}
<span class="badge bg-{{ role == 'ROLE_ADMIN' ? 'danger' : 'primary' }} small">
{{ role|replace({'ROLE_': ''}) }}
</span>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<strong><i class="bi bi-key me-2"></i>Changer le mot de passe</strong>
</div>
<div class="card-body">
{{ form_start(form) }}
<div class="mb-3">
{{ form_label(form.currentPassword, null, {label_attr: {class: 'form-label fw-semibold small'}}) }}
{{ form_widget(form.currentPassword, {attr: {class: 'form-control'}}) }}
{{ form_errors(form.currentPassword) }}
</div>
<div class="mb-3">
{{ form_label(form.newPassword.first, null, {label_attr: {class: 'form-label fw-semibold small'}}) }}
{{ form_widget(form.newPassword.first, {attr: {class: 'form-control'}}) }}
{{ form_errors(form.newPassword.first) }}
</div>
<div class="mb-4">
{{ form_label(form.newPassword.second, null, {label_attr: {class: 'form-label fw-semibold small'}}) }}
{{ form_widget(form.newPassword.second, {attr: {class: 'form-control'}}) }}
{{ form_errors(form.newPassword.second) }}
{{ form_errors(form.newPassword) }}
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-floppy me-1"></i>Enregistrer
</button>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,110 @@
{% extends 'base.html.twig' %}
{% block title %}Register - ServerControl{% endblock %}
{% block stylesheets %}
<style>
.form-section-title {
font-size: .7rem;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
color: #6c757d;
margin-bottom: .75rem;
padding-bottom: .4rem;
border-bottom: 1px solid rgba(108,117,125,.2);
}
.field-icon { color: #6c757d; }
.input-group-text { background: transparent; }
</style>
{% endblock %}
{% block body %}
<div class="row justify-content-center mt-5">
<div class="col-md-5 col-lg-4">
<div class="card">
<div class="card-header py-3 d-flex align-items-center gap-2">
<i class="bi bi-person-plus-fill fs-5 text-primary"></i>
<div>
<h5 class="mb-0">Create account</h5>
<small class="text-muted">Join ServerControl</small>
</div>
</div>
<div class="card-body p-4">
{{ form_start(form) }}
<p class="form-section-title"><i class="bi bi-person me-1"></i>Identity</p>
<div class="mb-3">
<label class="form-label fw-semibold">Email</label>
<div class="input-group">
<span class="input-group-text field-icon"><i class="bi bi-envelope"></i></span>
{{ form_widget(form.email, {attr: {class: 'form-control', placeholder: 'you@example.com'}}) }}
</div>
{{ form_errors(form.email) }}
</div>
<div class="mb-4">
<label class="form-label fw-semibold">Pseudo</label>
<div class="input-group">
<span class="input-group-text field-icon"><i class="bi bi-at"></i></span>
{{ form_widget(form.pseudo, {attr: {class: 'form-control', placeholder: 'Your display name'}}) }}
</div>
{{ form_errors(form.pseudo) }}
</div>
<p class="form-section-title"><i class="bi bi-key me-1"></i>Password</p>
<div class="mb-3">
<label class="form-label fw-semibold">Password</label>
<div class="input-group">
<span class="input-group-text field-icon"><i class="bi bi-lock"></i></span>
{{ form_widget(form.plainPassword.first, {attr: {class: 'form-control', placeholder: '••••••••', id: 'pwd1'}}) }}
<button class="btn btn-outline-secondary" type="button" onclick="togglePwd('pwd1','eye1')">
<i class="bi bi-eye" id="eye1"></i>
</button>
</div>
{{ form_errors(form.plainPassword.first) }}
</div>
<div class="mb-4">
<label class="form-label fw-semibold">Confirm password</label>
<div class="input-group">
<span class="input-group-text field-icon"><i class="bi bi-lock-fill"></i></span>
{{ form_widget(form.plainPassword.second, {attr: {class: 'form-control', placeholder: '••••••••', id: 'pwd2'}}) }}
<button class="btn btn-outline-secondary" type="button" onclick="togglePwd('pwd2','eye2')">
<i class="bi bi-eye" id="eye2"></i>
</button>
</div>
{{ form_errors(form.plainPassword.second) }}
{{ form_errors(form.plainPassword) }}
</div>
<div class="pt-2 border-top">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-person-check me-1"></i>Create account
</button>
</div>
{{ form_end(form) }}
</div>
<div class="card-footer text-center py-3 text-muted small">
Already have an account? <a href="{{ path('app_login') }}">Login</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
function togglePwd(inputId, eyeId) {
const input = document.getElementById(inputId);
const eye = document.getElementById(eyeId);
if (input.type === 'password') {
input.type = 'text';
eye.className = 'bi bi-eye-slash';
} else {
input.type = 'password';
eye.className = 'bi bi-eye';
}
}
</script>
{% endblock %}

View file

@ -0,0 +1,32 @@
{% extends 'base.html.twig' %}
{% block title %}Nouveau mot de passe - ServerControl{% endblock %}
{% block body %}
<div class="row justify-content-center mt-5">
<div class="col-md-4">
<div class="card p-4">
<h2 class="text-center mb-2"><i class="bi bi-shield-lock me-2"></i>Nouveau mot de passe</h2>
<p class="text-center mb-4" style="font-size:.85rem; color:var(--muted);">
Choisissez un nouveau mot de passe pour votre compte. Minimum 8 caracteres.
</p>
{{ form_start(form) }}
<div class="mb-3">
{{ form_label(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.first, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.plainPassword.first) }}
</div>
<div class="mb-3">
{{ form_label(form.plainPassword.second) }}
{{ form_widget(form.plainPassword.second, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(form.plainPassword.second) }}
</div>
{{ form_errors(form.plainPassword) }}
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-check-circle me-1"></i>Enregistrer le nouveau mot de passe
</button>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}