Files
HY2XS_flamy/apps/frontend/src/lang/package/ru.ts
T
founder b52fac1394 fix(admin): убрать каналы утечки секретов и остатки H UI из runtime
Экспорт в панели формировался через os.Create в /var/lib/hy2xs-admin/export,
и файл там оставался навсегда. При includeSecrets=true это означало
расшифрованные секреты пиров — фактические учётные данные доступа — в
открытом виде на диске, накапливающиеся с каждым нажатием кнопки. Выгрузки
формируются в памяти, каталога export/ больше нет.

Generic export/import таблицы config удалён целиком. Он исключал только сырой
Hysteria YAML, а в той же таблице лежат JWT_SECRET, PEER_SECRET_KEY,
PEER_SECRET_ENCRYPTION_KEY и HYSTERIA2_TRAFFIC_STATS_SECRET: кнопка Export
выгружала их открытым текстом, импорт позволял подменить. Для
PEER_SECRET_ENCRYPTION_KEY подмена ломает расшифровку секретов уже
существующих пиров. Production-сценария у этой пары не было.

Импорт пиров шёл мимо всей валидации, которую проходит обычное создание пира:
в базу попадало имя любой длины и с любыми символами, disabled с произвольным
числом, отрицательные счётчики. Файл применялся построчно, поэтому ошибка в
середине оставляла список наполовину изменённым, а импорт мог перезаписать
bootstrap-admin-peer, чей секрет продублирован в bootstrap-admin.secret.
Партия проверяется целиком до первой записи, неизвестные поля отклоняются.

Убран слой сетевых настроек панели: H_UI_WEB_PORT, H_UI_WEB_CONTEXT,
H_UI_CRT_PATH, H_UI_KEY_PATH и собственный TLS. Оркестратор передавал порт
аргументом, админка писала его в SQLite и тут же читала обратно, а UI
показывал поля disabled — второй источник истины, из которого ничего нельзя
было изменить. HUI_DATA/HUI_LOG заменены на HY2XS_DATA_DIR/HY2XS_LOG_DIR,
база переименована в hy2xs-admin.db, reference-схема — в schema.sql.

API namespace разделён по природе маршрутов: операторский API на /api,
machine-auth Hysteria на /internal/hysteria/auth. Путь machine-auth —
runtime-контракт, он уезжает в config.yaml и post-install.env, поэтому
объявлен одной константой на компонент.

Go-санитайзер экспорта вырезал секреты из URL только у ключей url/addr:
будущее upstream-поле с другим именем уносило учётные данные и access_token
целиком, а URL внутри списков не обрабатывались вовсе. Граница определяется
значением, а не именем ключа — как в TS-санитайзере оркестратора.

Заодно индикатор загрузки и цвета 401/404 переведены на брендовый токен:
NProgress приходил со своим #29d и был единственным элементом вне палитры.
2026-08-27 20:49:07 +05:00

370 lines
16 KiB
Go
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 default {
route: {
dashboard: "Дашборд",
peer: "Пиры",
peerList: "Управление пирами",
hysteria: "Hysteria",
hysteriaList: "Управление Hysteria",
config: "Система",
configList: "Настройки системы",
monitor: "Мониторинг",
monitorSystem: "Системный мониторинг",
log: "Логи",
logSystem: "Системные логи",
logHysteria: "Логи Hysteria",
},
login: {
title: "HY2XS admin",
username: "Логин",
password: "Пароль",
login: "Войти",
capsLockOn: "Caps Lock включён",
usernameFormatIncorrect: "Неверный формат логина",
passwordFormatIncorrect: "Неверный формат пароля",
},
dashboard: {
stale:
"Данные дашборда устарели. Выполняется автоматическая повторная попытка...",
topPeers24h: "Топ пиров (24ч)",
topPeersRange: "Топ пиров ({range})",
refreshFailed: "Не удалось обновить данные дашборда",
cpu: "CPU",
ram: "RAM",
disk: "Диск",
peers: "Пиры",
onlinePeers: "Пиры онлайн",
onlineDevices: "Устройства онлайн",
todayDownload: "Скачано за сегодня",
todayUpload: "Отдано за сегодня",
trafficChart: "Трафик и системные метрики",
trafficSeriesChart: "Трафик: download/upload",
noTrafficData: "Нет данных трафика за выбранный период",
systemSeriesChart: "Система: CPU/RAM",
download: "Скачано",
upload: "Отдано",
total: "Всего",
security: {
hysteriaStopped: "Hysteria2 остановлена",
trafficApiUnavailable: "Traffic API недоступен",
},
error: {
trafficApiUnavailable: "Traffic API недоступен",
collectorStale: "Данные коллектора устарели",
},
},
admin: {
changePasswordTitle: "Смена пароля",
oldPassword: "Старый пароль",
newPassword: "Новый пароль",
forcePasswordChangeNotice: "Перед продолжением необходимо сменить пароль.",
changedSuccess: "Пароль обновлён",
},
navbar: {
logout: "Выйти",
},
common: {
id: "ID",
createTime: "Создано",
operate: "Действия",
edit: "Изменить",
delete: "Удалить",
deleted: "Статус",
all: "Все",
enable: "Включено",
disable: "Отключено",
search: "Поиск",
reset: "Сброс",
add: "Добавить",
confirm: "Подтвердить",
cancel: "Отмена",
copySuccess: "Скопировано",
nodeUrl: "URL узла",
nodeQrCode: "QR узла",
resetTraffic: "Сбросить трафик",
import: "Импорт",
export: "Экспорт",
save: "Сохранить",
update: "Обновить",
downloadSuccess: "Загрузка завершена",
wait: "Версия меняется, подождите",
enableSuccess: "Hysteria2 запущена",
disableSuccess: "Hysteria2 остановлена",
success: "Готово",
refresh: "Обновить",
yes: "Да",
no: "Нет",
securityRisk: "Риски безопасности",
required: "Обязательное поле",
warning: "Внимание",
fileFormatUnsupported: "Формат файла не поддерживается",
fileTooLarge: "Файл слишком большой, не более 2 МБ",
weekLater: "Через неделю",
monthLater: "Через месяц",
yearLater: "Через год",
hourLater: "Через час",
dayLater: "Через день",
deleteConfirm: "Удалить пир «{username}»?",
resetTrafficConfirm: "Сбросить трафик для пира?",
invalid: "Некорректное значение",
switchLanguageSuccess: "Язык переключён",
logoutConfirm: "Выйти из системы?",
sessionExpired: "Текущая сессия истекла, войдите снова",
},
info: {
expireTime: "г-М-д Ч:м:с",
greeting1: "Доброе утро,",
greeting2: "Доброе утро,",
greeting3: "Добрый день,",
greeting4: "Добрый вечер,",
greeting5: "Доброй ночи,",
},
peer: {
name: "Пир",
remark: "Комментарий",
secret: "Секрет",
form: {
namePlaceholder: "например, ivan-laptop",
nameHint:
"Короткий идентификатор пира. Используйте латиницу, цифры и дефис — имя попадает в автогенерируемый секрет.",
remarkPlaceholder: "например, Ноутбук Ивана, отдел продаж",
remarkHint: "Необязательная пометка для оператора. Клиент её не видит.",
secretPlaceholder: "оставьте пустым — сгенерируем автоматически",
secretHint:
"Пароль подключения клиента. Если оставить поле пустым, секрет будет сгенерирован автоматически. При ручном вводе: от 6 до 128 символов.",
quotaHint: "Лимит трафика в байтах. Укажите -1 для безлимита.",
},
maxDevices: "Лимит устройств",
disabled: "Отключён",
status: "Статус",
traffic: "Трафик",
devices: "Устройства",
lastConnectionAt: "Последнее подключение",
overview: "Обзор",
authId: "Auth ID",
clientUri: "URI клиента",
clientQr: "QR клиента",
unlimited: "Безлимит",
secretRotateConfirm:
"Сменить секрет пира? Текущие клиентские конфиги перестанут работать до обновления.",
quota: "Квота",
download: "Скачано",
upload: "Отдано",
expireTime: "Срок действия",
kickUtilTimeLast: "Осталось офлайн",
kickUtilTime: "Отключить до",
deviceNo: "Лимит устройств",
onlineStatus: "Онлайн",
online: "Онлайн",
offline: "Офлайн",
device: "Устройства",
createdAt: "Создан",
bannedUntil: "Блокировка до",
totalTraffic: "Суммарный трафик",
copyUri: "Копировать URI",
more: "Ещё",
unit: "Ед. изм.",
loginAt: "Последний вход",
conAt: "Последнее подключение",
createTime: "Создано",
releaseSuccess: "Ограничение снято",
kick: "Отключить",
kickTip: "Принудительно отключить пользователя",
releaseKick: "Снять",
releaseKickTip: "Снять офлайн-статус",
},
config: {
orchestratorManaged: "Управляется hy2xs-orchestrator reconfigure",
hysteria2TrafficTime: "Период учёта трафика Hysteria2",
resetTrafficCron: "Расписание сброса трафика",
resetTrafficCronTip: "Cron-выражение для планового сброса трафика",
resetTrafficMonth: "Раз в месяц, в полночь первого дня",
resetTrafficWeek: "Раз в неделю, в полночь между субботой и воскресеньем",
invalidTrafficTime:
"Поле должно быть числом максимум с одним знаком после запятой",
},
monitor: {
cpuPercent: "CPU",
memPercent: "Память",
diskPercent: "Диск",
hysteria2UserTotal: "Пользователей онлайн",
hysteria2DeviceTotal: "Устройств онлайн",
hysteria2Version: "Версия Hysteria2",
hysteria2Running: "Статус Hysteria2",
hysteria2RunningTrue: "Работает",
hysteria2RunningFalse: "Остановлена",
},
log: {
numLine: "Количество строк",
level: "Уровень",
message: "Сообщение",
time: "Время",
},
errorPage: {
back: "Назад",
oops: "Упс!",
forbiddenTitle: "У вас нет прав для доступа к этой странице",
forbiddenGo: "Или вы можете перейти:",
backHome: "На главную",
forbiddenImageAlt: "Девочка уронила своё мороженое.",
notFoundHeadline: "Вебмастер сообщил, что вам нельзя на эту страницу...",
notFoundInfo:
"Проверьте правильность URL или нажмите кнопку ниже, чтобы вернуться на главную страницу.",
notFoundBackHome: "На главную",
},
hysteria: {
enable: "Включить",
disable: "Отключить",
addConfigItem: "Добавить параметр",
hysteria2Version: "Версия Hysteria2",
hysteria2Running: "Статус Hysteria2",
addOutbound: "Добавить outbound",
extension: "Расширение",
listen: "Адрес прослушивания",
tls: "TLS",
obfs: "Маскировка",
quic: "Параметры QUIC",
bandwidth: "Полоса",
congestion: "Congestion control",
speedTest: "Тест скорости",
udp: "UDP",
resolver: "DNS",
sniff: "Sniffing протоколов",
acl: "ACL",
outbounds: "Outbounds",
http: "Traffic Stats API (HTTP)",
masquerade: "Masquerade",
config: {
enable: "Включить/отключить",
remark: "Комментарий",
listen: "Адрес и порт прослушивания. Для IPv4 используйте 0.0.0.0:443.",
tlsType: "Тип TLS",
tls: {
cert: "Путь к cert-файлу",
key: "Путь к key-файлу",
sniGuard: "Проверка SNI клиента перед принятием TLS-соединения.",
},
acme: {
domains: "Домены",
email: "Email",
ca: "CA: letsencrypt или zerossl",
listenHost: "Адрес для ACME challenge",
dir: "Каталог ACME аккаунта и сертификатов",
type: "Тип ACME challenge: http, tls или dns",
http: { altPort: "Альтернативный порт HTTP challenge" },
tls: { altPort: "Альтернативный порт TLS-ALPN challenge" },
dns: { name: "DNS-провайдер", config: "Конфигурация ACME DNS" },
disableHTTP: "Отключить HTTP challenge",
disableTLSALPN: "Отключить TLS-ALPN challenge",
altHTTPPort: "Альтернативный HTTP-порт",
altTLSALPNPort: "Альтернативный TLS-ALPN-порт",
},
obfs: {
type: "Тип обфускации: gecko (production default HY2XS) или salamander (совместимость)",
salamander: { password: "Сильный пароль Salamander" },
gecko: {
password: "Сильный пароль Gecko",
minPacketSize:
"Минимальный размер фрагмента QUIC handshake. Baseline HY2XS: 512",
maxPacketSize:
"Максимальный размер фрагмента QUIC handshake. Baseline HY2XS: 1200, верхний предел upstream: 2048",
},
},
quic: {
initStreamReceiveWindow: "Начальное окно приёма QUIC stream",
maxStreamReceiveWindow: "Максимальное окно приёма QUIC stream",
initConnReceiveWindow: "Начальное окно приёма QUIC connection",
maxConnReceiveWindow: "Максимальное окно приёма QUIC connection",
maxIdleTimeout: "Максимальный idle timeout",
maxIncomingStreams: "Максимум входящих stream",
disablePathMTUDiscovery: "Отключить QUIC path MTU discovery",
disableStatelessReset:
"Отключить QUIC stateless reset. В baseline HY2XS выключено: stateless reset ускоряет переподключение клиента после перезапуска сервера или сна устройства",
},
bandwidth: {
up: "Вверх",
down: "Вниз",
disableLossCompensation:
"Отключить компенсацию потерь. В baseline HY2XS выключено, то есть компенсация работает",
},
congestion: {
type: "Fallback congestion controller: bbr или reno. Применяется, когда Brutal bandwidth не согласован сторонами",
bbrProfile: "Профиль BBR: standard, conservative или aggressive",
},
ignoreClientBandwidth: "Игнорировать bandwidth, заявленный клиентом",
speedTest: "Встроенный сервер теста скорости",
disableUDP: "Отключить UDP forwarding",
udpIdleTimeout: "Idle timeout для UDP-сессий",
resolver: {
type: "Тип",
tcp: { addr: "TCP DNS resolver", timeout: "Timeout DNS-запросов" },
udp: { addr: "UDP DNS resolver", timeout: "Timeout DNS-запросов" },
tls: {
addr: "DNS over TLS resolver",
timeout: "Timeout DNS-запросов",
sni: "SNI для TLS resolver",
insecure: "Отключить TLS-проверку",
},
https: {
addr: "DNS over HTTPS resolver",
timeout: "Timeout DNS-запросов",
sni: "SNI для HTTPS resolver",
insecure: "Отключить TLS-проверку",
},
},
sniff: {
enable: "Включить sniffing",
timeout: "Timeout sniffing",
rewriteDomain: "Повторно анализировать доменные запросы",
tcpPorts: "TCP-порты для sniffing",
udpPorts: "UDP-порты для sniffing",
},
aclType: "Тип ACL",
acl: {
file: "Путь к ACL-файлу",
inline: "Inline ACL-правила",
geoip: "Путь к GeoIP базе",
geosite: "Путь к GeoSite базе",
geoUpdateInterval: "Интервал обновления GeoIP/GeoSite",
},
outbounds: {
name: "Имя outbound",
type: "Тип",
socks5: {
addr: "Адрес SOCKS5",
username: "Логин SOCKS5",
password: "Пароль SOCKS5",
},
http: {
url: "URL HTTP/HTTPS proxy",
insecure: "Отключить TLS-проверку proxy",
},
direct: {
mode: "Тип",
bindIPv4: "Локальный IPv4",
bindIPv6: "Локальный IPv6",
bindDevice: "Сетевой интерфейс",
fastOpen: "TCP fast open",
},
},
trafficStats: { listen: "Адрес прослушивания" },
masquerade: {
type: "Тип",
file: { dir: "Каталог файлов" },
proxy: {
url: "URL проксируемого сайта",
rewriteHost: "Переписывать Host header",
insecure: "Отключить TLS-проверку",
},
string: {
content: "Ответ строкой",
headers: "HTTP headers",
statusCode: "HTTP status code",
},
listenHTTP: "HTTP listen address",
listenHTTPS: "HTTPS listen address",
forceHTTPS: "Принудительно использовать HTTPS",
},
},
},
};