b52fac1394
Экспорт в панели формировался через 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 и был единственным элементом вне палитры.
370 lines
16 KiB
Go
370 lines
16 KiB
Go
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",
|
||
},
|
||
},
|
||
},
|
||
};
|