Dnishe/templates/admin.html
2026-06-08 21:56:14 +00:00

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>