Files
IdP/apps/frontend/lib/system-settings-catalog.ts
2026-06-25 07:23:34 +03:00

157 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export type SystemSettingFieldType = 'text' | 'number' | 'boolean' | 'select' | 'textarea';
export interface SystemSettingMeta {
key: string;
label: string;
group: string;
type: SystemSettingFieldType;
unit?: string;
hint?: string;
options?: Array<{ value: string; label: string }>;
showWhen?: { key: string; equals: string };
}
export const SYSTEM_SETTING_GROUPS: Record<string, string> = {
general: 'Проект и брендинг',
pin: 'PIN-код и блокировка сессии',
auth: 'Аутентификация и регистрация',
ldap: 'LDAP / Active Directory',
messaging: 'Email и SMS',
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.domain.local или IP контроллера (192.168.x.x), если Docker не резолвит AD-имена' },
{ 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: 'EMAIL_ENABLED', label: 'Email включён', group: 'messaging', type: 'boolean', hint: 'Отправка OTP-кодов на почту через SMTP' },
{
key: 'EMAIL_PROVIDER',
label: 'Email-провайдер',
group: 'messaging',
type: 'select',
options: [
{ value: 'smtp', label: 'SMTP (Yandex, Gmail, Mail.ru и др.)' },
{ value: 'console', label: 'Только лог (отладка)' }
],
showWhen: { key: 'EMAIL_ENABLED', equals: 'true' }
},
{ key: 'EMAIL_SMTP_HOST', label: 'SMTP-хост', group: 'messaging', type: 'text', hint: 'smtp.yandex.ru, smtp.gmail.com', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_SMTP_PORT', label: 'SMTP-порт', group: 'messaging', type: 'number', unit: 'порт', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_SMTP_SECURE', label: 'SMTP SSL/TLS', group: 'messaging', type: 'boolean', hint: 'Включите для порта 465', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_SMTP_USER', label: 'SMTP-логин', group: 'messaging', type: 'text', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_SMTP_PASSWORD', label: 'SMTP-пароль', group: 'messaging', type: 'text', hint: 'App-password для Gmail/Yandex', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_FROM_ADDRESS', label: 'Email отправителя', group: 'messaging', type: 'text', hint: 'noreply@yourdomain.ru', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'EMAIL_FROM_NAME', label: 'Имя отправителя', group: 'messaging', type: 'text', showWhen: { key: 'EMAIL_ENABLED', equals: 'true' } },
{ key: 'SMS_ENABLED', label: 'SMS включён', group: 'messaging', type: 'boolean', hint: 'Отправка OTP-кодов по SMS' },
{
key: 'SMS_PROVIDER',
label: 'SMS-провайдер',
group: 'messaging',
type: 'select',
options: [
{ value: 'smsru', label: 'sms.ru' },
{ value: 'smsc', label: 'smsc.ru' },
{ value: 'huawei_modem', label: 'Huawei E5573 (локальный шлюз)' },
{ value: 'console', label: 'Только лог (отладка)' }
],
showWhen: { key: 'SMS_ENABLED', equals: 'true' }
},
{
key: 'SMS_GATEWAY_URL',
label: 'URL локального SMS-шлюза',
group: 'messaging',
type: 'text',
hint: 'IP или URL модема Huawei HiLink, например http://192.168.8.1',
showWhen: { key: 'SMS_ENABLED', equals: 'true' }
},
{
key: 'SMS_GATEWAY_USERNAME',
label: 'Логин модема Huawei',
group: 'messaging',
type: 'text',
hint: 'Обычно admin',
showWhen: { key: 'SMS_ENABLED', equals: 'true' }
},
{
key: 'SMS_GATEWAY_PASSWORD',
label: 'Пароль модема Huawei',
group: 'messaging',
type: 'text',
hint: 'Пароль веб-интерфейса модема (требуется для отправки SMS)',
showWhen: { key: 'SMS_ENABLED', equals: 'true' }
},
{
key: 'SMS_GATEWAY_MESSAGE_TEMPLATE',
label: 'Шаблон текста SMS (Huawei)',
group: 'messaging',
type: 'textarea',
hint: 'Теги: {{appname}} — название проекта, {{code}} — OTP-код, {{expiry}} — срок действия в минутах',
showWhen: { key: 'SMS_ENABLED', equals: 'true' }
},
{ key: 'SMS_API_KEY', label: 'API-ключ sms.ru', group: 'messaging', type: 'text', hint: 'api_id из личного кабинета sms.ru', showWhen: { key: 'SMS_ENABLED', equals: 'true' } },
{ key: 'SMS_LOGIN', label: 'Логин smsc.ru', group: 'messaging', type: 'text', showWhen: { key: 'SMS_ENABLED', equals: 'true' } },
{ key: 'SMS_PASSWORD', label: 'Пароль smsc.ru', group: 'messaging', type: 'text', showWhen: { key: 'SMS_ENABLED', equals: 'true' } },
{ key: 'SMS_SENDER', label: 'Имя отправителя SMS', group: 'messaging', type: 'text', hint: 'Опционально, если одобрено провайдером', showWhen: { key: 'SMS_ENABLED', equals: 'true' } },
{ 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 isSettingVisible(setting: { key: string; value: string }, allSettings: Array<{ key: string; value: string }>) {
const meta = getSettingMeta(setting.key);
if (!meta?.showWhen) return true;
const parent = allSettings.find((item) => item.key === meta.showWhen!.key);
const parentValue = parent?.value ?? 'false';
return parseBooleanValue(parentValue) === parseBooleanValue(meta.showWhen.equals);
}
function parseBooleanValue(value: string) {
return ['true', '1', 'yes'].includes(value.trim().toLowerCase());
}
export const MESSAGING_SETTING_KEYS = SYSTEM_SETTING_CATALOG.filter((item) => item.group === 'messaging').map((item) => item.key);
export const EMAIL_SETTING_KEYS = SYSTEM_SETTING_CATALOG.filter((item) =>
item.key.startsWith('EMAIL_')
).map((item) => item.key);
export const SMS_SETTING_KEYS = SYSTEM_SETTING_CATALOG.filter((item) =>
item.key.startsWith('SMS_')
).map((item) => item.key);
export function sortSettingsByCatalog<T extends { key: string }>(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));
}