7eea33a135
- registry.js: добавлен флаг requireConfirmOff для 7 критичных прав (questions.manage, classes.manage, library.upload, courses.manage, sessions.reset, theory.access, simulations.access); byRole() теперь возвращает это поле - admin.html: subtitle в модале прав — «учителя» → «пользователя»; tooltip на кнопке «Сбросить всё по умолчанию»; поле поиска над сеткой прав; CSS .perm-modified-dot (amber, 8px) - admin.js: badge «Инд.» → «Индивидуально» (font-size 11px); renderPermissions() рисует .perm-modified-dot когда значение отличается от registry default; togglePermission() показывает LS.confirm перед выключением критичных прав; window.filterPermissions() скрывает карточки и role-блоки по поисковому запросу Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
200 lines
8.4 KiB
JavaScript
200 lines
8.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Single source of truth for all granular permissions.
|
|
* Keys here MUST match what controllers/routes use in requirePermission(...).
|
|
*
|
|
* Shape:
|
|
* <key>: {
|
|
* role: 'teacher' | 'student', // primary role for admin UI grouping
|
|
* roles: string[], // all roles this key applies to
|
|
* default: 0 | 1, // PERM_DEFAULTS value
|
|
* label: 'Short label for admin UI',
|
|
* desc: 'Russian description for admin UI'
|
|
* }
|
|
*
|
|
* DISCREPANCY NOTE (2026-05-17):
|
|
* auth.js PERM_DEFAULTS includes a 'free_student' role with the same 8
|
|
* permission keys as 'student' (identical key strings, identical defaults).
|
|
* permissionsController.js ALL_PERMISSIONS does NOT list free_student entries
|
|
* (seedDefaults/admin UI only handles teacher and student).
|
|
* Resolution: each student-role key carries roles:['student','free_student']
|
|
* so that requirePermission() fallback works correctly for free_student users.
|
|
* The admin UI behaviour (filtering to teacher/student only) is preserved —
|
|
* permissionsController reads from registry.byRole('teacher') and
|
|
* registry.byRole('student').
|
|
*/
|
|
|
|
const PERMISSIONS = {
|
|
/* ── Teacher ─────────────────────────────────────────────────────────── */
|
|
'questions.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Управление вопросами',
|
|
desc: 'Создавать, редактировать и копировать вопросы в банке',
|
|
requireConfirmOff: true,
|
|
},
|
|
'questions.delete': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Удалять вопросы',
|
|
desc: 'Удалять вопросы из банка (требует "Управление вопросами")',
|
|
},
|
|
'students.invite': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Регистрировать учеников',
|
|
desc: 'Создавать новые аккаунты учеников напрямую из панели',
|
|
},
|
|
'sessions.reset': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Сброс попыток',
|
|
desc: 'Сбрасывать прохождение теста ученика в своём классе',
|
|
requireConfirmOff: true,
|
|
},
|
|
'results.export': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Экспорт результатов',
|
|
desc: 'Выгружать результаты и оценки класса в CSV',
|
|
},
|
|
'classes.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Управление классами',
|
|
desc: 'Создавать, редактировать и удалять свои классы',
|
|
requireConfirmOff: true,
|
|
},
|
|
'library.upload': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Загрузка файлов',
|
|
desc: 'Загружать файлы в библиотеку',
|
|
requireConfirmOff: true,
|
|
},
|
|
'library.folders': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Управление папками',
|
|
desc: 'Создавать папки и настраивать доступ к ним',
|
|
},
|
|
'schedule.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Дедлайны заданий',
|
|
desc: 'Устанавливать дедлайны и временные окна для заданий',
|
|
},
|
|
'announcements.send': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Объявления',
|
|
desc: 'Публиковать объявления в своих классах',
|
|
},
|
|
'templates.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Управление шаблонами',
|
|
desc: 'Создавать и использовать шаблоны курсов и уроков',
|
|
},
|
|
'templates.public': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Публикация шаблонов',
|
|
desc: 'Делать свои шаблоны публичными для всех учителей',
|
|
},
|
|
'courses.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Управление курсами',
|
|
desc: 'Создавать и редактировать теоретические курсы и уроки',
|
|
requireConfirmOff: true,
|
|
},
|
|
'courses.interactive': {
|
|
role: 'teacher', roles: ['teacher'], default: 1,
|
|
label: 'Интерактивные блоки',
|
|
desc: 'Добавлять интерактивные задания в уроки (сопоставление, пропуски, порядок)',
|
|
},
|
|
'shop.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Управление магазином',
|
|
desc: 'Создавать и редактировать товары в магазине наград',
|
|
},
|
|
'gamification.manage': {
|
|
role: 'teacher', roles: ['teacher'], default: 0,
|
|
label: 'Управление геймификацией',
|
|
desc: 'Начислять XP/монеты ученикам, управлять достижениями',
|
|
},
|
|
|
|
/* ── Student (also applies to free_student — same keys, same defaults) ── */
|
|
'tests.free': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Свободные тесты',
|
|
desc: 'Проходить тесты без задания (по предмету / случайно)',
|
|
},
|
|
'board.post': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Реакции на доске',
|
|
desc: 'Ставить реакции на задания на доске',
|
|
},
|
|
'profile.edit': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Редактирование профиля',
|
|
desc: 'Изменять своё имя и пароль',
|
|
},
|
|
'shop.purchase': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Покупки в магазине',
|
|
desc: 'Покупать предметы в магазине наград за монеты',
|
|
},
|
|
'gamification.challenges': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Испытания недели',
|
|
desc: 'Участвовать в еженедельных испытаниях и получать награды',
|
|
},
|
|
'theory.access': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Доступ к теории',
|
|
desc: 'Просматривать теоретические курсы и уроки',
|
|
requireConfirmOff: true,
|
|
},
|
|
'simulations.access': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Доступ к симуляциям',
|
|
desc: 'Открывать лабораторию с физическими, химическими и биологическими симуляциями',
|
|
requireConfirmOff: true,
|
|
},
|
|
'simulations.quiz': {
|
|
role: 'student', roles: ['student', 'free_student'], default: 1,
|
|
label: 'Задания в симуляциях',
|
|
desc: 'Использовать режим "Задания" в симуляциях (квиз-режим)',
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Check whether a given permission key exists in the registry.
|
|
* Used by perm() helper in auth.js to fail early on typos.
|
|
*/
|
|
function isKnown(key) {
|
|
return Object.prototype.hasOwnProperty.call(PERMISSIONS, key);
|
|
}
|
|
|
|
/** Return all registered permission keys. */
|
|
function listKeys() {
|
|
return Object.keys(PERMISSIONS);
|
|
}
|
|
|
|
/**
|
|
* Return all entries whose primary role matches.
|
|
* Returns objects shaped like ALL_PERMISSIONS entries (key, role, default, label, desc).
|
|
*/
|
|
function byRole(role) {
|
|
return Object.entries(PERMISSIONS)
|
|
.filter(([, v]) => v.role === role)
|
|
.map(([key, v]) => ({ key, role: v.role, default: v.default, label: v.label, desc: v.desc, requireConfirmOff: !!v.requireConfirmOff }));
|
|
}
|
|
|
|
/**
|
|
* Build a PERM_DEFAULTS-style lookup: { role: { key: bool } }
|
|
* Used by auth.js requirePermission fallback.
|
|
*/
|
|
function buildDefaultsMap() {
|
|
const map = {};
|
|
for (const [key, v] of Object.entries(PERMISSIONS)) {
|
|
for (const r of v.roles) {
|
|
if (!map[r]) map[r] = {};
|
|
map[r][key] = v.default === 1;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
module.exports = { PERMISSIONS, isKnown, listKeys, byRole, buildDefaultsMap };
|