feat: добавлен режим "Каникулы" и улучшения админ-панели

- Добавлен режим "Каникулы" который полностью заменяет главную страницу:
  * Карточка с эмодзи 🎉 и праздничным сообщением
  * Поддержка произвольного текста в формате Markdown
  * Карточка центрируется по вертикали при отсутствии текста

- Улучшения админ-панели:
  * Переключатель режима "Каникулы"
  * Редактор текста с подсказками по форматированию Markdown
  * Исправлена проблема с обновлением настроек (сохранение существующих значений)
  * Исправлена проблема с debug опциями в production (не блокируют обновление обычных настроек)

- Оптимизация загрузки:
  * Проверка режима каникул перед загрузкой групп
  * Динамическая загрузка ReactMarkdown только при необходимости
  * Кеш настроек сбрасывается на главной странице для актуальности

- Добавлен скрипт для сброса пароля администратора (scripts/reset-admin-password.js)

- Установлена библиотека react-markdown для рендеринга Markdown контента
This commit is contained in:
kilyabin
2025-12-04 23:22:42 +04:00
parent e46a2419c3
commit 3f74709513
9 changed files with 1562 additions and 23 deletions

121
scripts/reset-admin-password.js Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env node
/**
* Скрипт для сброса пароля администратора
*
* Использование:
* node scripts/reset-admin-password.js [новый_пароль]
* или
* node scripts/reset-admin-password.js (интерактивный режим)
*/
const Database = require('better-sqlite3');
const bcrypt = require('bcrypt');
const path = require('path');
const fs = require('fs');
const readline = require('readline');
// Определяем путь к базе данных
function findDatabase() {
const possiblePaths = [
path.join(process.cwd(), 'data', 'schedule-app.db'),
path.join(process.cwd(), '.next', 'standalone', 'data', 'schedule-app.db'),
'/opt/kspguti-schedule/data/schedule-app.db',
'/opt/kspguti-schedule/.next/standalone/data/schedule-app.db',
];
for (const dbPath of possiblePaths) {
if (fs.existsSync(dbPath)) {
return dbPath;
}
}
return null;
}
// Функция для чтения пароля из терминала (простая версия)
function readPassword(prompt) {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(prompt, (password) => {
rl.close();
resolve(password);
});
});
}
async function main() {
console.log('🔐 Сброс пароля администратора\n');
// Находим базу данных
const dbPath = findDatabase();
if (!dbPath) {
console.error('❌ Ошибка: База данных не найдена!');
console.log('\nИскали в следующих местах:');
console.log(' - ' + path.join(process.cwd(), 'data', 'schedule-app.db'));
console.log(' - ' + path.join(process.cwd(), '.next', 'standalone', 'data', 'schedule-app.db'));
console.log(' - /opt/kspguti-schedule/data/schedule-app.db');
console.log(' - /opt/kspguti-schedule/.next/standalone/data/schedule-app.db');
process.exit(1);
}
console.log('✓ Найдена база данных: ' + dbPath + '\n');
// Получаем новый пароль
let newPassword;
if (process.argv[2]) {
newPassword = process.argv[2];
} else {
newPassword = await readPassword('Введите новый пароль (минимум 8 символов): ');
if (newPassword.length < 8) {
console.error('❌ Ошибка: Пароль должен содержать минимум 8 символов');
process.exit(1);
}
const confirmPassword = await readPassword('Подтвердите пароль: ');
if (newPassword !== confirmPassword) {
console.error('❌ Ошибка: Пароли не совпадают');
process.exit(1);
}
}
if (newPassword.length < 8) {
console.error('❌ Ошибка: Пароль должен содержать минимум 8 символов');
process.exit(1);
}
// Открываем базу данных и обновляем пароль
try {
console.log('\n⏳ Обновление пароля...');
const db = new Database(dbPath);
// Хешируем новый пароль
const saltRounds = 10;
const hash = bcrypt.hashSync(newPassword, saltRounds);
// Обновляем пароль в базе данных
db.prepare('INSERT OR REPLACE INTO admin_password (id, password_hash) VALUES (1, ?)').run(hash);
db.close();
console.log('✅ Пароль успешно изменен!');
console.log('\n⚠ ВАЖНО: Сохраните пароль в безопасном месте!');
if (process.argv[2]) {
console.log('Новый пароль: ' + newPassword);
}
} catch (error) {
console.error('❌ Ошибка при изменении пароля:');
console.error(error.message);
process.exit(1);
}
}
main();