mise en place de la partie administration

This commit is contained in:
Esenjin 2024-12-30 20:17:45 +01:00
parent a24ba28630
commit ef73c36ee3
4 changed files with 1318 additions and 0 deletions

270
admin.php Normal file
View File

@ -0,0 +1,270 @@
<?php
session_start();
// Vérifier si un utilisateur est connecté
function checkAuth() {
if (!isset($_SESSION['admin_id'])) {
header('Location: admin.php?action=login');
exit;
}
}
// Se connecter à la base de données
function getDB() {
return new SQLite3('database.sqlite');
}
// Page de connexion
function showLoginForm($error = null) {
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion - ICO</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-admin.css">
</head>
<body class="admin-page">
<div class="admin-login">
<h1>Connexion</h1>
<?php if ($error): ?>
<div class="error-message"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="post" action="admin.php?action=login">
<div class="form-group">
<label for="username">Identifiant :</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="action-button">Se connecter</button>
</form>
</div>
</body>
</html>
<?php
}
// Page principale d'administration
function showAdminInterface() {
checkAuth();
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Administration - ICO</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Administration ICO</h1>
<div class="admin-actions">
<a href="index.php" target="_blank" class="action-button action-button-success">Accéder à la galerie</a>
<a href="admin.php?action=show_change_password" class="action-button">Changer le mot de passe</a>
<a href="admin.php?action=logout" class="action-button action-button-danger">Déconnexion</a>
</div>
</div>
<div class="admin-content">
<?php if (isset($_SESSION['success_message'])): ?>
<div class="message success-message"><?php echo htmlspecialchars($_SESSION['success_message']); ?></div>
<?php unset($_SESSION['success_message']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<div class="admin-menu">
<a href="arbre.php" class="admin-menu-item">
<div class="menu-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
<path d="M9 13h6"></path>
<path d="M12 10v6"></path>
</svg>
</div>
<div class="menu-content">
<h2>Gestion des albums</h2>
<p>Organisez vos albums et gérez l'arborescence de votre galerie photo.</p>
</div>
</a>
</div>
</div>
</body>
</html>
<?php
}
// Page de changement de mot de passe
function showChangePasswordForm() {
checkAuth();
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changer le mot de passe - ICO</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Changer le mot de passe</h1>
<a href="admin.php" class="action-button action-button-secondary">Retour</a>
</div>
<div class="admin-content">
<?php if (isset($_SESSION['error_message'])): ?>
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<form method="post" action="admin.php?action=change_password" class="form-container">
<div class="form-group">
<label for="current_password">Mot de passe actuel :</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">Nouveau mot de passe :</label>
<input type="password" id="new_password" name="new_password" required minlength="8">
</div>
<div class="form-group">
<label for="confirm_password">Confirmer le mot de passe :</label>
<input type="password" id="confirm_password" name="confirm_password" required minlength="8">
</div>
<div class="form-actions">
<button type="submit" class="action-button">Changer le mot de passe</button>
</div>
</form>
</div>
</body>
</html>
<?php
}
// Traiter la connexion
function handleLogin() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$db = getDB();
$stmt = $db->prepare('SELECT id, password_hash FROM admins WHERE username = :username');
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
$result = $stmt->execute();
if ($user = $result->fetchArray()) {
if (password_verify($password, $user['password_hash'])) {
$_SESSION['admin_id'] = $user['id'];
header('Location: admin.php');
exit;
}
}
showLoginForm('Identifiants incorrects');
return;
}
showLoginForm();
}
// Gérer le changement de mot de passe
function handlePasswordChange() {
checkAuth();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: admin.php');
return;
}
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// Vérifier que les nouveaux mots de passe correspondent
if ($newPassword !== $confirmPassword) {
$_SESSION['error_message'] = "Les nouveaux mots de passe ne correspondent pas.";
header('Location: admin.php?action=show_change_password');
return;
}
// Vérifier que le nouveau mot de passe est assez long
if (strlen($newPassword) < 8) {
$_SESSION['error_message'] = "Le nouveau mot de passe doit faire au moins 8 caractères.";
header('Location: admin.php?action=show_change_password');
return;
}
$db = getDB();
// Vérifier l'ancien mot de passe
$stmt = $db->prepare('SELECT password_hash FROM admins WHERE id = :id');
$stmt->bindValue(':id', $_SESSION['admin_id'], SQLITE3_INTEGER);
$result = $stmt->execute();
$user = $result->fetchArray();
if (!password_verify($currentPassword, $user['password_hash'])) {
$_SESSION['error_message'] = "Le mot de passe actuel est incorrect.";
header('Location: admin.php?action=show_change_password');
return;
}
// Mettre à jour le mot de passe
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $db->prepare('UPDATE admins SET password_hash = :hash WHERE id = :id');
$stmt->bindValue(':hash', $newHash, SQLITE3_TEXT);
$stmt->bindValue(':id', $_SESSION['admin_id'], SQLITE3_INTEGER);
if ($stmt->execute()) {
$_SESSION['success_message'] = "Mot de passe changé avec succès.";
header('Location: admin.php');
} else {
$_SESSION['error_message'] = "Une erreur est survenue lors du changement de mot de passe.";
header('Location: admin.php?action=show_change_password');
}
return;
}
// Gérer la déconnexion
function handleLogout() {
session_destroy();
header('Location: admin.php');
exit;
}
// Router principal
$action = $_GET['action'] ?? 'home';
switch ($action) {
case 'login':
handleLogin();
break;
case 'logout':
handleLogout();
break;
case 'show_change_password':
showChangePasswordForm();
break;
case 'change_password':
handlePasswordChange();
break;
default:
showAdminInterface();
break;
}
?>

247
arbre-img.php Normal file
View File

@ -0,0 +1,247 @@
<?php
require_once 'fonctions.php';
// Vérifier l'authentification
session_start();
if (!isset($_SESSION['admin_id'])) {
header('Location: admin.php?action=login');
exit;
}
// Récupérer le chemin courant
$currentPath = isset($_GET['path']) ? $_GET['path'] : './liste_albums';
$currentPath = realpath($currentPath);
// Vérification de sécurité
if (!isSecurePath($currentPath)) {
header('Location: arbre.php');
exit;
}
// Gérer l'upload des images
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'upload':
$uploadedFiles = $_FILES['images'] ?? [];
$successCount = 0;
$errors = [];
// Gérer les uploads multiples
for ($i = 0; $i < count($uploadedFiles['name']); $i++) {
if ($uploadedFiles['error'][$i] === UPLOAD_ERR_OK) {
$tmpName = $uploadedFiles['tmp_name'][$i];
$fileName = sanitizeFilename($uploadedFiles['name'][$i]);
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
// Vérifier l'extension
if (in_array($extension, ALLOWED_EXTENSIONS)) {
$destination = $currentPath . '/' . $fileName;
// Vérifier si le fichier existe déjà
if (file_exists($destination)) {
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
$counter = 1;
while (file_exists($destination)) {
$fileName = $baseName . '_' . $counter . '.' . $extension;
$destination = $currentPath . '/' . $fileName;
$counter++;
}
}
if (move_uploaded_file($tmpName, $destination)) {
$successCount++;
} else {
$errors[] = "Erreur lors du déplacement de $fileName";
}
} else {
$errors[] = "Extension non autorisée pour $fileName";
}
}
}
if ($successCount > 0) {
$_SESSION['success_message'] = "$successCount image(s) téléversée(s) avec succès.";
}
if (!empty($errors)) {
$_SESSION['error_message'] = implode("\n", $errors);
}
break;
case 'delete':
$images = $_POST['images'] ?? [];
$deleteCount = 0;
foreach ($images as $image) {
$imagePath = $currentPath . '/' . basename($image);
if (isSecurePath($imagePath) && file_exists($imagePath)) {
if (unlink($imagePath)) {
$deleteCount++;
}
}
}
if ($deleteCount > 0) {
$_SESSION['success_message'] = "$deleteCount image(s) supprimée(s).";
}
break;
}
}
header('Location: arbre-img.php?path=' . urlencode($currentPath));
exit;
}
// Récupérer les images du dossier courant
$images = [];
$tempImages = [];
foreach (new DirectoryIterator($currentPath) as $file) {
if ($file->isDot()) continue;
if ($file->isFile()) {
$extension = strtolower($file->getExtension());
if (in_array($extension, ALLOWED_EXTENSIONS)) {
$tempImages[] = [
'name' => $file->getFilename(),
'time' => $file->getCTime()
];
}
}
}
// Trier par date de création décroissante
usort($tempImages, function($a, $b) {
return $b['time'] - $a['time'];
});
// Extraire uniquement les noms de fichiers
$images = array_map(function($img) {
return $img['name'];
}, $tempImages);
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestion des images - ICO</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Gestion des images</h1>
<div class="admin-actions">
<button onclick="document.getElementById('imageUploadForm').click()" class="action-button action-button-success">
Ajouter des images
</button>
<button onclick="deleteSelected()" id="deleteSelectedBtn" style="display: none;" class="action-button action-button-danger">
Supprimer la sélection
</button>
<a href="arbre.php?path=<?php echo urlencode($currentPath); ?>" class="action-button action-button-secondary">
Retour
</a>
</div>
</div>
<div class="admin-content">
<?php if (isset($_SESSION['success_message'])): ?>
<div class="message success-message"><?php echo nl2br(htmlspecialchars($_SESSION['success_message'])); ?></div>
<?php unset($_SESSION['success_message']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="message error-message"><?php echo nl2br(htmlspecialchars($_SESSION['error_message'])); ?></div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<div class="upload-zone" id="dropZone">
<p>Glissez-déposez vos images ici ou cliquez sur "Ajouter des images"</p>
<form method="post" enctype="multipart/form-data" id="uploadForm">
<input type="hidden" name="action" value="upload">
<input type="file" name="images[]" id="imageUploadForm" multiple accept=".jpg,.jpeg,.png,.gif">
</form>
</div>
<form method="post" id="deleteForm">
<input type="hidden" name="action" value="delete">
<div class="images-grid">
<?php foreach($images as $image):
$imageUrl = getBaseUrl() . '/liste_albums/' . substr($currentPath, strpos($currentPath, '/liste_albums/') + strlen('/liste_albums/')) . '/' . $image;
?>
<div class="image-item">
<input type="checkbox" name="images[]" value="<?php echo htmlspecialchars($image); ?>"
class="image-checkbox" onchange="updateDeleteButton()">
<img src="<?php echo htmlspecialchars($imageUrl); ?>"
alt="<?php echo htmlspecialchars($image); ?>" loading="lazy">
<div class="image-actions">
<button type="button" onclick="deleteImage('<?php echo htmlspecialchars($image); ?>')"
class="tree-button tree-button-danger">🗑️</button>
</div>
</div>
<?php endforeach; ?>
</div>
</form>
</div>
<script>
// Gestion du drag & drop
const dropZone = document.getElementById('dropZone');
const uploadForm = document.getElementById('uploadForm');
const imageUploadForm = document.getElementById('imageUploadForm');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const files = e.dataTransfer.files;
if (files.length > 0) {
// Créer un objet DataTransfer
const dataTransfer = new DataTransfer();
// Ajouter les fichiers
for (let file of files) {
dataTransfer.items.add(file);
}
// Mettre à jour l'input
imageUploadForm.files = dataTransfer.files;
// Soumettre le formulaire
uploadForm.submit();
}
});
// Gestion de la suppression
function deleteImage(imageName) {
if (confirm('Êtes-vous sûr de vouloir supprimer cette image ?')) {
const form = document.getElementById('deleteForm');
form.innerHTML = `
<input type="hidden" name="action" value="delete">
<input type="hidden" name="images[]" value="${imageName}">
`;
form.submit();
}
}
function updateDeleteButton() {
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
const deleteBtn = document.getElementById('deleteSelectedBtn');
deleteBtn.style.display = checkboxes.length > 0 ? 'inline-flex' : 'none';
}
function deleteSelected() {
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
if (checkboxes.length > 0 && confirm('Êtes-vous sûr de vouloir supprimer les images sélectionnées ?')) {
document.getElementById('deleteForm').submit();
}
}
</script>
</body>
</html>

242
arbre.php Normal file
View File

@ -0,0 +1,242 @@
<?php
require_once 'fonctions.php';
session_start();
if (!isset($_SESSION['admin_id'])) {
header('Location: admin.php?action=login');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$path = $_POST['path'] ?? '';
$newName = $_POST['new_name'] ?? '';
$description = $_POST['description'] ?? '';
switch ($action) {
case 'create_folder':
if ($path && $newName) {
$newPath = $path . '/' . sanitizeFilename($newName);
if (!file_exists($newPath)) {
mkdir($newPath, 0755, true);
$infoContent = $newName . "\n" . $description;
file_put_contents($newPath . '/infos.txt', $infoContent);
$_SESSION['success_message'] = "Dossier créé avec succès.";
} else {
$_SESSION['error_message'] = "Ce dossier existe déjà.";
}
}
break;
case 'edit_folder':
if ($path && isSecurePath($path)) {
$infoContent = $newName . "\n" . $description;
$infoPath = $path . '/infos.txt';
if (file_put_contents($infoPath, $infoContent) !== false) {
$_SESSION['success_message'] = "Dossier modifié avec succès.";
} else {
$_SESSION['error_message'] = "Erreur lors de la modification du dossier.";
}
}
break;
case 'delete_folder':
if ($path && isSecurePath($path)) {
function rrmdir($dir) {
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir . "/" . $object)) {
rrmdir($dir . "/" . $object);
} else {
unlink($dir . "/" . $object);
}
}
}
rmdir($dir);
}
}
rrmdir($path);
$_SESSION['success_message'] = "Dossier supprimé avec succès.";
}
break;
}
header('Location: arbre.php');
exit;
}
$currentPath = isset($_GET['path']) ? $_GET['path'] : './liste_albums';
$currentPath = realpath($currentPath);
if (!isSecurePath($currentPath)) {
header('Location: arbre.php');
exit;
}
function generateTree($path, $currentPath) {
if (!is_dir($path)) return '';
$output = '<ul class="tree-list">';
foreach (new DirectoryIterator($path) as $item) {
if ($item->isDot()) continue;
if ($item->isDir()) {
$fullPath = $item->getPathname();
$info = getAlbumInfo($fullPath);
$isCurrentPath = realpath($fullPath) === $currentPath;
$hasSubfolders = hasSubfolders($fullPath);
$output .= '<li class="tree-item' . ($isCurrentPath ? ' active' : '') . '">';
$output .= '<div class="tree-item-content">';
$output .= '<a href="?path=' . urlencode($fullPath) . '" class="tree-link">';
$output .= '<span class="folder-icon">📁</span> ' . htmlspecialchars($info['title']);
$output .= '</a>';
$output .= '<div class="tree-actions">';
if (!$hasSubfolders) {
$output .= '<a href="arbre-img.php?path=' . urlencode($fullPath) . '" class="tree-button" style="text-decoration: none">🖼️</a>';
}
$output .= '<button onclick="editFolder(\'' . htmlspecialchars($fullPath) . '\', \'' . htmlspecialchars($info['title']) . '\', \'' . htmlspecialchars($info['description']) . '\')" class="tree-button">✏️</button>';
$output .= '<button onclick="createSubfolder(\'' . htmlspecialchars($fullPath) . '\')" class="tree-button"></button>';
if ($fullPath !== './liste_albums') {
$output .= '<button onclick="deleteFolder(\'' . htmlspecialchars($fullPath) . '\')" class="tree-button tree-button-danger">🗑️</button>';
}
$output .= '</div></div>';
$output .= generateTree($fullPath, $currentPath);
$output .= '</li>';
}
}
$output .= '</ul>';
return $output;
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arborescence - ICO</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Gestion de l'arborescence</h1>
<div class="admin-actions">
<button onclick="createSubfolder('./liste_albums')" class="action-button">Nouveau dossier</button>
<a href="admin.php" class="action-button action-button-secondary">Retour</a>
</div>
</div>
<div class="admin-content">
<?php if (isset($_SESSION['success_message'])): ?>
<div class="message success-message"><?php echo htmlspecialchars($_SESSION['success_message']); ?></div>
<?php unset($_SESSION['success_message']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<div class="tree-container">
<?php echo generateTree('./liste_albums', $currentPath); ?>
</div>
</div>
<!-- Modal de création de dossier -->
<div id="createFolderModal" class="modal">
<div class="modal-content">
<h2>Créer un nouveau dossier</h2>
<form method="post" action="arbre.php">
<input type="hidden" name="action" value="create_folder">
<input type="hidden" name="path" id="parentPath">
<div class="form-group">
<label for="new_name">Nom du dossier :</label>
<input type="text" id="new_name" name="new_name" required>
</div>
<div class="form-group">
<label for="description">Description :</label>
<textarea id="description" name="description" rows="4" class="form-textarea"></textarea>
</div>
<div class="form-actions">
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
<button type="submit" class="action-button">Créer</button>
</div>
</form>
</div>
</div>
<!-- Modal d'édition de dossier -->
<div id="editFolderModal" class="modal">
<div class="modal-content">
<h2>Modifier le dossier</h2>
<form method="post" action="arbre.php">
<input type="hidden" name="action" value="edit_folder">
<input type="hidden" name="path" id="editPath">
<div class="form-group">
<label for="edit_name">Nom du dossier :</label>
<input type="text" id="edit_name" name="new_name" required>
</div>
<div class="form-group">
<label for="edit_description">Description :</label>
<textarea id="edit_description" name="description" rows="4" class="form-textarea"></textarea>
</div>
<div class="form-actions">
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
<button type="submit" class="action-button">Enregistrer</button>
</div>
</form>
</div>
</div>
<!-- Modal de confirmation de suppression -->
<div id="deleteFolderModal" class="modal">
<div class="modal-content">
<h2>Confirmer la suppression</h2>
<p>Êtes-vous sûr de vouloir supprimer ce dossier et tout son contenu ?</p>
<form method="post" action="arbre.php">
<input type="hidden" name="action" value="delete_folder">
<input type="hidden" name="path" id="deletePath">
<div class="form-actions">
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
<button type="submit" class="action-button action-button-danger">Supprimer</button>
</div>
</form>
</div>
</div>
<script>
function createSubfolder(path) {
document.getElementById('parentPath').value = path;
document.getElementById('createFolderModal').style.display = 'block';
}
function editFolder(path, title, description) {
document.getElementById('editPath').value = path;
document.getElementById('edit_name').value = title;
document.getElementById('edit_description').value = description;
document.getElementById('editFolderModal').style.display = 'block';
}
function deleteFolder(path) {
document.getElementById('deletePath').value = path;
document.getElementById('deleteFolderModal').style.display = 'block';
}
function closeModal() {
document.getElementById('createFolderModal').style.display = 'none';
document.getElementById('editFolderModal').style.display = 'none';
document.getElementById('deleteFolderModal').style.display = 'none';
}
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
closeModal();
}
}
</script>
</body>
</html>

559
styles-admin.css Normal file
View File

@ -0,0 +1,559 @@
/* Reset et styles de base pour l'admin */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background-color: #121212;
color: #ffffff;
min-height: 100vh;
overflow-x: hidden;
}
/* Page d'administration */
.admin-page {
background-color: #121212;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
}
/* Page de connexion */
.admin-login {
width: 90%;
max-width: 400px;
margin: auto;
background-color: #1e1e1e;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
}
.admin-login h1 {
font-size: 2rem;
margin-bottom: 2rem;
text-align: center;
}
/* En-tête de l'administration */
.admin-header {
width: 100%;
max-width: 1200px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #2a2a2a;
}
.admin-header h1 {
font-size: 2rem;
margin: 0;
}
/* Zone de contenu principale */
.admin-content {
width: 100%;
max-width: 1200px;
background-color: #1e1e1e;
padding: 2rem;
border-radius: 1rem;
min-height: calc(100vh - 12rem);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
}
/* Boutons d'action */
.admin-actions {
display: flex;
gap: 1rem;
}
.action-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.8rem 1.5rem;
background-color: #2196f3;
color: white;
border: none;
border-radius: 2rem;
cursor: pointer;
font-size: 1rem;
text-decoration: none;
transition: all 0.3s ease;
}
.action-button:hover {
background-color: #1976d2;
transform: translateY(-2px);
}
.action-button-success {
background-color: #28a745;
}
.action-button-success:hover {
background-color: #218838;
}
.action-button-danger {
background-color: #dc3545;
}
.action-button-danger:hover {
background-color: #c82333;
}
.action-button-secondary {
background-color: #6c757d;
}
.action-button-secondary:hover {
background-color: #5a6268;
}
/* Menu administration */
.admin-menu {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 2rem;
padding: 1rem;
}
.admin-menu-item {
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
background: #1e1e1e;
border: 2px solid #333;
border-radius: 1.5rem;
text-decoration: none;
color: white;
padding: 2rem;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.admin-menu-item:hover {
transform: translateY(-5px);
border-color: #2196f3;
box-shadow: 0 5px 20px rgba(33, 150, 243, 0.15);
}
.menu-icon {
margin-bottom: 1rem;
}
.menu-icon svg {
width: 64px;
height: 64px;
stroke: #fff;
transition: all 0.3s ease;
}
.admin-menu-item:hover .menu-icon svg {
stroke: #2196f3;
transform: scale(1.1);
}
.menu-content h2 {
font-size: 1.2rem;
font-weight: 500;
margin: 0;
}
.menu-content p {
position: absolute;
left: 0;
right: 0;
bottom: -100%;
padding: 1rem;
background: rgba(33, 150, 243, 0.95);
color: white;
font-size: 0.9rem;
transition: 0.3s ease;
}
.admin-menu-item:hover .menu-content p {
bottom: 0;
}
/* Formulaires */
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #e0e0e0;
}
.form-group input {
width: 100%;
padding: 0.8rem;
border: none;
border-radius: 0.5rem;
background-color: #2a2a2a;
color: white;
font-size: 1rem;
}
.form-group input:focus {
outline: 2px solid #2196f3;
background-color: #333;
}
.form-container {
max-width: 500px;
margin: 0 auto;
}
.form-actions {
margin-top: 2rem;
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.form-textarea {
width: 100%;
padding: 0.8rem;
border: none;
border-radius: 0.5rem;
background-color: #2a2a2a;
color: white;
font-size: 1rem;
min-height: 100px;
resize: vertical;
}
.form-textarea:focus {
outline: 2px solid #2196f3;
background-color: #333;
}
/* Messages */
.message {
padding: 1rem;
border-radius: 0.5rem;
margin-bottom: 1.5rem;
text-align: center;
}
.success-message {
background-color: #28a745;
color: white;
}
.error-message {
background-color: #dc3545;
color: white;
}
/* Styles pour l'arborescence */
.tree-container {
padding: 1rem;
background: #2a2a2a;
border-radius: 0.5rem;
}
.tree-list {
list-style: none;
padding-left: 1.5rem;
}
.tree-item {
margin: 0.5rem 0;
}
.tree-item-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem;
border-radius: 0.25rem;
background: #1e1e1e;
}
.tree-item.active > .tree-item-content {
background: #2196f3;
}
.tree-link {
color: white;
text-decoration: none;
display: flex;
align-items: center;
gap: 0.5rem;
}
.folder-icon {
font-size: 1.2rem;
}
.tree-actions {
display: flex;
gap: 0.5rem;
}
.tree-button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0.25rem;
border-radius: 0.25rem;
font-size: 1rem;
}
.tree-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.tree-button-danger:hover {
background: rgba(220, 53, 69, 0.2);
}
.image-actions .tree-button {
background-color: rgba(0, 0, 0, 0.7);
padding: 0.5rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.image-actions .tree-button:hover {
background-color: rgba(220, 53, 69, 0.9);
transform: scale(1.1);
}
.tree-button-danger {
background-color: rgba(220, 53, 69, 0.7);
}
/* Styles pour le toggle des sous-dossiers */
.tree-item-content {
position: relative;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 0.25rem;
background: #1e1e1e;
margin: 0.25rem 0;
}
.toggle-btn {
background: none;
border: none;
padding: 0.25rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
transition: transform 0.3s ease;
}
.toggle-btn.active {
transform: rotate(90deg);
}
.toggle-spacer {
width: 24px;
}
.toggle-icon {
width: 16px;
height: 16px;
transition: transform 0.3s ease;
}
.subtree {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
opacity: 0;
}
.subtree.active {
max-height: 1000px;
opacity: 1;
transition: max-height 0.3s ease-in, opacity 0.3s ease-in;
}
/* Ajuster les marges et le padding pour une meilleure hiérarchie visuelle */
.tree-list {
list-style: none;
padding-left: 1.5rem;
margin: 0;
}
.tree-list:first-child {
padding-left: 0;
}
/* Animation pour le hover des boutons */
.toggle-btn:hover {
color: #2196f3;
background: rgba(33, 150, 243, 0.1);
border-radius: 4px;
}
/* Styles pour les modales */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #1e1e1e;
padding: 2rem;
border-radius: 1rem;
min-width: 300px;
max-width: 500px;
width: 90%;
}
.modal h2 {
margin-bottom: 1.5rem;
}
/* Styles pour les listes d'images */
.images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
padding: 1rem;
}
.image-item {
position: relative;
aspect-ratio: 1;
border-radius: 0.5rem;
overflow: hidden;
background-color: #2a2a2a;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.image-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
z-index: 1;
}
.image-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.image-item:hover img {
transform: scale(1.1);
}
.image-checkbox {
position: absolute;
top: 0.5rem;
left: 0.5rem;
z-index: 2;
width: 24px;
height: 24px;
cursor: pointer;
accent-color: #2196f3;
}
.image-checkbox:hover {
transform: scale(1.1);
}
.image-actions {
position: absolute;
top: 0.5rem;
right: 0.5rem;
z-index: 2;
}
.upload-zone {
border: 2px dashed #2196f3;
border-radius: 1rem;
padding: 2rem;
text-align: center;
margin: 1rem;
background-color: rgba(33, 150, 243, 0.1);
}
.upload-zone.drag-over {
background-color: rgba(33, 150, 243, 0.2);
border-color: #1976d2;
}
#imageUploadForm {
display: none;
}
/* Media Queries */
@media (max-width: 768px) {
.admin-page {
padding: 1rem;
}
.admin-header {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.admin-actions {
flex-direction: column;
width: 100%;
}
.action-button {
width: 100%;
justify-content: center;
}
.admin-menu {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
}
/* Accessibilité */
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 2px solid #2196f3;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}