292 lines
14 KiB
PHP
292 lines
14 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
require_once __DIR__ . '/../includes/auth.php';
|
|
require_once __DIR__ . '/../includes/layout.php';
|
|
|
|
admin_check();
|
|
|
|
$error = '';
|
|
|
|
// Création d'un compte
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
|
|
if ($_POST['action'] === 'create') {
|
|
$username = preg_replace('/[^a-z0-9._-]/', '', strtolower(trim($_POST['username'] ?? '')));
|
|
$password = trim($_POST['password'] ?? '');
|
|
$display = trim($_POST['display_name'] ?? '');
|
|
$domain = cfg('domain');
|
|
|
|
if (strlen($username) < 2) {
|
|
$error = 'Nom d\'utilisateur invalide (min 2 caractères, a-z0-9._-)';
|
|
} elseif (strlen($password) < 6) {
|
|
$error = 'Mot de passe trop court (min 6 caractères).';
|
|
} else {
|
|
$email = "{$username}@{$domain}";
|
|
$smbPath = "/srv/copymail/{$username}";
|
|
|
|
$st = db()->prepare('SELECT id FROM accounts WHERE username = ? OR email = ?');
|
|
$st->execute([$username, $email]);
|
|
if ($st->fetch()) {
|
|
$error = "Le compte « {$username} » existe déjà.";
|
|
} else {
|
|
$webhook = trim($_POST['webhook_url'] ?? '');
|
|
db()->prepare(
|
|
'INSERT INTO accounts (username, email, display_name, smb_path, webhook_url) VALUES (?, ?, ?, ?, ?)'
|
|
)->execute([$username, $email, $display, $smbPath, $webhook ?: null]);
|
|
|
|
// Crée le dossier, l'utilisateur Dovecot et le share Samba
|
|
$cmd = sprintf(
|
|
'/opt/copymail/scripts/create_account.sh %s %s %s 2>&1',
|
|
escapeshellarg($username),
|
|
escapeshellarg($password),
|
|
escapeshellarg($domain)
|
|
);
|
|
shell_exec($cmd);
|
|
|
|
flash('success', "Compte <strong>{$email}</strong> créé. Share SMB : <code>\\\\serveur\\{$username}</code>");
|
|
header('Location: /admin/accounts.php');
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($_POST['action'] === 'delete') {
|
|
$id = (int)$_POST['account_id'];
|
|
$st = db()->prepare('SELECT username FROM accounts WHERE id = ?');
|
|
$st->execute([$id]);
|
|
$row = $st->fetch();
|
|
if ($row) {
|
|
$cmd = sprintf('/opt/copymail/scripts/delete_account.sh %s 2>&1', escapeshellarg($row['username']));
|
|
shell_exec($cmd);
|
|
db()->prepare('DELETE FROM accounts WHERE id = ?')->execute([$id]);
|
|
flash('success', "Compte « {$row['username']} » supprimé.");
|
|
}
|
|
header('Location: /admin/accounts.php');
|
|
exit;
|
|
}
|
|
|
|
if ($_POST['action'] === 'toggle') {
|
|
$id = (int)$_POST['account_id'];
|
|
db()->prepare('UPDATE accounts SET active = 1 - active WHERE id = ?')->execute([$id]);
|
|
header('Location: /admin/accounts.php');
|
|
exit;
|
|
}
|
|
|
|
if ($_POST['action'] === 'edit') {
|
|
$id = (int)$_POST['account_id'];
|
|
$display = trim($_POST['display_name'] ?? '');
|
|
$webhook = trim($_POST['webhook_url'] ?? '');
|
|
$newPassword = trim($_POST['new_password'] ?? '');
|
|
|
|
db()->prepare('UPDATE accounts SET display_name = ?, webhook_url = ? WHERE id = ?')
|
|
->execute([$display ?: null, $webhook ?: null, $id]);
|
|
|
|
if (strlen($newPassword) >= 6) {
|
|
$st = db()->prepare('SELECT username FROM accounts WHERE id = ?');
|
|
$st->execute([$id]);
|
|
$row = $st->fetch();
|
|
if ($row) {
|
|
$domain = cfg('domain');
|
|
$hash = shell_exec(sprintf('doveadm pw -s SHA512-CRYPT -p %s', escapeshellarg($newPassword)));
|
|
$hash = trim($hash ?? '');
|
|
if ($hash) {
|
|
shell_exec(sprintf(
|
|
"sed -i 's|^%s@%s:.*|%s@%s:%s:::|' /etc/dovecot/users",
|
|
$row['username'], $domain,
|
|
$row['username'], $domain, $hash
|
|
));
|
|
$smbUser = $row['username'];
|
|
shell_exec(sprintf('(echo %s; echo %s) | smbpasswd -s %s 2>/dev/null',
|
|
escapeshellarg($newPassword), escapeshellarg($newPassword), escapeshellarg($smbUser)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
flash('success', "Compte mis à jour.");
|
|
header('Location: /admin/accounts.php');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$accounts = db()->query(
|
|
'SELECT a.*, COUNT(sf.id) as mail_count FROM accounts a
|
|
LEFT JOIN source_file sf ON sf.account_id = a.id
|
|
GROUP BY a.id ORDER BY a.created_at DESC'
|
|
)->fetchAll();
|
|
|
|
$domain = cfg('domain');
|
|
|
|
html_head('Comptes');
|
|
html_navbar('accounts');
|
|
?>
|
|
<div class="container-fluid">
|
|
<?php flash_render(); ?>
|
|
|
|
<div class="row g-4">
|
|
|
|
<!-- Formulaire création -->
|
|
<div class="col-lg-4">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header fw-semibold"><i class="bi bi-person-plus-fill me-2"></i>Nouveau compte</div>
|
|
<div class="card-body">
|
|
<?php if ($error): ?>
|
|
<div class="alert alert-danger py-2 small"><?= htmlspecialchars($error) ?></div>
|
|
<?php endif; ?>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="create">
|
|
<div class="mb-3">
|
|
<label class="form-label">Nom d'utilisateur</label>
|
|
<div class="input-group">
|
|
<input type="text" name="username" class="form-control" placeholder="copycaisse" pattern="[a-zA-Z0-9._-]+" required>
|
|
<span class="input-group-text text-muted small">@<?= htmlspecialchars($domain) ?></span>
|
|
</div>
|
|
<div class="form-text">Lettres, chiffres, . _ -</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Mot de passe IMAP/SMB</label>
|
|
<input type="password" name="password" class="form-control" required minlength="6">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Nom affiché <span class="text-muted">(optionnel)</span></label>
|
|
<input type="text" name="display_name" class="form-control" placeholder="Copy Caisse">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Webhook URL <span class="text-muted">(optionnel)</span></label>
|
|
<input type="url" name="webhook_url" class="form-control" placeholder="https://…/webhook">
|
|
<div class="form-text">POST JSON à chaque réception de mail.</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="bi bi-plus-circle me-1"></i>Créer le compte
|
|
</button>
|
|
</form>
|
|
<hr class="my-3">
|
|
<div class="small text-muted">
|
|
<i class="bi bi-info-circle me-1"></i>La création génère automatiquement :
|
|
<ul class="mt-1 mb-0">
|
|
<li>Boîte mail <code>username@<?= htmlspecialchars($domain) ?></code></li>
|
|
<li>Dossier <code>/srv/copymail/username/</code></li>
|
|
<li>Share SMB <code>\\serveur\username</code></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Liste des comptes -->
|
|
<div class="col-lg-8">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-people-fill me-2"></i>Comptes existants</span>
|
|
<span class="badge bg-secondary"><?= count($accounts) ?></span>
|
|
</div>
|
|
<?php if (empty($accounts)): ?>
|
|
<div class="card-body text-muted">Aucun compte créé.</div>
|
|
<?php else: ?>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Compte</th>
|
|
<th>Email</th>
|
|
<th>Share SMB</th>
|
|
<th class="text-center">Mails</th>
|
|
<th class="text-center">Statut</th>
|
|
<th class="text-center">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($accounts as $acc): ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?= htmlspecialchars($acc['username']) ?></strong>
|
|
<?php if ($acc['display_name']): ?>
|
|
<br><small class="text-muted"><?= htmlspecialchars($acc['display_name']) ?></small>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="small"><?= htmlspecialchars($acc['email']) ?></td>
|
|
<td class="small font-monospace">\\serveur\<?= htmlspecialchars($acc['username']) ?></td>
|
|
<td class="text-center">
|
|
<span class="badge bg-light text-dark"><?= $acc['mail_count'] ?></span>
|
|
</td>
|
|
<td class="text-center">
|
|
<form method="post" class="d-inline">
|
|
<input type="hidden" name="action" value="toggle">
|
|
<input type="hidden" name="account_id" value="<?= $acc['id'] ?>">
|
|
<button class="badge border-0 <?= $acc['active'] ? 'bg-success' : 'bg-secondary' ?>" title="Cliquer pour basculer">
|
|
<?= $acc['active'] ? 'actif' : 'inactif' ?>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
<td class="text-center">
|
|
<div class="d-flex gap-1 justify-content-center">
|
|
<button class="btn btn-sm btn-outline-secondary" title="Modifier"
|
|
onclick="openEdit(<?= $acc['id'] ?>, '<?= htmlspecialchars(addslashes($acc['display_name'] ?? ''), ENT_QUOTES) ?>', '<?= htmlspecialchars(addslashes($acc['webhook_url'] ?? ''), ENT_QUOTES) ?>', '<?= htmlspecialchars($acc['username']) ?>')">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<form method="post" onsubmit="return confirm('Supprimer le compte <?= htmlspecialchars($acc['username']) ?> et tous ses fichiers ?')">
|
|
<input type="hidden" name="action" value="delete">
|
|
<input type="hidden" name="account_id" value="<?= $acc['id'] ?>">
|
|
<button class="btn btn-sm btn-outline-danger" title="Supprimer">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Modal édition -->
|
|
<div class="modal fade" id="editModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="edit">
|
|
<input type="hidden" name="account_id" id="edit-id">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="bi bi-pencil me-2"></i>Modifier le compte <strong id="edit-username"></strong></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Nom affiché</label>
|
|
<input type="text" name="display_name" id="edit-display" class="form-control" placeholder="Nom affiché">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Webhook URL</label>
|
|
<input type="url" name="webhook_url" id="edit-webhook" class="form-control" placeholder="https://…/webhook">
|
|
<div class="form-text">POST JSON à chaque réception. Laisser vide pour désactiver.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Nouveau mot de passe <span class="text-muted">(laisser vide pour ne pas changer)</span></label>
|
|
<input type="password" name="new_password" id="edit-password" class="form-control" minlength="6" placeholder="••••••">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annuler</button>
|
|
<button type="submit" class="btn btn-primary"><i class="bi bi-floppy-fill me-1"></i>Enregistrer</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openEdit(id, display, webhook, username) {
|
|
document.getElementById('edit-id').value = id;
|
|
document.getElementById('edit-username').textContent = username;
|
|
document.getElementById('edit-display').value = display;
|
|
document.getElementById('edit-webhook').value = webhook;
|
|
document.getElementById('edit-password').value = '';
|
|
new bootstrap.Modal(document.getElementById('editModal')).show();
|
|
}
|
|
</script>
|
|
<?php html_foot(); ?>
|