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: 'modules', label: 'Темы модулей', icon: 'layers' }, { 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 = `
${esc(userName)}
${roleLabel}
На главную Выйти
`; renderIcons(); loadVersion(); } async function loadVersion() { const el = document.getElementById('sidebarVersion'); if (!el) return; try { const res = await fetch(`${API}/version.json?t=${Date.now()}`); if (!res.ok) { el.textContent = ''; return; } const v = await res.json(); if (!v.short) { el.textContent = ''; return; } const d = (v.date || '').slice(0, 10); el.textContent = `v${v.short}${d ? ' · ' + d : ''}`; el.title = `${v.short} · ${v.date || ''}`; } catch { el.textContent = ''; } } // --- 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 = ``; for (let i = 1; i <= totalPages; i++) { if (totalPages > 9 && i > 2 && i < totalPages - 1 && Math.abs(i - page) > 2) { if (i === 3) html += ``; continue; } html += ``; } html += ``; 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(); } });