315 lines
15 KiB
HTML
315 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Admin Dashboard</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div class="chat-container">
|
|
<div class="servers-bar">
|
|
<div class="server-icon active" data-server="admin">
|
|
<span>⚙️</span>
|
|
</div>
|
|
</div>
|
|
<div class="sidebar">
|
|
<div class="server-header">
|
|
<div class="server-name">Admin Panel</div>
|
|
<button id="logout-btn" class="logout-btn">⌫</button>
|
|
</div>
|
|
<div class="sidebar-tabs">
|
|
<button class="tab-btn active" data-tab="users">👥 Users</button>
|
|
<button class="tab-btn" data-tab="bulk">🗑️ Bulk Delete</button>
|
|
</div>
|
|
<div id="users-tab" class="tab-content active">
|
|
<input type="text" id="userSearch" placeholder="Search users..." class="auth-input">
|
|
<div id="usersList" class="channels"></div>
|
|
</div>
|
|
<div id="bulk-tab" class="tab-content">
|
|
<div class="bulk-delete-ui">
|
|
<div>
|
|
<button id="loadServersBtn" class="auth-btn">Load Servers</button>
|
|
<select id="serverSelect" class="auth-input">
|
|
<option>Select server</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button id="loadChannelsBtn" class="auth-btn">Load Channels</button>
|
|
<select id="channelSelect" class="auth-input">
|
|
<option>Select channel</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button id="loadMessagesBtn" class="auth-btn">Load Messages</button>
|
|
</div>
|
|
<button id="bulkDeleteBtn" class="auth-btn" disabled>Bulk Delete Selected</button>
|
|
<div id="bulkStatus" class="auth-message"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="main-content">
|
|
<div class="chat-header">
|
|
<h2>Admin Dashboard</h2>
|
|
<div class="current-user">
|
|
<span id="current-user-name"></span>
|
|
</div>
|
|
</div>
|
|
<div class="messages-container" id="adminMessages">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Tab switching
|
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
btn.onclick = () => {
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
document.getElementById(btn.dataset.tab + '-tab').classList.add('active');
|
|
if (btn.dataset.tab === 'users') {
|
|
loadGlobalMessages();
|
|
}
|
|
};
|
|
});
|
|
|
|
var token = localStorage.getItem('token');
|
|
if (!token) {
|
|
window.location.href = '/';
|
|
return;
|
|
}
|
|
|
|
// Common
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
const currentUserName = document.getElementById('current-user-name');
|
|
logoutBtn.onclick = () => {
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/';
|
|
};
|
|
|
|
fetch('/api/profile', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
}).then(res => res.json()).then(user => {
|
|
currentUserName.textContent = user.username + (user.badge ? ' (' + user.badge + ')' : '');
|
|
}).catch(() => window.location.href = '/');
|
|
|
|
// Users tab
|
|
const userSearch = document.getElementById('userSearch');
|
|
const usersList = document.getElementById('usersList');
|
|
userSearch.oninput = loadUsers;
|
|
async function loadUsers() {
|
|
try {
|
|
const res = await fetch('/api/admin/users', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }
|
|
});
|
|
const users = await res.json();
|
|
const search = userSearch.value.toLowerCase();
|
|
usersList.innerHTML = '';
|
|
users.filter(u => u.username.toLowerCase().includes(search)).forEach(u => {
|
|
const div = document.createElement('div');
|
|
div.className = 'channel';
|
|
const badgeHtml = u.badge ? '<span class="user-badge ' + u.badge.toLowerCase() + '">' + u.badge + '</span>' : '';
|
|
const bannedHtml = u.is_banned ? '<span style="color: #f04747;">(Banned)</span>' : '';
|
|
|
|
div.innerHTML = '<span>' + u.username + ' ' + badgeHtml + ' ' + bannedHtml + '</span>' +
|
|
'<button class="auth-btn" style="padding: 4px 12px; font-size: 12px;" onclick="toggleBan(' + u.id + ', ' + u.is_banned + ')">' +
|
|
(u.is_banned ? 'Unban' : 'Ban') + '</button>';
|
|
usersList.appendChild(div);
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
|
|
window.toggleBan = async (id, banned) => {
|
|
try {
|
|
await fetch('/api/admin/ban', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ user_id: id, ban: banned ? 0 : 1 })
|
|
});
|
|
loadUsers();
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
};
|
|
|
|
// Messages display
|
|
async function loadGlobalMessages() {
|
|
try {
|
|
const res = await fetch('/api/messages', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
});
|
|
const msgs = await res.json();
|
|
const container = document.getElementById('adminMessages');
|
|
container.innerHTML = '';
|
|
msgs.slice(0, 20).forEach(m => {
|
|
const div = document.createElement('div');
|
|
div.className = 'message';
|
|
const displayContent = m.content.length > 100 ? m.content.substring(0, 100) + '...' : m.content;
|
|
div.innerHTML =
|
|
'<div class="msg-avatar" style="background: #43b581;"></div>' +
|
|
'<div class="message-body">' +
|
|
'<div class="message-header">' +
|
|
'<span class="message-author">' + m.username + '</span>' +
|
|
'<span class="msg-pronouns">' + (m.pronouns || '') + '</span>' +
|
|
'<span class="message-time">' + m.timestamp + '</span>' +
|
|
'</div>' +
|
|
'<div class="message-content-wrapper">' +
|
|
'<div class="message-content">' + displayContent + '</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
container.appendChild(div);
|
|
});
|
|
// Autoscroll to bottom
|
|
container.scrollTop = container.scrollHeight;
|
|
} catch (e) {
|
|
console.error('Load messages:', e);
|
|
}
|
|
}
|
|
|
|
// Bulk delete
|
|
const loadServersBtn = document.getElementById('loadServersBtn');
|
|
const serverSelect = document.getElementById('serverSelect');
|
|
const loadChannelsBtn = document.getElementById('loadChannelsBtn');
|
|
const channelSelect = document.getElementById('channelSelect');
|
|
const loadMessagesBtn = document.getElementById('loadMessagesBtn');
|
|
const bulkDeleteBtn = document.getElementById('bulkDeleteBtn');
|
|
const bulkStatus = document.getElementById('bulkStatus');
|
|
let selectedMessages = [];
|
|
|
|
loadServersBtn.onclick = async () => {
|
|
try {
|
|
const res = await fetch('/api/admin/servers', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }
|
|
});
|
|
const servers = await res.json();
|
|
serverSelect.innerHTML = '<option>Select server</option>';
|
|
servers.forEach(s => {
|
|
const opt = document.createElement('option');
|
|
opt.value = s.id;
|
|
opt.textContent = s.name;
|
|
serverSelect.appendChild(opt);
|
|
});
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
};
|
|
|
|
loadChannelsBtn.onclick = async () => {
|
|
const serverId = serverSelect.value;
|
|
if (!serverId) return alert('Select server first');
|
|
try {
|
|
const res = await fetch('/api/admin/channels', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ server_id: serverId })
|
|
});
|
|
const channels = await res.json();
|
|
channelSelect.innerHTML = '<option>Select channel</option>';
|
|
channels.forEach(c => {
|
|
const opt = document.createElement('option');
|
|
opt.value = c.id;
|
|
opt.textContent = c.name;
|
|
channelSelect.appendChild(opt);
|
|
});
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
};
|
|
|
|
loadMessagesBtn.onclick = async () => {
|
|
const channelId = channelSelect.value;
|
|
if (!channelId) {
|
|
alert('Please select a channel first.');
|
|
return;
|
|
}
|
|
try {
|
|
const res = await fetch('/api/admin/messages', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ channel_id: channelId })
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error('Failed to load messages');
|
|
}
|
|
const msgs = await res.json();
|
|
const container = document.getElementById('adminMessages');
|
|
container.innerHTML = '';
|
|
selectedMessages = [];
|
|
if (msgs.length === 0) {
|
|
container.innerHTML = '<div class="message">No messages found in this channel.</div>';
|
|
} else {
|
|
msgs.forEach(m => {
|
|
const div = document.createElement('div');
|
|
div.className = 'message';
|
|
const displayContent = m.content.length > 100 ? m.content.substring(0, 100) + '...' : m.content;
|
|
div.innerHTML =
|
|
'<div class="msg-avatar" style="background: #43b581;"></div>' +
|
|
'<div class="message-body">' +
|
|
'<div class="message-header">' +
|
|
'<input type="checkbox" id="msg-checkbox-' + m.id + '" onchange="toggleMsgSelect(' + m.id + ')" style="margin-right: 10px;">' +
|
|
'<span class="message-author">' + m.username + '</span>' +
|
|
'<span class="msg-pronouns">' + (m.pronouns || '') + '</span>' +
|
|
'<span class="message-time">' + m.timestamp + '</span>' +
|
|
'</div>' +
|
|
'<div class="message-content-wrapper">' +
|
|
'<div class="message-content">' + displayContent + '</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
container.appendChild(div);
|
|
});
|
|
}
|
|
bulkDeleteBtn.disabled = true;
|
|
bulkDeleteBtn.textContent = 'Bulk Delete Selected';
|
|
bulkStatus.textContent = '';
|
|
// Autoscroll to bottom
|
|
container.scrollTop = container.scrollHeight;
|
|
} catch (e) {
|
|
alert('Error loading messages: ' + e.message);
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
window.toggleMsgSelect = (id) => {
|
|
if (selectedMessages.includes(id)) {
|
|
selectedMessages = selectedMessages.filter(x => x !== id);
|
|
} else {
|
|
selectedMessages.push(id);
|
|
}
|
|
bulkDeleteBtn.textContent = 'Delete ' + selectedMessages.length + ' selected';
|
|
bulkDeleteBtn.disabled = selectedMessages.length === 0;
|
|
};
|
|
|
|
bulkDeleteBtn.onclick = async () => {
|
|
if (selectedMessages.length === 0) return;
|
|
if (!confirm('Delete ' + selectedMessages.length + ' messages?')) return;
|
|
try {
|
|
const res = await fetch('/api/admin/bulk-delete', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ message_ids: selectedMessages })
|
|
});
|
|
const result = await res.json();
|
|
bulkStatus.textContent = 'Deleted ' + result.deleted + ' messages';
|
|
bulkStatus.className = 'auth-message success';
|
|
selectedMessages = [];
|
|
bulkDeleteBtn.disabled = true;
|
|
bulkDeleteBtn.textContent = 'Bulk Delete Selected';
|
|
loadMessagesBtn.click(); // reload
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
};
|
|
|
|
loadUsers();
|
|
loadGlobalMessages();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |