168 lines
5.8 KiB
JavaScript
168 lines
5.8 KiB
JavaScript
const API = location.origin;
|
||
let token = sessionStorage.getItem('adminToken');
|
||
function hdr() { return { 'X-Admin-Token': token }; }
|
||
function hdrJson() { return { 'Content-Type': 'application/json', 'X-Admin-Token': token }; }
|
||
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
||
|
||
// --- Auth check ---
|
||
async function checkAuth() {
|
||
if (!token) { location.href = 'login.html'; return false; }
|
||
try {
|
||
const res = await fetch(`${API}/api/stats`, { headers: hdr() });
|
||
if (!res.ok) { sessionStorage.removeItem('adminToken'); location.href = 'login.html'; return false; }
|
||
return true;
|
||
} catch { sessionStorage.removeItem('adminToken'); location.href = 'login.html'; return false; }
|
||
}
|
||
|
||
function doLogout() {
|
||
sessionStorage.removeItem('adminToken');
|
||
location.href = 'login.html';
|
||
}
|
||
|
||
// --- Sidebar ---
|
||
const NAV = [
|
||
{ page: 'dashboard', label: 'Дашборд' },
|
||
{ page: 'journal', label: 'Журнал' },
|
||
{ page: 'trash', label: 'Корзина' },
|
||
{ page: 'files', label: 'Файлы' },
|
||
{ page: 'links', label: 'Ссылки' },
|
||
{ page: 'students', label: 'Ученики' },
|
||
{ page: 'groups', label: 'Группы' },
|
||
{ page: 'branches', label: 'Филиалы' },
|
||
{ page: 'audit', label: 'Аудит' },
|
||
{ page: 'settings', label: 'Настройки' }
|
||
];
|
||
function buildSidebar(active) {
|
||
const el = document.getElementById('sidebar');
|
||
if (!el) return;
|
||
el.innerHTML = `
|
||
<div class="logo">Админ-панель</div>
|
||
<nav>
|
||
${NAV.map(n => `<a href="${n.page}.html" class="${n.page === active ? 'active' : ''}">${n.label}</a>`).join('')}
|
||
</nav>
|
||
<div class="bottom">
|
||
<a href="/">На главную</a>
|
||
<a class="logout" href="#" data-action="logout">Выйти</a>
|
||
</div>`;
|
||
}
|
||
|
||
// --- Common UI ---
|
||
function showToast(msg) {
|
||
const t = document.getElementById('toast');
|
||
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`);
|
||
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(); }
|
||
}); |