211 lines
7.5 KiB
JavaScript
211 lines
7.5 KiB
JavaScript
const API = location.origin;
|
||
let token = sessionStorage.getItem('authToken');
|
||
let currentUser = null;
|
||
function hdr() { return { 'X-Auth-Token': token }; }
|
||
function hdrJson() { return { 'Content-Type': 'application/json', 'X-Auth-Token': token }; }
|
||
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
||
function thumbSrc(p) {
|
||
if (!p) return p;
|
||
if (p.startsWith('/uploads/')) return `${API}/uploads/thumb/${p.slice('/uploads/'.length)}`;
|
||
if (p.includes('/api/files/')) return p + (p.includes('?') ? '&' : '?') + 'thumb=1';
|
||
return p;
|
||
}
|
||
|
||
// --- Auth check ---
|
||
async function checkAuth() {
|
||
if (!token) { location.href = 'login.html'; return false; }
|
||
try {
|
||
const res = await fetch(`${API}/api/auth/me`, { headers: hdr() });
|
||
if (!res.ok) { cleanupSession(); return false; }
|
||
currentUser = await res.json();
|
||
return true;
|
||
} catch { cleanupSession(); return false; }
|
||
}
|
||
|
||
function cleanupSession() {
|
||
sessionStorage.removeItem('authToken');
|
||
token = null;
|
||
currentUser = null;
|
||
location.href = 'login.html';
|
||
}
|
||
|
||
function isAdmin() { return currentUser && currentUser.role === 'admin'; }
|
||
|
||
async function requireAdminPage() {
|
||
if (!(await checkAuth())) return false;
|
||
if (!isAdmin()) { location.href = 'journal.html'; return false; }
|
||
return true;
|
||
}
|
||
|
||
async function doLogout() {
|
||
try { await fetch(`${API}/api/auth/logout`, { method: 'POST', headers: hdr() }); } catch {}
|
||
cleanupSession();
|
||
}
|
||
|
||
// --- Sidebar ---
|
||
function buildSidebar(active) {
|
||
const el = document.getElementById('sidebar');
|
||
if (!el) return;
|
||
const base = [
|
||
{ page: 'dashboard', label: 'Дашборд', icon: 'layout-dashboard' },
|
||
{ page: 'journal', label: 'Журнал', icon: 'book-open' },
|
||
{ page: 'students', label: 'Ученики', icon: 'graduation-cap' },
|
||
{ page: 'groups', label: 'Группы', icon: 'users' },
|
||
{ page: 'files', label: 'Файлы', icon: 'folder' },
|
||
{ page: 'links', label: 'Ссылки', icon: 'link' },
|
||
{ page: 'trash', label: 'Корзина', icon: 'trash-2' },
|
||
];
|
||
const adminNav = [
|
||
{ page: 'branches', label: 'Филиалы', icon: 'building-2' },
|
||
{ page: 'users', label: 'Пользователи', icon: 'user-cog' },
|
||
{ page: 'worker', label: 'Воркер ИИ', icon: 'bot' },
|
||
{ page: 'audit', label: 'Аудит', icon: 'scroll-text' },
|
||
{ page: 'bans', label: 'Блокировки', icon: 'shield-off' },
|
||
{ page: 'settings', label: 'Настройки', icon: 'settings' },
|
||
];
|
||
const nav = [...base, ...(isAdmin() ? adminNav : [])];
|
||
const userName = currentUser?.name || currentUser?.username || '';
|
||
const roleLabel = isAdmin() ? 'Администратор' : 'Тутор';
|
||
el.innerHTML = `
|
||
<div class="logo">Админ-панель</div>
|
||
<div class="user-box">
|
||
<div class="user-name">${esc(userName)}</div>
|
||
<div class="user-role">${roleLabel}</div>
|
||
</div>
|
||
<nav>
|
||
${nav.map(n => `<a href="${n.page}.html" data-nav="${n.page}" class="${n.page === active ? 'active' : ''}"><i data-lucide="${n.icon}"></i>${n.label}</a>`).join('')}
|
||
</nav>
|
||
<div class="bottom">
|
||
<a href="/" data-nav="home"><i data-lucide="home"></i>На главную</a>
|
||
<a class="logout" href="#" data-action="logout"><i data-lucide="log-out"></i>Выйти</a>
|
||
</div>`;
|
||
renderIcons();
|
||
}
|
||
|
||
// --- Common UI ---
|
||
function renderIcons() {
|
||
if (window.lucide && typeof window.lucide.createIcons === 'function') {
|
||
window.lucide.createIcons();
|
||
}
|
||
}
|
||
|
||
function showToast(msg) {
|
||
const t = document.getElementById('toast');
|
||
if (!t) return;
|
||
t.textContent = msg;
|
||
t.classList.add('show');
|
||
setTimeout(() => t.classList.remove('show'), 3000);
|
||
}
|
||
|
||
function showImg(src) {
|
||
const img = document.getElementById('imgModalSrc');
|
||
const m = document.getElementById('imgModal');
|
||
if (img && m) { img.src = src; m.classList.add('open'); }
|
||
}
|
||
|
||
async function fillAutocomplete() {
|
||
const res = await fetch(`${API}/api/students`, { headers: hdr() });
|
||
const students = await res.json();
|
||
const names = students.map(s => s.name);
|
||
const dlEdit = document.getElementById('studentEditList');
|
||
if (dlEdit) {
|
||
dlEdit.innerHTML = '';
|
||
names.forEach(n => { const o = document.createElement('option'); o.value = n; dlEdit.appendChild(o); });
|
||
}
|
||
const dlAdd = document.getElementById('studentAddList');
|
||
if (dlAdd) {
|
||
dlAdd.innerHTML = '';
|
||
names.forEach(n => { const o = document.createElement('option'); o.value = n; dlAdd.appendChild(o); });
|
||
}
|
||
}
|
||
|
||
function copyStudentLink(name) {
|
||
const url = `${location.origin}/?nameInput=${encodeURIComponent(name)}`;
|
||
const done = () => showToast('Ссылка скопирована: ' + url);
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
navigator.clipboard.writeText(url).then(done).catch(() => fallbackCopy(url, done));
|
||
} else {
|
||
fallbackCopy(url, done);
|
||
}
|
||
}
|
||
|
||
function fallbackCopy(text, done) {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.style.position = 'fixed';
|
||
ta.style.opacity = '0';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
try { document.execCommand('copy'); done(); } catch { prompt('Скопируйте вручную:', text); }
|
||
document.body.removeChild(ta);
|
||
}
|
||
|
||
function studentUrlContent(name) {
|
||
const url = `${location.origin}/?nameInput=${encodeURIComponent(name)}`;
|
||
return `[InternetShortcut]\r\nURL=${url}\r\n`;
|
||
}
|
||
|
||
function safeWindowsName(name) {
|
||
return String(name).replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, '_');
|
||
}
|
||
|
||
function downloadBlob(blob, filename) {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
async function exportStudentUrlZip(students, zipName) {
|
||
if (typeof JSZip === 'undefined') { alert('JSZip не загружен'); return; }
|
||
const zip = new JSZip();
|
||
students.forEach(s => {
|
||
zip.file(safeWindowsName(s.name) + '.url', studentUrlContent(s.name));
|
||
});
|
||
const blob = await zip.generateAsync({ type: 'blob' });
|
||
downloadBlob(blob, zipName);
|
||
}
|
||
|
||
function copyText(text) {
|
||
const done = () => showToast('Скопировано: ' + text);
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
navigator.clipboard.writeText(text).then(done).catch(() => fallbackCopy(text, done));
|
||
} else {
|
||
fallbackCopy(text, done);
|
||
}
|
||
}
|
||
|
||
function renderPager(el, page, totalPages, onPage) {
|
||
if (!el) return;
|
||
if (totalPages <= 1) { el.innerHTML = ''; return; }
|
||
let html = `<button ${page === 1 ? 'disabled' : ''} data-nav="-1">‹</button>`;
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
if (totalPages > 9 && i > 2 && i < totalPages - 1 && Math.abs(i - page) > 2) {
|
||
if (i === 3) html += `<button disabled>…</button>`;
|
||
continue;
|
||
}
|
||
html += `<button class="${i === page ? 'active' : ''}" data-page="${i}">${i}</button>`;
|
||
}
|
||
html += `<button ${page === totalPages ? 'disabled' : ''} data-nav="1">›</button>`;
|
||
el.innerHTML = html;
|
||
el._onPage = onPage;
|
||
el._page = page;
|
||
if (!el._bound) {
|
||
el._bound = true;
|
||
el.addEventListener('click', e => {
|
||
const btn = e.target.closest('button');
|
||
if (!btn || btn.disabled) return;
|
||
const p = btn.dataset.page ? parseInt(btn.dataset.page, 10) : el._page + parseInt(btn.dataset.nav, 10);
|
||
if (el._onPage) el._onPage(p);
|
||
});
|
||
}
|
||
}
|
||
|
||
document.addEventListener('click', e => {
|
||
const imgEl = e.target.closest('[data-img]');
|
||
if (imgEl) { e.preventDefault(); showImg(imgEl.dataset.img); return; }
|
||
const out = e.target.closest('[data-action="logout"]');
|
||
if (out) { e.preventDefault(); doLogout(); }
|
||
}); |