Аудит: читаемые имена и «было → стало» вместо сырых данных #28

Merged
rust merged 8 commits from t25_audit_readable into main 2026-09-14 23:36:11 +03:00
2 changed files with 77 additions and 1 deletions
Showing only changes of commit ac9d92d196 - Show all commits
@@ -23,6 +23,17 @@ const COLUMNS = [
const ACTOR_TONES = { operator: 'brand', tenant: 'neutral', system: 'warn' } const ACTOR_TONES = { operator: 'brand', tenant: 'neutral', system: 'warn' }
// Код поля «изменённые настройки» устаревшего формата деталей.
const FIELD_SETTINGS = 'fields'
// Поля-ссылки на контейнер и служебные контейнеры с человекочитаемыми именами.
const CONTAINER_FIELDS = new Set(['to', 'col', 'containerId'])
const CONTAINER_VALUE_KEYS = {
inbox: 'operator.columnValue.inbox',
archive: 'operator.columnValue.archive',
trash: 'operator.columnValue.trash',
}
// Строковые представления булевых значений в деталях события (как в JSON). // Строковые представления булевых значений в деталях события (как в JSON).
const TRUE_VALUE = 'true' const TRUE_VALUE = 'true'
const FALSE_VALUE = 'false' const FALSE_VALUE = 'false'
@@ -53,9 +64,20 @@ function valueLabel(field, raw) {
if (raw === null || raw === undefined || raw === '') return '—' if (raw === null || raw === undefined || raw === '') return '—'
if (raw === TRUE_VALUE) return t('operator.da') if (raw === TRUE_VALUE) return t('operator.da')
if (raw === FALSE_VALUE) return t('operator.net') if (raw === FALSE_VALUE) return t('operator.net')
if (CONTAINER_FIELDS.has(field) && CONTAINER_VALUE_KEYS[raw]) return t(CONTAINER_VALUE_KEYS[raw])
return translate(`operator.value.${field}.${raw}`, raw) return translate(`operator.value.${field}.${raw}`, raw)
} }
// Список ключей настроек из устаревшего формата → русские названия через запятую.
function settingList(raw) {
return String(raw)
.split(',')
.map((key) => key.trim())
.filter(Boolean)
.map((key) => translate(`operator.setting.${key}`, key))
.join(', ')
}
function changesOf(row) { function changesOf(row) {
return Array.isArray(row.changes) ? row.changes : [] return Array.isArray(row.changes) ? row.changes : []
} }
@@ -63,6 +85,10 @@ function changesOf(row) {
// Строка изменения: «Поле: было → стало» либо «Поле: значение». // Строка изменения: «Поле: было → стало» либо «Поле: значение».
function changeLine(change) { function changeLine(change) {
const field = fieldLabel(change.field) const field = fieldLabel(change.field)
if (change.field === FIELD_SETTINGS) {
return `${field}: ${settingList(change.to)}`
}
const to = valueLabel(change.field, change.to) const to = valueLabel(change.field, change.to)
return hasFrom(change) return hasFrom(change)
? `${field}: ${valueLabel(change.field, change.from)}${to}` ? `${field}: ${valueLabel(change.field, change.from)}${to}`
+51 -1
View File
@@ -879,7 +879,7 @@ export const ru = {
"status": "Статус", "status": "Статус",
"targetLogin": "Вход", "targetLogin": "Вход",
"cardId": "Карточка", "cardId": "Карточка",
"to": "Куда", "to": "Колонка",
"col": "Колонка", "col": "Колонка",
"containerId": "Колонка", "containerId": "Колонка",
"attempted": "Обработано", "attempted": "Обработано",
@@ -892,6 +892,56 @@ export const ru = {
"phase": "Этап", "phase": "Этап",
"dialogId": "Диалог" "dialogId": "Диалог"
}, },
"setting": {
"archiveAfterDays": "Срок до архива",
"archiveClearDays": "Очистка архива, дней",
"trashClearDays": "Очистка корзины, дней",
"minLen": "Минимальная длина сообщения",
"discJoinLimit": "Суточный лимит вступлений",
"discJoinDelayMin": "Мин. задержка вступления, сек",
"discJoinDelayMax": "Макс. задержка вступления, сек",
"discEvalSample": "Сообщений для оценки",
"discEvalThreshold": "Порог соответствия, %",
"excludeBudgetFrom": "Исключать бюджет от",
"excludeBudgetTo": "Исключать бюджет до",
"autoArchive": "Автоперенос в архив",
"aiEnabled": "Использовать ИИ",
"aiFilterEnabled": "ИИ-фильтр входящих",
"conversionOn": "Конвертация валют",
"remindersEnabled": "Напоминания",
"mlEnabled": "ML в пайплайне",
"blockResumes": "Блокировать резюме",
"budgetRequiredHire": "Требовать бюджет для вакансий",
"budgetRequiredOrder": "Требовать бюджет для фриланса",
"autoMonitorNew": "Проверять новые каналы автоматически",
"discPaused": "Поиск каналов на паузе",
"targetCurrency": "Целевая валюта",
"rateSource": "Источник курсов",
"aiProvider": "Провайдер ИИ",
"aiPrompt": "Промпт классификатора",
"aiFilterPrompt": "Промпт фильтра",
"cardPrompt": "Промпт структуры карточки",
"domainDescription": "Описание домена",
"wantedType": "Тип заявок",
"hireLabel": "Метка вакансии",
"orderLabel": "Метка фриланса",
"stopPhrases": "Стоп-фразы",
"domainKeywords": "Ключевые слова домена",
"hireMarkers": "Маркеры найма",
"levelTerms": "Термины уровней",
"resumeMarkers": "Маркеры резюме",
"excludeKeywords": "Исключающие слова",
"excludeLocations": "Исключающие локации",
"excludeTypes": "Исключающие типы",
"colState": "Состояние колонок",
"myPrompts": "Мои промпты",
"aiConfigs": "Провайдеры ИИ"
},
"columnValue": {
"inbox": "Неразобранное",
"archive": "Архив",
"trash": "Корзина"
},
"value": { "value": {
"status": { "status": {
"active": "Активен", "active": "Активен",