feat(secrets): scoped shared secrets rule-management UI (Phase 2)

Completes scoped shared secrets end-to-end: /shared-secrets list/new/edit
routes (mirroring metric-alert-rules) with an env-key name, a WRITE-ONLY
value (password input; never pre-filled — the API returns only has_value;
omitted on PATCH to keep the stored secret, provided to rotate; cleared
after save), an encrypted toggle (flipping it requires re-entering the
value, matching the server's 400 guard), a global|app scope with an
App-grouping picker (listApps), description, and enabled. 409 conflicts
surface a friendly message. New "System" nav entry (IconKey) + api.ts
client + full sharedsecrets.* i18n (en/ru parity).

Reviewed: typescript APPROVE (0 CRITICAL/HIGH).
This commit is contained in:
2026-05-29 16:11:46 +03:00
parent fa6d5bd3ba
commit 15e5b186cd
7 changed files with 1990 additions and 1 deletions
+91
View File
@@ -18,6 +18,7 @@
"eventTriggers": "Триггеры событий",
"logScanRules": "Лог-правила",
"metricAlertRules": "Метрик-алерты",
"sharedSecrets": "Общие секреты",
"triggers": "Триггеры",
"proxies": "Прокси",
"events": "События",
@@ -987,6 +988,96 @@
"disabled": "отключено"
}
},
"sharedsecrets": {
"eyebrow": "Система",
"title": "Общие секреты",
"titleNew": "Создать новый секрет",
"titleSingular": "Общий секрет",
"lede": "Именованные переменные окружения, которые реконсилятор внедряет в подходящие нагрузки. Задайте секрету глобальную область, чтобы он применялся ко всем приложениям, или ограничьте его одним приложением. Значения доступны только для записи — они сохраняются один раз и никогда не возвращаются. Включено {enabled} из {total}.",
"ledeNew": "Укажите секрету ключ переменной окружения и значение. Задайте глобальную область, чтобы применить его ко всем приложениям, или ограничьте одним приложением. Значение доступно только для записи — после сохранения оно больше не отображается.",
"stat": {
"total": "ВСЕГО",
"global": "ГЛОБАЛЬНЫЕ",
"app": "ПРИЛОЖЕНИЕ",
"enabled": "ВКЛЮЧЕНО"
},
"toolbar": {
"newButton": "Новый секрет",
"backToList": "К списку секретов"
},
"filter": {
"all": "ВСЕ",
"global": "ГЛОБАЛЬНЫЕ",
"app": "ПРИЛОЖЕНИЕ"
},
"empty": {
"heading": "Пока нет общих секретов",
"body": "Начните с глобального секрета, например DATABASE_URL, затем сузьте его, ограничив секрет отдельным приложением.",
"cta": "Создать первый секрет"
},
"list": {
"name": "Название",
"scope": "Область",
"value": "Значение",
"encrypted": "Шифрование",
"status": "Статус",
"open": "Открыть",
"valueSet": "задано",
"valueNone": "—",
"encOn": "зашифровано",
"encOff": "открыто"
},
"detail": {
"config": "Конфигурация",
"configSub": "область {scope}",
"dangerZone": "Опасная зона",
"dangerZoneSub": "Удаление общего секрета немедленно убирает его и прекращает внедрение.",
"deleteButton": "Удалить секрет",
"deleteTitle": "Удалить общий секрет?",
"deleteMessage": "Секрет «{name}» будет удалён немедленно и перестанет внедряться."
},
"form": {
"name": "Название",
"namePlaceholder": "напр. DATABASE_URL",
"nameHint": "Ключ переменной окружения, внедряемый в подходящие нагрузки.",
"value": "Значение",
"valuePlaceholder": "Введите значение секрета",
"valuePlaceholderEdit": "Оставьте пустым, чтобы сохранить текущее значение",
"valueHintNew": "Значение секрета. Хранится только для записи — после сохранения больше не отображается.",
"valueHintEditSet": "Значение задано. Оставьте пустым, чтобы сохранить его, или введите новое для замены.",
"valueHintEditUnset": "Значение ещё не сохранено. Введите его, чтобы задать.",
"encrypted": "Шифрование",
"encryptedHint": "Зашифрованные секреты хранятся запечатанными и расшифровываются только при внедрении. Отключайте только для не секретных значений конфигурации.",
"encryptedFlipWarning": "Изменение настройки шифрования требует повторного ввода значения. Введите значение выше, чтобы сохранить.",
"scope": "Область",
"scopeGlobalOption": "Глобально — все приложения",
"scopeAppOption": "Приложение — одно приложение",
"scopeAppEmpty": "Приложение не выбрано",
"scopePick": "Выбрать приложение…",
"scopePickTitle": "Выберите приложение",
"scopeClear": "Очистить приложение",
"scopeSelected": "Приложение",
"scopeUnknown": "Неизвестное приложение",
"scopeHint": "Глобальные секреты применяются ко всем приложениям. Секреты с областью приложения применяются только к нагрузкам этого приложения.",
"description": "Описание",
"descriptionPlaceholder": "Необязательная заметка о назначении секрета",
"enabled": "Включено",
"enabledHint": "Отключённые секреты остаются в таблице, но не внедряются.",
"required": "ОБЯЗАТЕЛЬНО",
"optional": "НЕОБЯЗАТЕЛЬНО",
"submit": "Создать секрет",
"submitting": "Создаём…",
"conflict": "Общий секрет с такой областью и названием уже существует."
},
"scope": {
"global": "Глобально",
"app": "Приложение · {name}"
},
"status": {
"enabled": "включено",
"disabled": "отключено"
}
},
"logscan": {
"title": "Правила сканирования логов",
"titleNew": "Новое правило",