fix(admin): связать отзыв учётных данных с идентичностью сессий и свести адрес control plane к одному
Отзыв секрета не сходился: `auth_id` при смене секрета оставался прежним, поэтому сессия, установленная по отозванным учётным данным, была неотличима от законной, и цикл учёта не имел признака, по которому её следовало завершить. У состояния есть путь без единой неудачи — Hysteria регистрирует соединение в Traffic Stats API только после возврата backend-auth, поэтому успешный /kick может пройти мимо. Новое поколение credentials получает новый auth_id, kick идёт по старому, пережившая сессия становится orphan. Адрес Traffic Stats API имел два контракта: оркестратор принимал любой IPv4, админка всегда шла на loopback. Валидная по всем гейтам конфигурация выключала лимит устройств, учёт трафика и принудительное отключение разом. Адрес зафиксирован, а расхождение файла с ним админка называет. Состояние службы стало трёхзначным: util.Exec выбрасывал вывод systemctl при ненулевом коде, поэтому «остановлена» и «спросить не удалось» приходили одним значением, а доступность Traffic Stats API выводилась из него же. Журнал Hysteria разбирается в фактическом формате upstream (time — дробное число), страница конфигурации показывает файл вместо дефолтов UI и не возит секреты в браузер, санитайзер выгрузки следует по YAML-якорям. Разбор: docs/acceptance/2026-09-02-v1.0.0-rc4-preflight-findings.md
This commit is contained in:
@@ -43,8 +43,21 @@ export default {
|
||||
download: "Скачано",
|
||||
upload: "Отдано",
|
||||
total: "Всего",
|
||||
serviceStateLabel: "Служба Hysteria",
|
||||
// Три состояния, а не два: «спросить systemd не удалось» — это не
|
||||
// «служба остановлена», и действия оператора у них разные.
|
||||
serviceState: {
|
||||
active: "Работает",
|
||||
inactive: "Остановлена",
|
||||
unknown: "Состояние неизвестно",
|
||||
},
|
||||
trafficApiLabel: "Traffic Stats API",
|
||||
trafficApiReachable: "Доступен",
|
||||
trafficApiUnreachable: "Недоступен",
|
||||
security: {
|
||||
hysteriaStopped: "Hysteria2 остановлена",
|
||||
hysteriaStateUnknown:
|
||||
"Состояние службы Hysteria2 неизвестно: systemd не ответил",
|
||||
trafficApiUnavailable: "Traffic API недоступен",
|
||||
},
|
||||
error: {
|
||||
@@ -243,13 +256,16 @@ export default {
|
||||
onlineStatus: "Онлайн",
|
||||
online: "Онлайн",
|
||||
offline: "Офлайн",
|
||||
onlineUnknown: "Онлайн неизвестен",
|
||||
onlineUnavailable: "Картина подключений сейчас недоступна",
|
||||
onlineUnavailableHint:
|
||||
"Traffic Stats API Hysteria не ответил, поэтому онлайн и число устройств неизвестны. Сохранённое состояние пиров в таблице верно.",
|
||||
device: "Устройства",
|
||||
createdAt: "Создан",
|
||||
bannedUntil: "Блокировка до",
|
||||
totalTraffic: "Суммарный трафик",
|
||||
copyUri: "Копировать URI",
|
||||
more: "Ещё",
|
||||
unit: "Ед. изм.",
|
||||
loginAt: "Последний вход",
|
||||
conAt: "Последнее подключение",
|
||||
createTime: "Создано",
|
||||
@@ -278,17 +294,10 @@ export default {
|
||||
resetTrafficMonth: "Раз в месяц, в полночь первого дня",
|
||||
resetTrafficWeek: "Раз в неделю, в полночь между субботой и воскресеньем",
|
||||
},
|
||||
monitor: {
|
||||
cpuPercent: "CPU",
|
||||
memPercent: "Память",
|
||||
diskPercent: "Диск",
|
||||
hysteria2UserTotal: "Пользователей онлайн",
|
||||
hysteria2DeviceTotal: "Устройств онлайн",
|
||||
hysteria2Version: "Версия Hysteria2",
|
||||
hysteria2Running: "Статус Hysteria2",
|
||||
hysteria2RunningTrue: "Работает",
|
||||
hysteria2RunningFalse: "Остановлена",
|
||||
},
|
||||
// Раздел `monitor` удалён вместе со своим единственным потребителем —
|
||||
// плашкой «Статус Hysteria2» на странице конфигурации. Состояние службы
|
||||
// имеет три значения, а не два, и его фразы живут в `dashboard.serviceState`:
|
||||
// «остановлена» и «неизвестно» — разные ответы оператору.
|
||||
log: {
|
||||
numLine: "Количество строк",
|
||||
level: "Уровень",
|
||||
@@ -309,157 +318,35 @@ export default {
|
||||
notFoundBackHome: "На главную",
|
||||
},
|
||||
hysteria: {
|
||||
enable: "Включить",
|
||||
disable: "Отключить",
|
||||
addConfigItem: "Добавить параметр",
|
||||
hysteria2Version: "Версия Hysteria2",
|
||||
hysteria2Running: "Статус Hysteria2",
|
||||
// addOutbound удалён вместе с редактором outbounds: страница read-only,
|
||||
// маршрутов записи серверного конфига в API нет.
|
||||
outboundsEmpty: "Outbounds в конфигурации сервера не заданы",
|
||||
ownedByOrchestrator: "Конфигурацией Hysteria владеет установщик",
|
||||
ownedByOrchestratorHint:
|
||||
"Панель показывает файл /etc/hysteria/config.yaml только на чтение. Изменения вносит `hy2xs-orchestrator reconfigure`.",
|
||||
// Разделы production-профиля. Экран показывает то, что записано в файле,
|
||||
// и отдельно называет секции сверх профиля: универсального редактора всех
|
||||
// возможностей Hysteria здесь нет намеренно — конфиг создаёт оркестратор.
|
||||
listen: "Адрес прослушивания",
|
||||
auth: "Авторизация пиров",
|
||||
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: {
|
||||
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",
|
||||
},
|
||||
},
|
||||
trafficStats: "Traffic Stats API",
|
||||
notSet: "не задано",
|
||||
sectionMissing: "Секция в конфигурации отсутствует",
|
||||
tlsMissing: "Ни tls, ни acme в конфигурации не заданы",
|
||||
valuesHidden: "значения не показываются",
|
||||
secretSet: "задан",
|
||||
secretMissing: "не задан",
|
||||
obfsPasswordHint: "выдаётся в клиентской ссылке пира",
|
||||
driftTitle: "Расхождение конфигурации",
|
||||
driftHint:
|
||||
"В файле есть секции вне production-профиля HY2XS. Оркестратор их не создаёт и не поддерживает: скорее всего, конфигурацию правили вручную.",
|
||||
trafficStatsMissing: "Traffic Stats API не настроен",
|
||||
trafficStatsMissingHint:
|
||||
"Без секции trafficStats не работают лимит устройств, учёт трафика и принудительное отключение: панель обращается к Hysteria только через этот API.",
|
||||
trafficStatsNotLoopback:
|
||||
"адрес не loopback: панель обращается к Traffic Stats API только по 127.0.0.1",
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user