This commit is contained in:
Arthur Puechberty
2026-01-18 15:08:55 +01:00
parent 6de9768e3f
commit 3f1f3ba40d
25 changed files with 1994 additions and 3 deletions
+131
View File
@@ -1035,3 +1035,134 @@ body {
object-fit: cover;
display: none;
}
/* ===== Logs System ===== */
.logs-types-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-md);
}
.log-type-card {
background: var(--bg-dark);
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--spacing-md);
transition: all 0.2s ease;
cursor: pointer;
}
.log-type-card:hover {
border-color: var(--primary-color);
background: rgba(88, 101, 242, 0.05);
}
.log-type-card.active {
border-color: var(--primary-color);
background: rgba(88, 101, 242, 0.1);
}
.log-type-checkbox {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
cursor: pointer;
}
.log-type-checkbox input[type="checkbox"] {
margin-top: 4px;
width: 18px;
height: 18px;
accent-color: var(--primary-color);
}
.log-type-icon {
font-size: 1.5rem;
line-height: 1;
}
.log-type-info {
flex: 1;
}
.log-type-name {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 2px;
}
.log-type-desc {
font-size: 0.85rem;
color: var(--text-muted);
}
.log-type-status {
font-size: 1rem;
margin-left: auto;
}
.logs-channels-preview {
background: var(--bg-dark);
border-radius: var(--radius-md);
padding: var(--spacing-md);
}
.log-channel-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-color);
}
.log-channel-item:last-child {
border-bottom: none;
}
.log-channel-icon {
color: var(--text-muted);
font-weight: 500;
}
.log-channel-name {
flex: 1;
font-family: monospace;
color: var(--text-primary);
}
.log-channel-status {
font-size: 0.85rem;
padding: 2px 8px;
border-radius: var(--radius-sm);
}
.log-channel-status.created {
background: rgba(87, 242, 135, 0.15);
color: #57F287;
}
.log-channel-status.pending {
background: rgba(240, 178, 50, 0.15);
color: #F0B232;
}
.btn-group {
display: flex;
gap: var(--spacing-sm);
}
.btn-danger {
background: var(--error-color);
color: white;
border: none;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-danger:hover {
background: #c0392b;
}
+65
View File
@@ -74,6 +74,10 @@
<span class="nav-item-icon">🤖</span>
Apparence du bot
</a>
<a class="nav-item" data-section="logs">
<span class="nav-item-icon">📜</span>
Logs
</a>
</div>
</nav>
@@ -1113,6 +1117,66 @@
</div>
</section>
<!-- Section: Logs -->
<section class="config-section" id="section-logs">
<div class="config-card">
<div class="config-card-header">
<div class="config-card-title">
<span class="icon">📜</span>
<h3>Système de Logs</h3>
</div>
<label class="toggle-switch">
<input type="checkbox" id="logs-enabled">
<span class="toggle-slider"></span>
</label>
</div>
<div class="config-card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">
Activez les logs pour suivre toutes les actions sur votre serveur. Le bot créera automatiquement les salons de logs dans une catégorie dédiée.
</p>
<!-- Catégorie existante ou nouvelle -->
<div class="form-group">
<label class="form-label">📁 Catégorie pour les logs</label>
<select class="form-select" id="logs-category">
<option value="">📜 Créer une nouvelle catégorie "LOGS"</option>
</select>
<small class="text-muted">Sélectionnez une catégorie existante ou laissez vide pour en créer une nouvelle automatiquement.</small>
</div>
<!-- Types de logs -->
<div class="form-group">
<label class="form-label">🔧 Types de logs à activer</label>
<p class="text-muted" style="margin-bottom: 1rem;">Sélectionnez les types de logs que vous souhaitez activer. Un salon sera créé pour chaque type activé.</p>
<div class="logs-types-grid" id="logs-types-container">
<!-- Généré dynamiquement par JS -->
</div>
</div>
<!-- Aperçu des salons -->
<div class="form-group" id="logs-preview-container" style="display: none;">
<label class="form-label">👁️ Salons de logs</label>
<div class="logs-channels-preview" id="logs-channels-preview">
<!-- Généré dynamiquement -->
</div>
</div>
</div>
<div class="config-card-footer">
<div id="status-logs-form" class="status-message"></div>
<div class="btn-group">
<button type="button" class="btn btn-danger" id="logs-delete-btn" style="display: none;">
🗑️ Supprimer tous les salons
</button>
<button type="button" class="btn btn-primary" id="logs-save-btn">
💾 Sauvegarder
</button>
</div>
</div>
</div>
</section>
</div>
</main>
@@ -1133,5 +1197,6 @@
<script src="/guild/scheduledMessagesForm.js"></script>
<script src="/guild/sendMessageForm.js"></script>
<script src="/guild/botAppearanceForm.js"></script>
<script src="/guild/logsForm.js"></script>
</body>
</html>
+244
View File
@@ -0,0 +1,244 @@
// ===== LOGS FORM =====
(function() {
const logsEnabled = document.getElementById('logs-enabled');
const logsCategory = document.getElementById('logs-category');
const logsTypesContainer = document.getElementById('logs-types-container');
const logsPreviewContainer = document.getElementById('logs-preview-container');
const logsChannelsPreview = document.getElementById('logs-channels-preview');
const logsSaveBtn = document.getElementById('logs-save-btn');
const logsDeleteBtn = document.getElementById('logs-delete-btn');
const statusLogsForm = document.getElementById('status-logs-form');
let logTypes = [];
let currentConfig = null;
// Charger la config des logs
async function loadLogsConfig() {
try {
const res = await fetch(`/api/bot/get-logs-config/${guildId}`);
const data = await res.json();
if (data.success) {
logTypes = data.logTypes || [];
currentConfig = data.config || {};
// Remplir le select des catégories
logsCategory.innerHTML = '<option value="">📜 Créer une nouvelle catégorie "LOGS"</option>';
(data.categories || []).forEach(cat => {
const option = document.createElement('option');
option.value = cat.id;
option.textContent = cat.name;
if (currentConfig.category_id === cat.id) {
option.selected = true;
}
logsCategory.appendChild(option);
});
// Activer/désactiver le toggle
logsEnabled.checked = !!currentConfig.enabled;
// Générer les checkboxes pour les types de logs
renderLogTypes();
// Mettre à jour l'aperçu
updatePreview();
// Afficher le bouton supprimer si des salons existent
updateDeleteButton();
}
} catch (err) {
console.error('Erreur chargement config logs:', err);
}
}
// Générer les checkboxes des types de logs
function renderLogTypes() {
logsTypesContainer.innerHTML = '';
logTypes.forEach(logType => {
const isEnabled = currentConfig[`${logType.key}_enabled`];
const channelId = currentConfig[`${logType.key}_channel_id`];
const div = document.createElement('div');
div.className = 'log-type-card' + (isEnabled ? ' active' : '');
div.innerHTML = `
<label class="log-type-checkbox">
<input type="checkbox" name="log-type" value="${logType.key}" ${isEnabled ? 'checked' : ''}>
<span class="log-type-icon">${logType.name.split(' ')[0]}</span>
<div class="log-type-info">
<div class="log-type-name">${logType.name.substring(logType.name.indexOf(' ') + 1)}</div>
<div class="log-type-desc">${logType.description}</div>
</div>
${channelId ? `<span class="log-type-status">✅</span>` : ''}
</label>
`;
const checkbox = div.querySelector('input[type="checkbox"]');
checkbox.addEventListener('change', () => {
div.classList.toggle('active', checkbox.checked);
updatePreview();
});
logsTypesContainer.appendChild(div);
});
}
// Mettre à jour l'aperçu des salons
function updatePreview() {
const checkedTypes = [...document.querySelectorAll('input[name="log-type"]:checked')]
.map(cb => cb.value);
if (checkedTypes.length === 0 || !logsEnabled.checked) {
logsPreviewContainer.style.display = 'none';
return;
}
logsPreviewContainer.style.display = 'block';
logsChannelsPreview.innerHTML = '';
checkedTypes.forEach(key => {
const logType = logTypes.find(lt => lt.key === key);
if (!logType) return;
const channelId = currentConfig[`${key}_channel_id`];
const div = document.createElement('div');
div.className = 'log-channel-item';
div.innerHTML = `
<span class="log-channel-icon">#</span>
<span class="log-channel-name">${logType.channelName}</span>
<span class="log-channel-status ${channelId ? 'created' : 'pending'}">
${channelId ? '✅ Créé' : '⏳ Sera créé'}
</span>
`;
logsChannelsPreview.appendChild(div);
});
}
// Mettre à jour le bouton supprimer
function updateDeleteButton() {
const hasChannels = logTypes.some(lt => currentConfig[`${lt.key}_channel_id`]);
logsDeleteBtn.style.display = hasChannels ? 'inline-flex' : 'none';
}
// Sauvegarder la config
logsSaveBtn.addEventListener('click', async () => {
const enabledLogs = [...document.querySelectorAll('input[name="log-type"]:checked')]
.map(cb => cb.value);
logsSaveBtn.disabled = true;
logsSaveBtn.textContent = '⏳ Sauvegarde...';
statusLogsForm.textContent = '';
statusLogsForm.className = 'status-message';
try {
const res = await fetch('/api/bot/save-logs-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
guildId,
enabled: logsEnabled.checked,
categoryId: logsCategory.value || null,
enabledLogs
})
});
const data = await res.json();
if (data.success) {
statusLogsForm.textContent = '✅ Configuration sauvegardée !';
statusLogsForm.className = 'status-message success';
// Mettre à jour la config locale
if (data.categoryId) {
currentConfig.category_id = data.categoryId;
}
if (data.channels) {
for (const [key, channelId] of Object.entries(data.channels)) {
currentConfig[`${key}_channel_id`] = channelId;
if (enabledLogs.includes(key)) {
currentConfig[`${key}_enabled`] = 1;
}
}
}
// Rafraîchir l'affichage
renderLogTypes();
updatePreview();
updateDeleteButton();
// Recharger les catégories
await loadLogsConfig();
} else {
statusLogsForm.textContent = '❌ ' + (data.error || 'Erreur lors de la sauvegarde');
statusLogsForm.className = 'status-message error';
}
} catch (err) {
console.error('Erreur sauvegarde logs:', err);
statusLogsForm.textContent = '❌ Erreur de connexion';
statusLogsForm.className = 'status-message error';
}
logsSaveBtn.disabled = false;
logsSaveBtn.textContent = '💾 Sauvegarder';
});
// Supprimer tous les salons
logsDeleteBtn.addEventListener('click', async () => {
if (!confirm('⚠️ Êtes-vous sûr de vouloir supprimer tous les salons de logs ? Cette action est irréversible.')) {
return;
}
logsDeleteBtn.disabled = true;
logsDeleteBtn.textContent = '⏳ Suppression...';
statusLogsForm.textContent = '';
try {
const res = await fetch('/api/bot/delete-logs-channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ guildId })
});
const data = await res.json();
if (data.success) {
statusLogsForm.textContent = '✅ Tous les salons de logs ont été supprimés.';
statusLogsForm.className = 'status-message success';
// Reset la config locale
currentConfig = { enabled: false };
logsEnabled.checked = false;
// Décocher tous les types
document.querySelectorAll('input[name="log-type"]').forEach(cb => {
cb.checked = false;
cb.closest('.log-type-card')?.classList.remove('active');
});
// Rafraîchir
renderLogTypes();
updatePreview();
updateDeleteButton();
} else {
statusLogsForm.textContent = '❌ ' + (data.error || 'Erreur lors de la suppression');
statusLogsForm.className = 'status-message error';
}
} catch (err) {
console.error('Erreur suppression logs:', err);
statusLogsForm.textContent = '❌ Erreur de connexion';
statusLogsForm.className = 'status-message error';
}
logsDeleteBtn.disabled = false;
logsDeleteBtn.textContent = '🗑️ Supprimer tous les salons';
});
// Events toggle
logsEnabled.addEventListener('change', updatePreview);
// Charger au démarrage
window.addEventListener('guildLoaded', loadLogsConfig);
if (typeof guildId !== 'undefined' && guildId) {
loadLogsConfig();
}
})();