162759c599
Разбор кода на c0a43ae со сверкой с официальной документацией Hysteria 2.
Общая тема: операции, у которых была только одна из двух необходимых половин.
Отзыв доступа. Запись disabled=1 видит лишь выборка в Hysteria2Auth, то есть
закрывает БУДУЩИЕ обращения к HTTP-auth; установленная QUIC-сессия живёт своей
жизнью и сама не разрывается. После «Отключить» пир пользовался доступом сколько
угодно долго, а панель показывала его отключённым. Появился DisconnectPeers —
только официальный Traffic Stats /kick, без записи в базу; прежний Hysteria2Kick
вместе с разрывом проставлял banned_until и потому для отключения не годился.
Порядок «запись, затем разрыв» обратному не подлежит и доказан снимком базы в
момент прихода /kick. Неудача разрыва не откатывает disabled и сообщается кодом
peer_disconnect_failed: обычная ошибка означала бы для оператора вывод, прямо
противоположный истине. KickPeer переведён на тот же примитив — он писал
banned_until дважды и мог ответить чистым отказом уже в применённом состоянии.
Ограничение устройств. Отказ /online обрабатывался возвратом успеха
авторизации, то есть недоступность 127.0.0.1 превращала объявленный лимит в
безлимит. Вторая половина дыры была тише: Hysteria2Online отдавал пустую карту
БЕЗ ошибки, когда systemd отвечал «служба неактивна», — а этот ответ не
отличается от «спросить systemctl не удалось». Пути разделены: терпимый для
отображения, строгий для решения о доступе. Hysteria2IsRunning убран с путей
принятия решений совсем.
Журнал. entry.Info() вызывался без аргумента, и logrus писал "msg":"" для
каждого запроса — пустой столбец на экране был точным отражением файла. Ветка
«файла ещё нет» отвечала голым массивом вместо {records,total}, поэтому на
свежей установке страница системных логов не работала вовсе. Битая строка
вызывала vo.Fail И continue: клиент получал два JSON-документа подряд.
Панель. Общий LogViewer и utils/download.ts (копий скачивания было четыре, две
ставили запрос вне try и глушили причину); меню на command с быстрым
включением/отключением; popper-style у подсказки; kick с подтверждением и
названным сроком; отмена подтверждений перестала быть ошибкой. Отдельно:
skipErrorToast гасил и транспортный отказ, при том что страницы писали
«перехватчик уже показал» и молчали, — обрыв связи не показывал ничего.
Закреплено go-тестами против настоящего HTTP, контрактными тестами панели и
двумя гейтами приёмки. Ручная часть — в
docs/acceptance/2026-09-01-v1.0.0-rc2-preflight-findings.md.
105 lines
5.1 KiB
JavaScript
105 lines
5.1 KiB
JavaScript
import { createRequire } from "node:module";
|
||
|
||
import js from "@eslint/js";
|
||
import globals from "globals";
|
||
import tseslint from "typescript-eslint";
|
||
import pluginVue from "eslint-plugin-vue";
|
||
import prettier from "eslint-config-prettier";
|
||
import vueParser from "vue-eslint-parser";
|
||
|
||
/**
|
||
* Конфигурация ESLint во flat-формате.
|
||
*
|
||
* Заменяет `.eslintrc.cjs`. Причина не в моде на формат: eslint 8 объявлен
|
||
* EOL, перестал получать исправления и оставался последним источником десятка
|
||
* предупреждений `pnpm audit` в проекте (minimatch, brace-expansion, js-yaml,
|
||
* flatted — всё в его собственном графе). Eslintrc-формат в eslint 9 работает
|
||
* только через переменную окружения, а в 10 удалён вовсе, поэтому обновление
|
||
* версии без перехода на flat-config было бы отсрочкой на один релиз.
|
||
*
|
||
* Набор правил сохранён ровно тем же, что был в `.eslintrc.cjs`: перевод
|
||
* формата не место для изменения политики линтинга.
|
||
*/
|
||
|
||
// Глобальные имена авто-импорта генерирует unplugin-auto-import в
|
||
// eslintrc-формате (`{ globals: { ref: true, ... } }`). Flat-config такой файл
|
||
// через `extends` не подключает, поэтому список читается напрямую — источник
|
||
// истины остаётся один, и он по-прежнему пересоздаётся сборкой.
|
||
const require = createRequire(import.meta.url);
|
||
const autoImportGlobals = require("./.eslintrc-auto-import.json").globals;
|
||
|
||
export default tseslint.config(
|
||
{
|
||
// Перенесено из .eslintignore: eslint 9 этот файл больше не читает, и
|
||
// список исключений обязан жить в самой конфигурации.
|
||
//
|
||
// src/assets — SVG и изображения; src/types/*.d.ts — объявления, которые
|
||
// генерируют unplugin-auto-import и unplugin-vue-components, править их
|
||
// вручную нельзя, а замечания линтера по ним только шумят.
|
||
ignores: [
|
||
"dist/**",
|
||
"node_modules/**",
|
||
"public/**",
|
||
"src/assets/**",
|
||
"src/types/*.d.ts",
|
||
],
|
||
},
|
||
|
||
js.configs.recommended,
|
||
...tseslint.configs.recommended,
|
||
...pluginVue.configs["flat/essential"],
|
||
|
||
{
|
||
languageOptions: {
|
||
ecmaVersion: "latest",
|
||
sourceType: "module",
|
||
globals: {
|
||
...globals.browser,
|
||
...globals.node,
|
||
...autoImportGlobals,
|
||
// Объявлены в src/types/global.d.ts и используются как глобальные
|
||
// типы. Списку положено совпадать с содержимым того файла: `no-undef`
|
||
// не знает о declare global, и отсутствующее здесь имя падает ошибкой
|
||
// линтера при совершенно корректном коде.
|
||
//
|
||
// DialogType и OptionType отсюда убраны: в src/types/global.d.ts их
|
||
// нет и в панели они не встречаются — это остаток чужого шаблона,
|
||
// разрешавший необъявленные имена.
|
||
IdDto: "readonly",
|
||
BaseDto: "readonly",
|
||
PageVo: "readonly",
|
||
},
|
||
},
|
||
rules: {
|
||
// Имя компонента из одного слова — устоявшееся соглашение этого проекта.
|
||
"vue/multi-word-component-names": "off",
|
||
"vue/no-v-model-argument": "off",
|
||
"@typescript-eslint/no-empty-function": "off",
|
||
"@typescript-eslint/no-explicit-any": "off",
|
||
"@typescript-eslint/no-non-null-assertion": "off",
|
||
},
|
||
},
|
||
|
||
{
|
||
// .vue разбирает vue-eslint-parser, а <script lang="ts"> внутри него —
|
||
// парсер TypeScript. Без второй половины содержимое блока не разбирается.
|
||
files: ["**/*.vue"],
|
||
languageOptions: {
|
||
parser: vueParser,
|
||
parserOptions: {
|
||
parser: tseslint.parser,
|
||
ecmaVersion: "latest",
|
||
sourceType: "module",
|
||
},
|
||
},
|
||
},
|
||
|
||
// Последним: отключает правила форматирования, которыми владеет prettier.
|
||
//
|
||
// Ни один из включённых наборов таких правил сейчас не содержит, так что это
|
||
// страховка, а не исправление: форматированием занимается отдельный скрипт
|
||
// lint:prettier, и правило-дубликат в линтере привело бы к двум источникам
|
||
// истины о стиле.
|
||
prettier
|
||
);
|