mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
Add Paramètres page: consolidate maintenance + account settings
This commit is contained in:
37
SPECS.md
Normal file
37
SPECS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
- l'ordre des TFE sur la page d'accueil ; est-ce que ce serait possible de les faire dérouler par année – avec les plus récents tout en haut –, mais en rendant l'ordre de chaque année aléatoire
|
||||
|
||||
- On a les pdf et notes d'intention des dernières années, est-ce que vous voulez déjà y avoir accès ? Est-ce qu'il y a une nomenclature particulière qui vous fait plaisir ?
|
||||
|
||||
## admin
|
||||
|
||||
- Il a été décidé de – pour l’instant – ne pas rendre les TFE visibles vers l’extérieur.
|
||||
|
||||
- option d’ouverture “interne” – qui sera à priori le défaut appliqué sur une majorité des TFE –
|
||||
|
||||
pas disponnible:
|
||||
|
||||
- le pdf ainsi que la note d’intention ne soient que disponibles quand les personnes se trouvent physiquement à l’erg (via adresse IP) ou via login (à voir ce qui est le plus simple à intégrer techniquement).
|
||||
|
||||
## le formulaire de dépôt
|
||||
|
||||
- On demandera aux étudiant·es de préparer une image au bon format pour le dépôt du TFE. Est-ce que vous pouvez nous donner la taille qu'il faudrait ?
|
||||
|
||||
- la page de formulaire:celle que les étudiant·es doivent remplir lors du dépôt du TFE, ajouter:
|
||||
- l'explication;
|
||||
- le contexte des différents choix soient visibles.
|
||||
|
||||
- Quand un·é étudiant·e dépose son TFE, il ne doit pas être publié directement. Il doit arriver dans la base de donnée, et quelqu'un viendrait juste clicker sur “publier” dans le backoffice une fois la défense orale terminée (et en fonction du retour du jury).
|
||||
|
||||
- l’option “libre” ne doit donc pas encore exister cette année.
|
||||
- créer un système de toggle pour quelles sont les options actives dans le formulaire
|
||||
- Il n’y a pour l’instant que l’option “interdit” et “interne”. L’option “libre” ne sera activée que à partir de l’année académique prochaine.
|
||||
|
||||
- la case “contact” soit accompagnée d’une case à cocher/décocher ; « Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam ». En fonction de cette réponse, le contact apparaîtrait ou non sur la page du TFE.
|
||||
|
||||
## la base de donnée
|
||||
|
||||
- rajouter une catégorie “objet” pour que, dans un futur éventuel, on puisse différencier les TFE des FRART et des thèses. Pour l’instant c’est juste un tag qui doit apparaître en back-office.
|
||||
|
||||
- quelle(s) fonte(s) est-ce que vous utilisez sur le site ?
|
||||
- est-ce que vous pouvez m’envoyez un export de la maquette du site ? (en .jpg c’est ok, c’est juste pour rafraîchir nos mémoires afin qu'on puisse produire les textes en adéquation avec ce qui existe)
|
||||
- les questions du dernier mail :)
|
||||
6
TODO.md
6
TODO.md
@@ -1,6 +1,12 @@
|
||||
# TODO
|
||||
|
||||
## Done
|
||||
- [x] Create Paramètres page consolidating maintenance toggle and account settings into two sections
|
||||
- [x] New `public/admin/parametres.php` with Maintenance + Compte administrateur sections
|
||||
- [x] Nav updated: “Compte” replaced by “Paramètres” linking to `parametres.php`
|
||||
- [x] Maintenance bar removed from `index.php`
|
||||
- [x] `actions/maintenance.php` and `actions/account.php` redirect to `parametres.php` via POST `redirect` param
|
||||
- [x] CSS added for `.admin-settings-section` and `.admin-maintenance-status`
|
||||
- [x] Fix nav logo: revert to "Xamxam", apply display font (Combined), step-2 size, letter-spacing, text-shadow via .nav-logo class
|
||||
- [x] Bump all font sizes ~10% across all CSS files (admin, system, search, main, apropos, common, tfe)
|
||||
- [x] Migrate to utopia fluid type scale (--step--2 → --step-5) and space scale (--space-3xs → --space-3xl) across all CSS files
|
||||
|
||||
@@ -23,15 +23,18 @@ $action = $_POST['action'] ?? 'change_password';
|
||||
|
||||
// ── Remove credentials ────────────────────────────────────────────────────────
|
||||
if ($action === 'remove_credentials') {
|
||||
$backUrl = $_POST['redirect'] ?? '/admin/parametres.php';
|
||||
if (!preg_match('#^/admin/#', $backUrl)) { $backUrl = '/admin/parametres.php'; }
|
||||
|
||||
if (!$hasPassword) {
|
||||
App::flash('error', 'Aucun fichier de mot de passe à supprimer.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!is_writable($credentialsFile) && !is_writable(dirname($credentialsFile))) {
|
||||
App::flash('error', 'Le fichier de configuration n\'est pas accessible en écriture.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -42,7 +45,7 @@ if ($action === 'remove_credentials') {
|
||||
exit;
|
||||
} else {
|
||||
App::flash('error', 'Impossible de supprimer le fichier de configuration.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -50,11 +53,14 @@ if ($action === 'remove_credentials') {
|
||||
// ── Change / set password ─────────────────────────────────────────────────────
|
||||
|
||||
// 1. If a password is already set, verify the current one.
|
||||
$backUrl = $_POST['redirect'] ?? '/admin/parametres.php';
|
||||
if (!preg_match('#^/admin/#', $backUrl)) { $backUrl = '/admin/parametres.php'; }
|
||||
|
||||
if ($hasPassword) {
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
if (!AdminAuth::login($currentPassword)) {
|
||||
App::flash('error', 'Mot de passe actuel incorrect.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -65,13 +71,13 @@ $confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (strlen($newPassword) < 12) {
|
||||
App::flash('error', 'Le nouveau mot de passe doit contenir au moins 12 caractères.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
App::flash('error', 'Les mots de passe ne correspondent pas.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -79,7 +85,7 @@ if ($newPassword !== $confirmPassword) {
|
||||
$hash = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
if ($hash === false) {
|
||||
App::flash('error', 'Erreur lors du hachage du mot de passe.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -100,13 +106,13 @@ $tmpFile = $credentialsFile . '.tmp.' . bin2hex(random_bytes(6));
|
||||
if (file_put_contents($tmpFile, $configContent, LOCK_EX) === false) {
|
||||
@unlink($tmpFile);
|
||||
App::flash('error', 'Impossible d\'écrire le fichier de configuration. Vérifiez les permissions sur config/.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
if (!rename($tmpFile, $credentialsFile)) {
|
||||
@unlink($tmpFile);
|
||||
App::flash('error', 'Impossible de mettre à jour le fichier de configuration.');
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -118,5 +124,5 @@ App::flash('success', $hasPassword
|
||||
? 'Mot de passe mis à jour avec succès.'
|
||||
: 'Mot de passe défini avec succès. L\'authentification PHP est maintenant active.');
|
||||
|
||||
header('Location: /admin/account.php');
|
||||
header('Location: ' . $backUrl);
|
||||
exit;
|
||||
|
||||
@@ -24,5 +24,10 @@ if ($action === 'enable_maintenance') {
|
||||
App::flash('error', "Action inconnue.");
|
||||
}
|
||||
|
||||
header('Location: /admin/');
|
||||
$redirect = isset($_POST['redirect']) ? $_POST['redirect'] : '/admin/';
|
||||
// Allow only internal admin redirects for safety
|
||||
if (!preg_match('#^/admin/#', $redirect)) {
|
||||
$redirect = '/admin/';
|
||||
}
|
||||
header('Location: ' . $redirect);
|
||||
exit();
|
||||
|
||||
@@ -76,29 +76,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
<?php include APP_ROOT . '/templates/partials/flash-messages.php'; ?>
|
||||
|
||||
<!-- Maintenance mode toggle -->
|
||||
<?php $maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag'); ?>
|
||||
<aside role="status" class="admin-maintenance-bar <?= $maintenanceOn ? 'admin-maintenance-bar--active' : '' ?>" aria-label="Statut du site">
|
||||
<?php if ($maintenanceOn): ?>
|
||||
<span>⚠ Mode maintenance <strong>activé</strong> — le site public est inaccessible.</span>
|
||||
<form method="post" action="actions/maintenance.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="disable_maintenance">
|
||||
<button type="submit" class="admin-btn admin-btn--sm">Désactiver la maintenance</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span>Site public : <strong>en ligne</strong></span>
|
||||
<form method="post" action="actions/maintenance.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="enable_maintenance">
|
||||
<button type="submit" class="admin-btn admin-btn--sm admin-btn--warning"
|
||||
onclick="return confirm('Mettre le site en maintenance ? Les visiteurs verront une page 503.')">
|
||||
Activer la maintenance
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
|
||||
<!-- Stats (always reflects full DB, independent of active filters) -->
|
||||
<dl class="admin-stats">
|
||||
<div class="admin-stat">
|
||||
|
||||
151
public/admin/parametres.php
Normal file
151
public/admin/parametres.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
$pageTitle = "Paramètres";
|
||||
|
||||
$credentialsFile = APP_ROOT . '/config/admin_credentials.php';
|
||||
$hasPassword = defined('ADMIN_PASSWORD_HASH');
|
||||
$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag');
|
||||
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
?>
|
||||
<?php $isAdmin = true; $bodyClass = 'admin-body'; require_once APP_ROOT . '/templates/head.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/header.php'; ?>
|
||||
|
||||
<main id="main-content">
|
||||
<h1>Paramètres</h1>
|
||||
|
||||
<?php include APP_ROOT . '/templates/partials/flash-messages.php'; ?>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
SECTION 1 — Maintenance
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<section class="admin-settings-section" aria-labelledby="settings-maintenance-title">
|
||||
<h2 class="admin-settings-section__title" id="settings-maintenance-title">Maintenance</h2>
|
||||
|
||||
<div class="admin-maintenance-status <?= $maintenanceOn ? 'admin-maintenance-status--active' : '' ?>">
|
||||
<?php if ($maintenanceOn): ?>
|
||||
<p class="admin-maintenance-status__msg">
|
||||
<strong>⚠ Mode maintenance activé</strong> — le site public est inaccessible.
|
||||
</p>
|
||||
<form method="post" action="actions/maintenance.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="disable_maintenance">
|
||||
<input type="hidden" name="redirect" value="/admin/parametres.php">
|
||||
<button type="submit" class="admin-btn admin-btn--sm">Désactiver la maintenance</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<p class="admin-maintenance-status__msg">
|
||||
Site public : <strong>en ligne</strong>
|
||||
</p>
|
||||
<form method="post" action="actions/maintenance.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="enable_maintenance">
|
||||
<input type="hidden" name="redirect" value="/admin/parametres.php">
|
||||
<button type="submit" class="admin-btn admin-btn--sm admin-btn--warning"
|
||||
onclick="return confirm('Mettre le site en maintenance ? Les visiteurs verront une page 503.')">
|
||||
Activer la maintenance
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
SECTION 2 — Compte administrateur
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<section class="admin-settings-section" aria-labelledby="settings-account-title">
|
||||
<h2 class="admin-settings-section__title" id="settings-account-title">Compte administrateur</h2>
|
||||
|
||||
<!-- Status info -->
|
||||
<dl class="admin-account-status">
|
||||
<div class="admin-account-status__row">
|
||||
<dt class="admin-account-status__label">Authentification PHP</dt>
|
||||
<dd><?php $badgeType = 'ok'; $badgeValue = $hasPassword; $badgeOkLabel = 'Active'; $badgeWarnLabel = 'Non configurée'; include APP_ROOT . '/templates/partials/status-badge.php'; ?></dd>
|
||||
</div>
|
||||
<div class="admin-account-status__row">
|
||||
<dt class="admin-account-status__label">Fichier de configuration</dt>
|
||||
<dd>
|
||||
<code class="admin-account-status__code">config/admin_credentials.php</code>
|
||||
<?php $badgeType = 'ok'; $badgeValue = file_exists($credentialsFile); $badgeOkLabel = 'Présent'; $badgeWarnLabel = 'Absent'; include APP_ROOT . '/templates/partials/status-badge.php'; ?>
|
||||
</dd>
|
||||
</div>
|
||||
<?php if (!$hasPassword): ?>
|
||||
<p class="admin-account-status__note">
|
||||
Aucun mot de passe PHP configuré. Le formulaire ci-dessous créera
|
||||
<code>config/admin_credentials.php</code> avec un hash bcrypt.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<!-- Password change form -->
|
||||
<h3 class="admin-section-title"><?= $hasPassword ? 'Changer le mot de passe' : 'Définir le mot de passe' ?></h3>
|
||||
|
||||
<form method="post" action="/admin/actions/account.php" class="admin-form" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="redirect" value="/admin/parametres.php">
|
||||
|
||||
<?php if ($hasPassword): ?>
|
||||
<div>
|
||||
<label for="current_password">Mot de passe actuel</label>
|
||||
<div>
|
||||
<input type="password" id="current_password"
|
||||
name="current_password" required autocomplete="current-password">
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div>
|
||||
<label for="new_password">Nouveau mot de passe</label>
|
||||
<div>
|
||||
<input type="password" id="new_password"
|
||||
name="new_password" required autocomplete="new-password"
|
||||
minlength="12">
|
||||
<small>Minimum 12 caractères.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="confirm_password">Confirmer le mot de passe</label>
|
||||
<div>
|
||||
<input type="password" id="confirm_password"
|
||||
name="confirm_password" required autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-footer">
|
||||
<button type="submit" class="admin-btn">
|
||||
<?= $hasPassword ? 'Mettre à jour le mot de passe' : 'Définir le mot de passe' ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($hasPassword): ?>
|
||||
<!-- Danger zone: remove password -->
|
||||
<h3 class="admin-section-title admin-section-title--danger">Zone de danger</h3>
|
||||
<div class="admin-danger-zone">
|
||||
<p class="admin-danger-zone__description">
|
||||
<strong>Supprimer la configuration du mot de passe PHP</strong><br>
|
||||
<small>
|
||||
Supprime <code>config/admin_credentials.php</code>. L'accès admin
|
||||
dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
|
||||
</small>
|
||||
</p>
|
||||
<form method="post" action="/admin/actions/account.php"
|
||||
onsubmit="return confirm('Supprimer le fichier de mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="remove_credentials">
|
||||
<input type="hidden" name="redirect" value="/admin/parametres.php">
|
||||
<input type="hidden" name="current_password_remove" id="current_password_remove" value="">
|
||||
<button type="submit" class="admin-btn admin-btn--danger">Supprimer le fichier</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
@@ -905,6 +905,51 @@
|
||||
margin-top: var(--space-2xs);
|
||||
}
|
||||
|
||||
/* ── Settings page sections ─────────────────────────────────────────────── */
|
||||
.admin-settings-section {
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 6px;
|
||||
padding: var(--space-m) var(--space-l);
|
||||
margin-bottom: var(--space-l);
|
||||
}
|
||||
|
||||
.admin-settings-section__title {
|
||||
font-size: var(--step-0);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 var(--space-m);
|
||||
padding-bottom: var(--space-2xs);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.admin-maintenance-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-s);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 4px;
|
||||
padding: var(--space-xs) var(--space-m);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.admin-maintenance-status--active {
|
||||
background: var(--warning-muted-bg);
|
||||
border-color: var(--warning);
|
||||
}
|
||||
|
||||
.admin-maintenance-status__msg {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-maintenance-status form {
|
||||
display: inline;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Cancel link ────────────────────────────────────────────────────────── */
|
||||
.admin-cancel-link {
|
||||
font-size: var(--step--1);
|
||||
|
||||
@@ -22,7 +22,7 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
<li><a href="/admin/pages.php" <?= in_array($_currentPage, ['pages.php', 'pages-edit.php']) ? 'aria-current="page"' : '' ?>>Pages statiques</a></li>
|
||||
<li><a href="/admin/tags.php" <?= $_currentPage === 'tags.php' ? 'aria-current="page"' : '' ?>>Mots-clés</a></li>
|
||||
<li><a href="/admin/system.php" <?= in_array($_currentPage, ['system.php', 'status.php', 'logs.php']) ? 'aria-current="page"' : '' ?>>Système</a></li>
|
||||
<li><a href="/admin/account.php" <?= $_currentPage === 'account.php' ? 'aria-current="page"' : '' ?>>Compte</a></li>
|
||||
<li><a href="/admin/parametres.php" <?= $_currentPage === 'parametres.php' ? 'aria-current="page"' : '' ?>>Paramètres</a></li>
|
||||
<?php if ($_thesisId && in_array($_currentPage, ['edit.php', 'thanks.php'])): ?>
|
||||
<li><a href="/admin/edit.php?id=<?= intval($_thesisId) ?>" <?= $_currentPage === 'edit.php' ? 'aria-current="page"' : '' ?>>Modifier</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user