Rôles, tableau de bord et tests unitaires
This commit is contained in:
parent
f508a2fcb5
commit
1799035236
51 changed files with 4350 additions and 0 deletions
32
templates/security/forgot_password.html.twig
Normal file
32
templates/security/forgot_password.html.twig
Normal 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 %}
|
||||
61
templates/security/login.html.twig
Normal file
61
templates/security/login.html.twig
Normal 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 %}
|
||||
30
templates/security/pending.html.twig
Normal file
30
templates/security/pending.html.twig
Normal 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 %}
|
||||
70
templates/security/profile.html.twig
Normal file
70
templates/security/profile.html.twig
Normal 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 %}
|
||||
110
templates/security/register.html.twig
Normal file
110
templates/security/register.html.twig
Normal 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 %}
|
||||
32
templates/security/reset_password.html.twig
Normal file
32
templates/security/reset_password.html.twig
Normal 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 %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue