fix(v1): разблокировать сборку, починить жизненный цикл cron и закрыть каналы утечки
Сборка не собиралась: два контракта приёмки роняли её на корректном коде.
verify_api_namespace_contract искал возвращение legacy-пространства имён
через grep по '/hui' и находил router_test.go, который ПЕРЕЧИСЛЯЕТ этот
префикс, чтобы доказать отсутствие маршрута, и сам versions.sh, где строка
стоит в тексте проверки. Падение приходило шестым шагом из четырнадцати, до
резолва Hysteria. За ним прятался второй такой же: проверка транзакционности
импорта пиров брала файл от начала applyPeerImportEntry и до конца, захватывая
объявленные ниже ExistPeerName и UpdatePeerLastConnectionAt.
Обе проверки теперь смотрят на код, а не на упоминания: добавлены помощники
code_without_comments и code_mentions_in, а отсутствие legacy-маршрута
доказывает тест на таблице маршрутов собранного роутера.
Планировщик стал собственностью процесса. InitCron вызывался из runServer и
на каждом вызове создавал новый cron.New(), не сохраняя ссылку; cron.Stop()
не вызывался нигде. Смена RESET_TRAFFIC_CRON выполняла StopServer(), точка
входа крутила for { runServer() } — и каждая правка добавляла целый
дублирующий набор джоб, а старое расписание сброса продолжало работать.
Фиксированные джобы регистрируются один раз, расписание переносится на месте
по EntryID, HTTP-сервер не трогается. Добавлено штатное завершение по SIGTERM.
Выражение проверяется до записи в базу тем же парсером (cron.ParseStandard),
которым его разбирает планировщик: раньше невалидная строка сохранялась, API
отвечал успехом, а сброс трафика молча исчезал.
updateConfigs стал атомарным: полная проверка партии, одна транзакция,
применение к рантайму. Прежний тест ставил запрещённый ключ первым и не
смотрел в базу — поймать частичное применение он был неспособен.
Удалены четыре ключа таблицы config без единого потребителя: HYSTERIA2_ENABLE,
HYSTERIA2_CONFIG (второй источник истины, читался первым), HYSTERIA2_TRAFFIC_TIME
и HYSTERIA2_CONFIG_REMARK. Имя профиля в share URI выводится из имени пира.
Безопасность:
- bootstrap-пароль администратора больше не генерируется и не пишется в журнал,
который отдаётся кнопкой выгрузки; отсутствие env — отказ старта;
- собственный журнал админки санитизируется наравне с чужим;
- golang-jwt/jwt v3 -> v5: GO-2025-3553 не имеет исправленной версии в v3 и
достижима с неаутентифицированного запроса; набор алгоритмов подписи
зафиксирован через WithValidMethods;
- удалён вход по несолёному SHA-224 из предыдущего поколения;
- убран modulo bias в util.RandomString — единственном генераторе секретов;
- пир установщика защищён во всех путях записи, а не только в импорте;
- удалена латентная паника в service.GetToken и недостижимая ветка GetAdminInfo,
проверявшая меньше, чем middleware.
Toolchain: Go 1.21.13 -> 1.26.7, Node 20.19.0 (EOL) -> 24.20.0. На прежнем
графе govulncheck находил 21 вызываемую уязвимость, 17 из них в stdlib,
попадающей в production-бинарь. Сейчас — ноль. Добавлен обязательный шаг
проверки зависимостей (govulncheck + pnpm audit) с записью результата в
metadata пакета.
This commit is contained in:
@@ -129,7 +129,7 @@ export default {
|
||||
form: {
|
||||
namePlaceholder: "e.g. ivan-laptop",
|
||||
nameHint:
|
||||
"Short peer identifier. Use latin letters, digits and hyphens — the name becomes part of the auto-generated secret.",
|
||||
"Short peer identifier. Use latin letters, digits and hyphens — the name becomes part of the auto-generated secret and is shown to the client as the profile name.",
|
||||
remarkPlaceholder: "e.g. Ivan's laptop, sales team",
|
||||
remarkHint: "Optional operator note. It is never shown to the client.",
|
||||
secretPlaceholder: "leave empty to generate automatically",
|
||||
@@ -185,13 +185,11 @@ export default {
|
||||
},
|
||||
config: {
|
||||
orchestratorManaged: "Managed by hy2xs-orchestrator reconfigure",
|
||||
hysteria2TrafficTime: "Hysteria2 Traffic Time",
|
||||
resetTrafficCron: "Reset traffic schedule task",
|
||||
resetTrafficCronTip:
|
||||
"Scheduled task expression, reference: https://pkg.go.dev/github.com/robfig/cron/v3",
|
||||
"Scheduled task expression, reference: https://pkg.go.dev/github.com/robfig/cron/v3. An empty value disables the automatic reset",
|
||||
resetTrafficMonth: "Run once a month, midnight, first of month",
|
||||
resetTrafficWeek: "Run once a week, midnight between Sat/Sun",
|
||||
invalidTrafficTime: "Field must be a number with up to one decimal place",
|
||||
},
|
||||
monitor: {
|
||||
cpuPercent: "CPU Usage",
|
||||
@@ -229,7 +227,6 @@ export default {
|
||||
hysteria2Version: "Hysteria2 Version",
|
||||
hysteria2Running: "Hysteria2 Status",
|
||||
addOutbound: "Add Outbound",
|
||||
extension: "Extension",
|
||||
listen: "Listen",
|
||||
tls: "TLS",
|
||||
obfs: "Obfuscation",
|
||||
@@ -245,8 +242,6 @@ export default {
|
||||
http: "Traffic Stats API (HTTP)",
|
||||
masquerade: "Masquerade",
|
||||
config: {
|
||||
enable: "Enable/Disable",
|
||||
remark: "Remark",
|
||||
listen:
|
||||
"When the IP address is omitted, the server will listen on all interfaces, both IPv4 and IPv6. To listen on IPv4 only, you can use 0.0.0.0:443. To listen on IPv6 only, you can use [::]:443.",
|
||||
tlsType: "TLS type",
|
||||
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
form: {
|
||||
namePlaceholder: "например, ivan-laptop",
|
||||
nameHint:
|
||||
"Короткий идентификатор пира. Используйте латиницу, цифры и дефис — имя попадает в автогенерируемый секрет.",
|
||||
"Короткий идентификатор пира. Используйте латиницу, цифры и дефис — имя попадает в автогенерируемый секрет и показывается клиенту как название профиля.",
|
||||
remarkPlaceholder: "например, Ноутбук Ивана, отдел продаж",
|
||||
remarkHint: "Необязательная пометка для оператора. Клиент её не видит.",
|
||||
secretPlaceholder: "оставьте пустым — сгенерируем автоматически",
|
||||
@@ -181,13 +181,11 @@ export default {
|
||||
},
|
||||
config: {
|
||||
orchestratorManaged: "Управляется hy2xs-orchestrator reconfigure",
|
||||
hysteria2TrafficTime: "Период учёта трафика Hysteria2",
|
||||
resetTrafficCron: "Расписание сброса трафика",
|
||||
resetTrafficCronTip: "Cron-выражение для планового сброса трафика",
|
||||
resetTrafficCronTip:
|
||||
"Cron-выражение для планового сброса трафика. Пустое значение отключает автоматический сброс",
|
||||
resetTrafficMonth: "Раз в месяц, в полночь первого дня",
|
||||
resetTrafficWeek: "Раз в неделю, в полночь между субботой и воскресеньем",
|
||||
invalidTrafficTime:
|
||||
"Поле должно быть числом максимум с одним знаком после запятой",
|
||||
},
|
||||
monitor: {
|
||||
cpuPercent: "CPU",
|
||||
@@ -225,7 +223,6 @@ export default {
|
||||
hysteria2Version: "Версия Hysteria2",
|
||||
hysteria2Running: "Статус Hysteria2",
|
||||
addOutbound: "Добавить outbound",
|
||||
extension: "Расширение",
|
||||
listen: "Адрес прослушивания",
|
||||
tls: "TLS",
|
||||
obfs: "Маскировка",
|
||||
@@ -241,8 +238,6 @@ export default {
|
||||
http: "Traffic Stats API (HTTP)",
|
||||
masquerade: "Masquerade",
|
||||
config: {
|
||||
enable: "Включить/отключить",
|
||||
remark: "Комментарий",
|
||||
listen: "Адрес и порт прослушивания. Для IPv4 используйте 0.0.0.0:443.",
|
||||
tlsType: "Тип TLS",
|
||||
tls: {
|
||||
|
||||
+519
-867
File diff suppressed because it is too large
Load Diff
+53
-55
@@ -1,65 +1,63 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import "@vue/runtime-core";
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {};
|
||||
export {}
|
||||
|
||||
declare module "@vue/runtime-core" {
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
ElAlert: typeof import("element-plus/es")["ElAlert"];
|
||||
ElAvatar: typeof import("element-plus/es")["ElAvatar"];
|
||||
ElButton: typeof import("element-plus/es")["ElButton"];
|
||||
ElCard: typeof import("element-plus/es")["ElCard"];
|
||||
ElCol: typeof import("element-plus/es")["ElCol"];
|
||||
ElDatePicker: typeof import("element-plus/es")["ElDatePicker"];
|
||||
ElDialog: typeof import("element-plus/es")["ElDialog"];
|
||||
ElDrawer: typeof import("element-plus/es")["ElDrawer"];
|
||||
ElDropdown: typeof import("element-plus/es")["ElDropdown"];
|
||||
ElDropdownItem: typeof import("element-plus/es")["ElDropdownItem"];
|
||||
ElDropdownMenu: typeof import("element-plus/es")["ElDropdownMenu"];
|
||||
ElEmpty: typeof import("element-plus/es")["ElEmpty"];
|
||||
ElForm: typeof import("element-plus/es")["ElForm"];
|
||||
ElFormItem: typeof import("element-plus/es")["ElFormItem"];
|
||||
ElInput: typeof import("element-plus/es")["ElInput"];
|
||||
ElInputNumber: typeof import("element-plus/es")["ElInputNumber"];
|
||||
ElMenu: typeof import("element-plus/es")["ElMenu"];
|
||||
ElMenuItem: typeof import("element-plus/es")["ElMenuItem"];
|
||||
ElOption: typeof import("element-plus/es")["ElOption"];
|
||||
ElPagination: typeof import("element-plus/es")["ElPagination"];
|
||||
ElProgress: typeof import("element-plus/es")["ElProgress"];
|
||||
ElRadioButton: typeof import("element-plus/es")["ElRadioButton"];
|
||||
ElRadioGroup: typeof import("element-plus/es")["ElRadioGroup"];
|
||||
ElRow: typeof import("element-plus/es")["ElRow"];
|
||||
ElScrollbar: typeof import("element-plus/es")["ElScrollbar"];
|
||||
ElSelect: typeof import("element-plus/es")["ElSelect"];
|
||||
ElSubMenu: typeof import("element-plus/es")["ElSubMenu"];
|
||||
ElSwitch: typeof import("element-plus/es")["ElSwitch"];
|
||||
ElTable: typeof import("element-plus/es")["ElTable"];
|
||||
ElTableColumn: typeof import("element-plus/es")["ElTableColumn"];
|
||||
ElTabPane: typeof import("element-plus/es")["ElTabPane"];
|
||||
ElTabs: typeof import("element-plus/es")["ElTabs"];
|
||||
ElTag: typeof import("element-plus/es")["ElTag"];
|
||||
ElTooltip: typeof import("element-plus/es")["ElTooltip"];
|
||||
ElUpload: typeof import("element-plus/es")["ElUpload"];
|
||||
Hamburger: typeof import("./../components/Hamburger/index.vue")["default"];
|
||||
IEpCaretBottom: typeof import("~icons/ep/caret-bottom")["default"];
|
||||
IEpDownload: typeof import("~icons/ep/download")["default"];
|
||||
IEpMoreFilled: typeof import("~icons/ep/more-filled")["default"];
|
||||
IEpRefresh: typeof import("~icons/ep/refresh")["default"];
|
||||
IEpRefreshRight: typeof import("~icons/ep/refresh-right")["default"];
|
||||
IEpUpload: typeof import("~icons/ep/upload")["default"];
|
||||
IEpUserFilled: typeof import("~icons/ep/user-filled")["default"];
|
||||
ImputMultiple: typeof import("./../components/ImputMultiple/index.vue")["default"];
|
||||
LangSelect: typeof import("./../components/LangSelect/index.vue")["default"];
|
||||
MapAdd: typeof import("./../components/MapAdd/index.vue")["default"];
|
||||
Pagination: typeof import("./../components/Pagination/index.vue")["default"];
|
||||
RouterLink: typeof import("vue-router")["RouterLink"];
|
||||
RouterView: typeof import("vue-router")["RouterView"];
|
||||
SvgIcon: typeof import("./../components/SvgIcon/index.vue")["default"];
|
||||
UnitSelect: typeof import("./../components/UnitSelect/index.vue")["default"];
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
Hamburger: typeof import('./../components/Hamburger/index.vue')['default']
|
||||
IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
|
||||
IEpDownload: typeof import('~icons/ep/download')['default']
|
||||
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
|
||||
IEpRefresh: typeof import('~icons/ep/refresh')['default']
|
||||
IEpUserFilled: typeof import('~icons/ep/user-filled')['default']
|
||||
ImputMultiple: typeof import('./../components/ImputMultiple/index.vue')['default']
|
||||
LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
|
||||
MapAdd: typeof import('./../components/MapAdd/index.vue')['default']
|
||||
Pagination: typeof import('./../components/Pagination/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
|
||||
UnitSelect: typeof import('./../components/UnitSelect/index.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import("element-plus/es")["ElLoadingDirective"];
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,6 @@
|
||||
:model="dataForm"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('config.hysteria2TrafficTime')"
|
||||
prop="hysteria2TrafficTime"
|
||||
>
|
||||
<el-input
|
||||
v-model="dataForm.hysteria2TrafficTime"
|
||||
:placeholder="$t('config.hysteria2TrafficTime')"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-tooltip
|
||||
:content="$t('config.resetTrafficCronTip')"
|
||||
placement="bottom"
|
||||
@@ -79,6 +69,14 @@ export default {
|
||||
//
|
||||
// Generic import/export конфигурации удалён: он выгружал таблицу `config`
|
||||
// целиком, вместе с JWT- и peer-ключами.
|
||||
//
|
||||
// Поля «Период учёта трафика Hysteria2» здесь тоже больше нет. Ключ
|
||||
// HYSTERIA2_TRAFFIC_TIME не имел в рантайме ни одного потребителя: интервал
|
||||
// сбора метрик задан в коде админки, а traffic accounting его не читал.
|
||||
// Настройка меняла строку в базе и ничего больше — то есть панель обещала
|
||||
// оператору управление, которого не существовало.
|
||||
//
|
||||
// Осталась ровно одна настройка, которой панель действительно владеет.
|
||||
import { Select } from "@element-plus/icons-vue";
|
||||
import { listConfigApi, updateConfigsApi } from "@/api/config";
|
||||
import { ConfigsUpdateDto } from "@/api/config/types";
|
||||
@@ -88,7 +86,6 @@ const { t } = useI18n();
|
||||
|
||||
const dataFormRef = ref(ElForm);
|
||||
|
||||
const hysteria2TrafficTimeKey = "HYSTERIA2_TRAFFIC_TIME";
|
||||
const resetTrafficCronKey = "RESET_TRAFFIC_CRON";
|
||||
|
||||
const cronResetTraffic = [
|
||||
@@ -96,24 +93,18 @@ const cronResetTraffic = [
|
||||
{ key: t("config.resetTrafficWeek"), value: "@weekly" },
|
||||
];
|
||||
|
||||
const dataFormRules = {
|
||||
hysteria2TrafficTime: [
|
||||
{
|
||||
required: true,
|
||||
message: t("common.required"),
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
{
|
||||
pattern: /^\d+(\.\d)?$/,
|
||||
message: t("config.invalidTrafficTime"),
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
};
|
||||
// Правил формы здесь нет намеренно.
|
||||
//
|
||||
// Поле — el-select с allow-create, то есть значение произвольное, и
|
||||
// единственная осмысленная проверка cron-выражения — разбор его тем же
|
||||
// парсером, которым пользуется планировщик. Такой парсер живёт на сервере;
|
||||
// вторая, приблизительная реализация на фронтенде расходилась бы с ним и
|
||||
// давала бы либо ложные отказы, либо ложные разрешения. Пустое значение
|
||||
// легально и означает «автоматический сброс выключен».
|
||||
const dataFormRules = {};
|
||||
|
||||
const state = reactive({
|
||||
dataForm: {
|
||||
hysteria2TrafficTime: "1",
|
||||
resetTrafficCron: "",
|
||||
},
|
||||
});
|
||||
@@ -124,10 +115,6 @@ const submitForm = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
let configs: ConfigsUpdateDto[] = [
|
||||
{
|
||||
key: hysteria2TrafficTimeKey,
|
||||
value: state.dataForm.hysteria2TrafficTime,
|
||||
},
|
||||
{
|
||||
key: resetTrafficCronKey,
|
||||
value: state.dataForm.resetTrafficCron,
|
||||
@@ -143,13 +130,11 @@ const submitForm = () => {
|
||||
|
||||
const setConfig = async () => {
|
||||
const { data } = await listConfigApi({
|
||||
keys: [hysteria2TrafficTimeKey, resetTrafficCronKey],
|
||||
keys: [resetTrafficCronKey],
|
||||
});
|
||||
|
||||
data.forEach((configVo) => {
|
||||
if (configVo.key === hysteria2TrafficTimeKey) {
|
||||
state.dataForm.hysteria2TrafficTime = configVo.value;
|
||||
} else if (configVo.key === resetTrafficCronKey) {
|
||||
if (configVo.key === resetTrafficCronKey) {
|
||||
state.dataForm.resetTrafficCron = configVo.value;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -51,16 +51,6 @@
|
||||
:disabled="true"
|
||||
>
|
||||
<el-tabs v-model="activeName" class="tabs">
|
||||
<el-tab-pane :label="$t('hysteria.extension')" name="extension">
|
||||
<el-tooltip
|
||||
:content="$t('hysteria.config.remark')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-form-item label="remark" prop="remark">
|
||||
<el-input v-model="configForm.remark" clearable />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('hysteria.listen')" name="listen">
|
||||
<el-tooltip
|
||||
:content="$t('hysteria.config.listen')"
|
||||
@@ -1062,18 +1052,19 @@ import {
|
||||
Hysteria2ServerConfig,
|
||||
} from "@/api/config/types";
|
||||
import Outbounds from "./components/Outbounds/index.vue";
|
||||
import {
|
||||
exportHysteria2ConfigApi,
|
||||
getHysteria2ConfigApi,
|
||||
listConfigApi,
|
||||
} from "@/api/config";
|
||||
import { exportHysteria2ConfigApi, getHysteria2ConfigApi } from "@/api/config";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { assignWith, deepCopy } from "@/utils/copy";
|
||||
import { dashboardSummaryApi } from "@/api/dashboard";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const hysteria2Remark = "HYSTERIA2_CONFIG_REMARK";
|
||||
// Вкладки «extension» с полем remark здесь больше нет.
|
||||
//
|
||||
// Она показывала HYSTERIA2_CONFIG_REMARK — пустую строку в таблице `config`,
|
||||
// которую clean install создавал один раз и которую не записывал никто. Имя
|
||||
// профиля в клиентской ссылке теперь выводится из имени пира на сервере
|
||||
// (см. service/hysteria2_api.go), поэтому настраивать здесь нечего.
|
||||
const dataFormRef = ref(ElForm);
|
||||
|
||||
const dataFormRules = {
|
||||
@@ -1118,11 +1109,8 @@ const resolverTypes = ref<string[]>(["tcp", "udp", "tls", "https"]);
|
||||
const masqueradeTypes = ref<string[]>(["file", "proxy", "string"]);
|
||||
|
||||
const state = reactive({
|
||||
configForm: {
|
||||
remark: "",
|
||||
},
|
||||
dataForm: { ...defaultHysteria2ServerConfig } as Hysteria2ServerConfig,
|
||||
activeName: "extension",
|
||||
activeName: "listen",
|
||||
tlsType: "acme",
|
||||
aclType: "inline",
|
||||
obfs: false,
|
||||
@@ -1143,7 +1131,6 @@ const state = reactive({
|
||||
});
|
||||
|
||||
const {
|
||||
configForm,
|
||||
activeName,
|
||||
dataForm,
|
||||
tlsType,
|
||||
@@ -1183,17 +1170,6 @@ const handleExport = async () => {
|
||||
};
|
||||
|
||||
const setConfig = () => {
|
||||
listConfigApi({
|
||||
keys: [hysteria2Remark],
|
||||
}).then((response) => {
|
||||
const data = response.data;
|
||||
data.forEach((configVo) => {
|
||||
if (configVo.key === hysteria2Remark) {
|
||||
state.configForm.remark = configVo.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
getHysteria2ConfigApi().then((response) => {
|
||||
const data = response.data;
|
||||
if (data) {
|
||||
|
||||
Reference in New Issue
Block a user