залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
{{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}}