217 lines
11 KiB
HTML
217 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Настройки — Админ-панель</title>
|
|
<link rel="stylesheet" href="admin.css">
|
|
</head>
|
|
<body data-page="settings">
|
|
<div class="layout">
|
|
<div class="sidebar" id="sidebar"></div>
|
|
<div class="main">
|
|
<div class="page-head">
|
|
<h2>Настройки</h2>
|
|
<p class="page-sub">Параметры формы и данные системы</p>
|
|
</div>
|
|
|
|
<div class="settings-stack settings-stack-grid">
|
|
<div class="settings-card wide">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="bar-chart-3"></i></div>
|
|
<h3>Системная информация</h3>
|
|
</div>
|
|
<div id="systemInfo" class="sys-grid">
|
|
<div class="sys-card loading">Загрузка…</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="shield"></i></div>
|
|
<h3>Защита от повторов</h3>
|
|
</div>
|
|
<p class="hint">Если ученик уже отправил ответ, следующая отправка от него будет отклонена в течение заданного времени. Минимальное значение — 1 минута.</p>
|
|
<div class="settings-row">
|
|
<div class="settings-field">
|
|
<label for="spamInterval">Интервал ожидания</label>
|
|
<div class="unit-row">
|
|
<input type="number" id="spamInterval" class="settings-input" min="1" max="1440" step="1">
|
|
<span class="unit">минут</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="link"></i></div>
|
|
<h3>Страница публичной ссылки /s/<hash></h3>
|
|
</div>
|
|
<p class="hint">Управление видимостью элементов на публичных страницах записей.</p>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
<input type="checkbox" id="shareShowMessage">
|
|
Показать сообщение ученика
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
<input type="checkbox" id="shareShowDate">
|
|
Показать дату в карточке записи
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
<input type="checkbox" id="shareShowNames">
|
|
Показывать фамилии и имена учеников
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
<input type="checkbox" id="shareShowPhotos">
|
|
Показывать фото группы
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label for="cookieNoticeText">Уведомление о cookie</label>
|
|
<textarea id="cookieNoticeText" class="settings-input" rows="3" style="min-height:80px"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="file-text"></i></div>
|
|
<h3>Футер на форме</h3>
|
|
</div>
|
|
<p class="hint">Тексты показываются внизу формы ответа ученика.</p>
|
|
<div class="settings-row">
|
|
<div class="settings-field">
|
|
<label for="footerLeft">Текст слева</label>
|
|
<input type="text" id="footerLeft" class="settings-input" placeholder="Например: Поле, руководитель">
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="footerRight">Текст справа</label>
|
|
<input type="text" id="footerRight" class="settings-input" placeholder="Например: Год, курс">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="bot"></i></div>
|
|
<h3>Промпт ИИ для исправления текста</h3>
|
|
</div>
|
|
<p class="hint">Инструкция для нейросети при исправлении ошибок в записи «Что делала(а)».</p>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label for="aiPrompt">Промпт</label>
|
|
<textarea id="aiPrompt" class="settings-input" rows="4" style="min-height:100px;font-family:monospace;font-size:.85rem"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-field" style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
<input type="checkbox" id="aiAutoCheck">
|
|
Автоматически проверять новые записи (без участия человека)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="ai-queue" id="aiQueueInfo"></div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="boxes"></i></div>
|
|
<h3>Профили моделей ИИ</h3>
|
|
</div>
|
|
<p class="hint">Модель, выбранная по умолчанию, обрабатывает записи учеников. «Нативная» — llama.cpp в Docker. Можно добавить любого провайдера с OpenAI-совместимым API (OpenRouter, OpenAI, Ollama и др.).</p>
|
|
<div class="ai-queue" id="aiProfileActive"></div>
|
|
<div class="card-foot" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:.88rem;min-width:0;max-width:100%">
|
|
Модель по умолчанию:
|
|
<select id="aiActiveProfile" class="settings-input" style="width:auto;min-width:0;max-width:100%;flex:1 1 200px">
|
|
<option value="native">Нативная (llama.cpp)</option>
|
|
</select>
|
|
</label>
|
|
<button class="btn-primary ghost" id="aiProfileAddBtn" type="button"><i data-lucide="plus"></i> Добавить профиль</button>
|
|
</div>
|
|
<div id="aiProfilesList" class="ai-profiles-list"></div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="database"></i></div>
|
|
<h3>Резервное копирование</h3>
|
|
</div>
|
|
<p class="hint">Скачайте архив со всеми данными (ученики, группы, журнал, настройки) и фотографиями. Восстановление полностью заменит текущие данные.</p>
|
|
<div class="data-stats" id="dataStats"><span class="chip">Загрузка статистики…</span></div>
|
|
<div class="card-foot">
|
|
<button class="btn-primary ghost" id="backupBtn" type="button"><i data-lucide="download"></i> Скачать бэкап</button>
|
|
<button class="btn-primary ghost" id="restoreBtn" type="button"><i data-lucide="upload"></i> Восстановить из файла</button>
|
|
<input type="file" id="restoreFile" accept=".tar.gz,.gz,application/gzip" style="display:none">
|
|
</div>
|
|
<div class="restore-status" id="restoreStatus"></div>
|
|
</div>
|
|
|
|
<div class="settings-card" id="bansCard">
|
|
<div class="card-head">
|
|
<div class="ic"><i data-lucide="shield-off"></i></div>
|
|
<h3>Блокировки IP</h3>
|
|
</div>
|
|
<p class="hint">IP автоматически блокируется на 24 часа: за заполнение скрытого антиспам-поля, за 10 неудачных попыток входа или 10 неверных паролей на защищённой ссылке.</p>
|
|
<div class="data-stats" id="bansList"><span class="chip">Загрузка…</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="save-bar" id="saveBar">
|
|
<span class="hint" id="saveHint" style="display:inline-flex;align-items:center;gap:6px"><i data-lucide="triangle-alert"></i> Есть несохранённые изменения</span>
|
|
<button class="btn-primary" id="saveBtn" type="button">Сохранить изменения</button>
|
|
</div>
|
|
|
|
<div class="modal-overlay" id="aiProfileModal">
|
|
<div class="edit-modal" style="max-width:560px">
|
|
<h3 id="aiProfileModalTitle">Добавить профиль ИИ</h3>
|
|
<div class="settings-field">
|
|
<label for="aiProfileName">Название профиля <span style="color:#ef4444">*</span></label>
|
|
<input type="text" id="aiProfileName" class="settings-input" placeholder="Например: OpenRouter GPT-4o-mini">
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="aiProfileModel">Модель <span style="color:#ef4444">*</span></label>
|
|
<input type="text" id="aiProfileModel" class="settings-input" placeholder="openai/gpt-4o-mini">
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="aiProfileBaseUrl">Base URL (OpenAI-совместимый API) <span style="color:#ef4444">*</span></label>
|
|
<input type="text" id="aiProfileBaseUrl" class="settings-input" placeholder="https://openrouter.ai/api/v1">
|
|
</div>
|
|
<div class="settings-field">
|
|
<label for="aiProfileApiKey">API-ключ (если требуется)</label>
|
|
<input type="password" id="aiProfileApiKey" class="settings-input" placeholder="sk-...">
|
|
</div>
|
|
<span id="aiProfileTestStatus" style="font-size:.85rem;min-height:1.2em"></span>
|
|
<div class="actions" style="flex-wrap:wrap">
|
|
<button class="btn-primary" id="aiProfileSaveBtn" type="button">Сохранить</button>
|
|
<button class="btn-primary ghost" id="aiProfileTestBtn" type="button"><i data-lucide="plug-zap"></i> Проверить</button>
|
|
<button class="btn-primary ghost" id="aiProfileCancelBtn" type="button">Отмена</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="toast" id="toast"></div>
|
|
<script src="vendor/lucide.min.js"></script>
|
|
<script src="admin.js"></script>
|
|
<script src="js/settings.js"></script>
|
|
</body>
|
|
</html> |