export type SystemSettingFieldType = 'text' | 'number' | 'boolean'; export interface SystemSettingMeta { key: string; label: string; group: string; type: SystemSettingFieldType; unit?: string; hint?: string; } export const SYSTEM_SETTING_GROUPS: Record = { general: 'Проект и брендинг', pin: 'PIN-код и блокировка сессии', auth: 'Аутентификация и регистрация', ldap: 'LDAP / Active Directory', limits: 'Лимиты и ограничения', media: 'Медиа и файлы' }; export const SYSTEM_SETTING_CATALOG: SystemSettingMeta[] = [ { key: 'PROJECT_NAME', label: 'Название проекта', group: 'general', type: 'text', hint: 'Отображается в меню, заголовке и боковой панели' }, { key: 'PROJECT_TAGLINE', label: 'Слоган проекта', group: 'general', type: 'text' }, { key: 'PROJECT_DOMAIN', label: 'Домен IdP', group: 'general', type: 'text' }, { key: 'PIN_LOCK_TIMEOUT_MINUTES', label: 'Таймаут блокировки PIN', group: 'pin', type: 'number', unit: 'мин' }, { key: 'PIN_DELETE_GRACE_MINUTES', label: 'Задержка удаления PIN', group: 'pin', type: 'number', unit: 'мин', hint: 'Сколько ждать после запроса удаления PIN-кода' }, { key: 'PIN_REQUIRE_ON_DELETE', label: 'Требовать PIN при удалении', group: 'pin', type: 'boolean' }, { key: 'PIN_MIN_LENGTH', label: 'Мин. длина PIN', group: 'pin', type: 'number', unit: 'цифр' }, { key: 'PIN_MAX_LENGTH', label: 'Макс. длина PIN', group: 'pin', type: 'number', unit: 'цифр' }, { key: 'OTP_EXPIRY_MINUTES', label: 'Срок жизни OTP', group: 'auth', type: 'number', unit: 'мин' }, { key: 'OTP_MAX_ATTEMPTS', label: 'Попыток ввода OTP', group: 'auth', type: 'number' }, { key: 'SESSION_REFRESH_DAYS', label: 'Срок refresh-токена', group: 'auth', type: 'number', unit: 'дн' }, { key: 'PASSWORD_MIN_LENGTH', label: 'Мин. длина пароля', group: 'auth', type: 'number', unit: 'симв' }, { key: 'REGISTRATION_ENABLED', label: 'Регистрация включена', group: 'auth', type: 'boolean' }, { key: 'MAINTENANCE_MODE', label: 'Режим обслуживания', group: 'auth', type: 'boolean', hint: 'Блокирует вход для обычных пользователей' }, { key: 'LDAP_ENABLED', label: 'LDAP включён', group: 'ldap', type: 'boolean', hint: 'Показывает кнопку входа через LDAP на странице авторизации' }, { key: 'LDAP_USE_LDAPS', label: 'Использовать LDAPS', group: 'ldap', type: 'boolean', hint: 'Шифрованное подключение (порт 636)' }, { key: 'LDAP_HOST', label: 'Хост LDAP', group: 'ldap', type: 'text', hint: 'DC-1.local,DC-2.local или ldaps://DC-1.local:636' }, { key: 'LDAP_PORT', label: 'Порт LDAP', group: 'ldap', type: 'number', hint: '636 для LDAPS, 389 для LDAP. Не используйте 640.' }, { key: 'LDAP_DOMAIN', label: 'Домен AD', group: 'ldap', type: 'text', hint: 'mvkug.local — для bind вида user@domain' }, { key: 'LDAP_SKIP_TLS_VERIFY', label: 'Не проверять TLS', group: 'ldap', type: 'boolean', hint: 'Только для тестовых окружений' }, { key: 'LDAP_BASE_DN', label: 'Base DN (поиск пользователей)', group: 'ldap', type: 'text', hint: 'OU или CN контейнера с пользователями. Напр. OU=Lugansk,DC=mvkug,DC=local. Если CN не работает — попробуйте OU' }, { key: 'LDAP_BIND_USERNAME', label: 'Логин сервисной УЗ', group: 'ldap', type: 'text', hint: 'mvkadmin или mvkug\\mvkadmin — учётная запись для подключения к AD' }, { key: 'LDAP_BIND_DN', label: 'Bind DN (опционально)', group: 'ldap', type: 'text', hint: 'Полный DN сервисной УЗ. Оставьте пустым, если указан логин выше' }, { key: 'LDAP_BIND_PASSWORD', label: 'Пароль сервисной УЗ', group: 'ldap', type: 'text', hint: 'Секретное значение' }, { key: 'LDAP_USER_FILTER', label: 'Фильтр пользователя', group: 'ldap', type: 'text', hint: 'Placeholder {username}' }, { key: 'LDAP_USERNAME_ATTR', label: 'Атрибут логина', group: 'ldap', type: 'text' }, { key: 'LDAP_EMAIL_ATTR', label: 'Атрибут почты', group: 'ldap', type: 'text' }, { key: 'LDAP_DISPLAY_NAME_ATTR', label: 'Атрибут имени', group: 'ldap', type: 'text' }, { key: 'MAX_FAMILY_MEMBERS', label: 'Макс. участников семьи', group: 'limits', type: 'number' }, { key: 'MAX_ACTIVE_SESSIONS', label: 'Макс. активных сессий', group: 'limits', type: 'number' }, { key: 'AVATAR_URL_TTL_MINUTES', label: 'TTL ссылки на аватар', group: 'media', type: 'number', unit: 'мин' } ]; export function getSettingMeta(key: string) { return SYSTEM_SETTING_CATALOG.find((item) => item.key === key); } export function sortSettingsByCatalog(settings: T[]) { const order = new Map(SYSTEM_SETTING_CATALOG.map((item, index) => [item.key, index])); return [...settings].sort((a, b) => (order.get(a.key) ?? 999) - (order.get(b.key) ?? 999)); }