TTW_Bot_GO/internal/webui/templates/dashboard.html

142 lines
6.0 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
<h2>Пользователи чата</h2>
<div style="overflow-x: auto;">
<table id="users-table">
<thead>
<tr>
<th>Пользователь</th>
<th>Сообщений</th>
<th>Последняя активность</th>
<th>Модератор</th>
<th>VIP</th>
<th>Подписчик</th>
<th>Действия</th>
<th>Отмечать</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script>
let users = [];
let refreshInterval = null;
async function loadUsers() {
try {
const res = await fetch('/api/users');
users = await res.json();
renderTable();
} catch(err) {
console.error('Failed to load users', err);
}
}
function renderTable() {
const tbody = document.querySelector('#users-table tbody');
if (!users.length) {
tbody.innerHTML = '<tr><td colspan="8">Нет активных пользователей</td></tr>';
return;
}
tbody.innerHTML = users.map(user => `
<tr data-username="${escapeHtml(user.username)}">
<td>${escapeHtml(user.username)}</td>
<td>${user.message_count}</td>
<td>${formatRelativeTime(user.last_active)}</td>
<td><input type="checkbox" class="mod-checkbox" ${user.is_mod ? 'checked' : ''} data-username="${escapeHtml(user.username)}"></td>
<td><input type="checkbox" class="vip-checkbox" ${user.is_vip ? 'checked' : ''} data-username="${escapeHtml(user.username)}"></td>
<td><input type="checkbox" disabled ${user.is_subscriber ? 'checked' : ''}></td>
<td>
<button class="warn-btn" data-user="${escapeHtml(user.username)}">Предупредить</button>
<button class="timeout10s-btn" data-user="${escapeHtml(user.username)}">10 сек</button>
<button class="timeout10m-btn" data-user="${escapeHtml(user.username)}">10 мин</button>
<button class="ban-btn" data-user="${escapeHtml(user.username)}">Забанить</button>
<button class="unban-btn" data-user="${escapeHtml(user.username)}">Разбанить</button>
</td>
<td><input type="checkbox" class="mark-checkbox" ${user.is_marked ? 'checked' : ''} data-username="${escapeHtml(user.username)}"></td>
</tr>
`).join('');
document.querySelectorAll('.mod-checkbox').forEach(cb => {
cb.addEventListener('change', (e) => toggleMod(cb.dataset.username, cb.checked));
});
document.querySelectorAll('.vip-checkbox').forEach(cb => {
cb.addEventListener('change', (e) => toggleVip(cb.dataset.username, cb.checked));
});
document.querySelectorAll('.warn-btn').forEach(btn => {
btn.addEventListener('click', () => userAction(btn.dataset.user, 'warn'));
});
document.querySelectorAll('.timeout10s-btn').forEach(btn => {
btn.addEventListener('click', () => userAction(btn.dataset.user, 'timeout10sec'));
});
document.querySelectorAll('.timeout10m-btn').forEach(btn => {
btn.addEventListener('click', () => userAction(btn.dataset.user, 'timeout10min'));
});
document.querySelectorAll('.ban-btn').forEach(btn => {
btn.addEventListener('click', () => userAction(btn.dataset.user, 'ban'));
});
document.querySelectorAll('.unban-btn').forEach(btn => {
btn.addEventListener('click', () => userAction(btn.dataset.user, 'unban'));
});
document.querySelectorAll('.mark-checkbox').forEach(cb => {
cb.addEventListener('change', (e) => toggleMark(cb.dataset.username, cb.checked));
});
}
async function toggleMod(username, isMod) {
const action = isMod ? 'set_mod' : 'unset_mod';
await userAction(username, action);
}
async function toggleVip(username, isVip) {
const action = isVip ? 'set_vip' : 'unset_vip';
await userAction(username, action);
}
async function toggleMark(username, isMarked) {
await userAction(username, 'toggle_mark');
}
async function userAction(username, action) {
try {
const res = await fetch('/api/users/action', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ username, action, platform: 'twitch' })
});
if (!res.ok) throw new Error(await res.text());
await loadUsers();
} catch(err) {
alert('Ошибка: ' + err.message);
}
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
function formatRelativeTime(timestamp) {
if (!timestamp) return 'никогда';
const date = new Date(timestamp);
const now = new Date();
const seconds = Math.floor((now - date) / 1000);
if (seconds < 60) return 'только что';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes} мин. назад`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours} ч. назад`;
const days = Math.floor(hours / 24);
if (days < 30) return `${days} дн. назад`;
const months = Math.floor(days / 30);
if (months < 12) return `${months} мес. назад`;
const years = Math.floor(months / 12);
return `${years} г. назад`;
}
loadUsers();
refreshInterval = setInterval(loadUsers, 3000);
</script>
{{end}}