fix(routing): scope excluded_ips to section rules, humanize section names, and add component visibility
This commit is contained in:
+1907
-1813
File diff suppressed because it is too large
Load Diff
+1867
-1812
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: TACHYON\n"
|
"Project-Id-Version: TACHYON\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-09-16 15:18+0300\n"
|
"POT-Creation-Date: 2026-09-16 16:15+0300\n"
|
||||||
"PO-Revision-Date: 2026-09-16 15:18+0300\n"
|
"PO-Revision-Date: 2026-09-16 16:15+0300\n"
|
||||||
"Last-Translator: Dushnilin\n"
|
"Last-Translator: Dushnilin\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
@@ -311,6 +311,9 @@ msgstr "Chat ID администраторов"
|
|||||||
msgid "Adult content"
|
msgid "Adult content"
|
||||||
msgstr "Взрослый контент"
|
msgstr "Взрослый контент"
|
||||||
|
|
||||||
|
msgid "Advanced DPI bypass engine with Lua scripts (nfqws2)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Advanced System"
|
msgid "Advanced System"
|
||||||
msgstr "Расширенные настройки"
|
msgstr "Расширенные настройки"
|
||||||
|
|
||||||
@@ -1079,6 +1082,9 @@ msgstr "Community-списки"
|
|||||||
msgid "Component action timed out"
|
msgid "Component action timed out"
|
||||||
msgstr "Превышено время ожидания действия компонента"
|
msgstr "Превышено время ожидания действия компонента"
|
||||||
|
|
||||||
|
msgid "Component Cards Visibility"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Component update available"
|
msgid "Component update available"
|
||||||
msgstr "Доступно обновление компонента"
|
msgstr "Доступно обновление компонента"
|
||||||
|
|
||||||
@@ -1184,6 +1190,9 @@ msgstr "Скопировать отчет для поддержки"
|
|||||||
msgid "Core configuration and preferences"
|
msgid "Core configuration and preferences"
|
||||||
msgstr "Основные настройки и параметры"
|
msgstr "Основные настройки и параметры"
|
||||||
|
|
||||||
|
msgid "Core orchestration service and LuCI application"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Could not compare FakeIP and control public IPs"
|
msgid "Could not compare FakeIP and control public IPs"
|
||||||
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
|
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
|
||||||
|
|
||||||
@@ -1253,6 +1262,9 @@ msgstr "Пользовательская стратегия добавлена!"
|
|||||||
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
|
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
|
||||||
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
|
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
|
||||||
|
|
||||||
|
msgid "Customize which component cards are displayed in the Components tab. Checkboxes show or hide components."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Daily Quota"
|
msgid "Daily Quota"
|
||||||
msgstr "Дневная квота"
|
msgstr "Дневная квота"
|
||||||
|
|
||||||
@@ -1286,6 +1298,9 @@ msgstr "Дни недели"
|
|||||||
msgid "Dedicated cgroup unavailable"
|
msgid "Dedicated cgroup unavailable"
|
||||||
msgstr "Выделенная cgroup недоступна"
|
msgstr "Выделенная cgroup недоступна"
|
||||||
|
|
||||||
|
msgid "Dedicated direct HTTP/SOCKS5 outbound proxy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "По умолчанию"
|
msgstr "По умолчанию"
|
||||||
|
|
||||||
@@ -1370,6 +1385,9 @@ msgstr "Прямое (по умолчанию)"
|
|||||||
msgid "Direct Bypass"
|
msgid "Direct Bypass"
|
||||||
msgstr "Прямой WAN"
|
msgstr "Прямой WAN"
|
||||||
|
|
||||||
|
msgid "Direct cgroup bypass for TorrServer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Direct connection"
|
msgid "Direct connection"
|
||||||
msgstr "Прямое подключение"
|
msgstr "Прямое подключение"
|
||||||
|
|
||||||
@@ -1622,6 +1640,9 @@ msgstr "Загрузка и применение наборов правил и
|
|||||||
msgid "DPI bandwidth throttling or delay detected — connection throttled during data transfer."
|
msgid "DPI bandwidth throttling or delay detected — connection throttled during data transfer."
|
||||||
msgstr "Обнаружено замедление DPI — соединение заблокировано или задушено при передаче данных."
|
msgstr "Обнаружено замедление DPI — соединение заблокировано или задушено при передаче данных."
|
||||||
|
|
||||||
|
msgid "DPI bypass engine (nfqws v1)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "DPI Detected"
|
msgid "DPI Detected"
|
||||||
msgstr "DPI обнаружен"
|
msgstr "DPI обнаружен"
|
||||||
|
|
||||||
@@ -2126,6 +2147,9 @@ msgstr "Семейные профили"
|
|||||||
msgid "Family profiles and device grouping"
|
msgid "Family profiles and device grouping"
|
||||||
msgstr "Семейные профили и группировка устройств"
|
msgstr "Семейные профили и группировка устройств"
|
||||||
|
|
||||||
|
msgid "Fast packet transport network client"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
|
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
|
||||||
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
|
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
|
||||||
|
|
||||||
@@ -3071,6 +3095,9 @@ msgstr "Загрузка..."
|
|||||||
msgid "Local Address"
|
msgid "Local Address"
|
||||||
msgstr "Локальный адрес"
|
msgstr "Локальный адрес"
|
||||||
|
|
||||||
|
msgid "Local SOCKS proxy desynchronizer (ciadpi)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Местоположение"
|
msgstr "Местоположение"
|
||||||
|
|
||||||
@@ -3959,6 +3986,9 @@ msgstr "Ошибка чтения"
|
|||||||
msgid "Ready to benchmark DNS providers"
|
msgid "Ready to benchmark DNS providers"
|
||||||
msgstr "Готов к тестированию DNS провайдеров"
|
msgstr "Готов к тестированию DNS провайдеров"
|
||||||
|
|
||||||
|
msgid "Real-time WebRTC anti-censorship tunnel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Reality fake site"
|
msgid "Reality fake site"
|
||||||
msgstr "Фейковый сайт Reality"
|
msgstr "Фейковый сайт Reality"
|
||||||
|
|
||||||
@@ -5489,6 +5519,9 @@ msgstr "Великобритания (gb)"
|
|||||||
msgid "United States (us)"
|
msgid "United States (us)"
|
||||||
msgstr "США (us)"
|
msgstr "США (us)"
|
||||||
|
|
||||||
|
msgid "Universal proxy platform routing engine"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "unknown"
|
msgid "unknown"
|
||||||
msgstr "неизвестно"
|
msgstr "неизвестно"
|
||||||
|
|
||||||
@@ -5834,6 +5867,9 @@ msgstr "Будни (Пн-Пт)"
|
|||||||
msgid "Weekends (Sat-Sun)"
|
msgid "Weekends (Sat-Sun)"
|
||||||
msgstr "Выходные (Сб-Вс)"
|
msgstr "Выходные (Сб-Вс)"
|
||||||
|
|
||||||
|
msgid "Western Digital Tunnel Transport"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "What Tachyon should do when this section matches"
|
msgid "What Tachyon should do when this section matches"
|
||||||
msgstr "Что должен делать Tachyon при совпадении этой секции"
|
msgstr "Что должен делать Tachyon при совпадении этой секции"
|
||||||
|
|
||||||
@@ -5942,6 +5978,9 @@ msgstr "Правила маршрутизации sing-box для Zapret2 нас
|
|||||||
msgid "Zapret2 sing-box route rules are not configured"
|
msgid "Zapret2 sing-box route rules are not configured"
|
||||||
msgstr "Правила маршрутизации sing-box для Zapret2 не настроены"
|
msgstr "Правила маршрутизации sing-box для Zapret2 не настроены"
|
||||||
|
|
||||||
|
msgid "Zero config mesh VPN daemon"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Testing strategy"
|
msgid "Testing strategy"
|
||||||
msgstr "Тестирование стратегии"
|
msgstr "Тестирование стратегии"
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export function resetDirectClashApiState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayName(section: Tachyon.ConfigSection) {
|
function getDisplayName(section: Tachyon.ConfigSection) {
|
||||||
return section.label || section['.name'];
|
return section.label || (section as any).name || section['.name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSettingsSection(configSections: Tachyon.ConfigSection[]) {
|
function getSettingsSection(configSections: Tachyon.ConfigSection[]) {
|
||||||
@@ -1806,6 +1806,7 @@ export async function getDashboardSections(
|
|||||||
code: outbound?.code || sectionName,
|
code: outbound?.code || sectionName,
|
||||||
displayName:
|
displayName:
|
||||||
section.label ||
|
section.label ||
|
||||||
|
(section as any).name ||
|
||||||
section.interface ||
|
section.interface ||
|
||||||
customName ||
|
customName ||
|
||||||
defaultLabel,
|
defaultLabel,
|
||||||
|
|||||||
@@ -1589,11 +1589,17 @@ function handleOpenLeakCheck() {
|
|||||||
function handleOpenStrategyFuzzer() {
|
function handleOpenStrategyFuzzer() {
|
||||||
getConfigSections()
|
getConfigSections()
|
||||||
.then((sections) => {
|
.then((sections) => {
|
||||||
const ruleNames = sections
|
const ruleSections = sections
|
||||||
.filter((s) => s['.type'] === 'section' || s['.type'] === 'rule')
|
.filter((s) => s['.type'] === 'section' || s['.type'] === 'rule')
|
||||||
.map((s) => s.name || s['.name'])
|
.map((s) => ({
|
||||||
.filter((n): n is string => Boolean(n));
|
id: s['.name'],
|
||||||
renderStrategyFuzzerModal(ruleNames);
|
label:
|
||||||
|
(typeof s.label === 'string' && s.label.trim()) ||
|
||||||
|
(typeof s.name === 'string' && s.name.trim()) ||
|
||||||
|
s['.name'],
|
||||||
|
}))
|
||||||
|
.filter((r) => Boolean(r.id));
|
||||||
|
renderStrategyFuzzerModal(ruleSections);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
renderStrategyFuzzerModal([]);
|
renderStrategyFuzzerModal([]);
|
||||||
|
|||||||
@@ -3,7 +3,17 @@ import { renderButton } from '../../../../partials';
|
|||||||
import { showToast } from '../../../../helpers/showToast';
|
import { showToast } from '../../../../helpers/showToast';
|
||||||
import { Tachyon } from '../../../types';
|
import { Tachyon } from '../../../types';
|
||||||
|
|
||||||
export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
|
export interface FuzzerRuleSection {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderStrategyFuzzerModal(
|
||||||
|
ruleNames: Array<string | FuzzerRuleSection> = [],
|
||||||
|
) {
|
||||||
|
const normalizedRules: FuzzerRuleSection[] = ruleNames.map((r) =>
|
||||||
|
typeof r === 'string' ? { id: r, label: r } : r,
|
||||||
|
);
|
||||||
let pollingInterval: ReturnType<typeof setInterval> | null = null;
|
let pollingInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let isPolling = false;
|
let isPolling = false;
|
||||||
|
|
||||||
@@ -292,7 +302,9 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
|
|||||||
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
{ class: 'cbi-input-select', style: 'width: 100%;' },
|
||||||
[
|
[
|
||||||
E('option', { value: '', selected: true }, _('Provider Global Default')),
|
E('option', { value: '', selected: true }, _('Provider Global Default')),
|
||||||
...ruleNames.map((r) => E('option', { value: r }, `${_('Rule:')} ${r}`)),
|
...normalizedRules.map((r) =>
|
||||||
|
E('option', { value: r.id }, `${_('Rule:')} ${r.label}`),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
ruleSelect.addEventListener('change', () => {
|
ruleSelect.addEventListener('change', () => {
|
||||||
@@ -2035,19 +2047,23 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
|
|||||||
selectedRuleSection,
|
selectedRuleSection,
|
||||||
);
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
const targetLabel =
|
||||||
|
normalizedRules.find((r) => r.id === selectedRuleSection)?.label ||
|
||||||
|
selectedRuleSection ||
|
||||||
|
_('Global Default');
|
||||||
if (typeof ui?.addNotification === 'function') {
|
if (typeof ui?.addNotification === 'function') {
|
||||||
ui.addNotification(
|
ui.addNotification(
|
||||||
_('Tachyon'),
|
_('Tachyon'),
|
||||||
E(
|
E(
|
||||||
'p',
|
'p',
|
||||||
{},
|
{},
|
||||||
`${_('Strategy applied successfully and service reloaded!')} (${item.name} -> ${selectedRuleSection || _('Global Default')})`,
|
`${_('Strategy applied successfully and service reloaded!')} (${item.name} -> ${targetLabel})`,
|
||||||
),
|
),
|
||||||
'info',
|
'info',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
showToast(
|
showToast(
|
||||||
`${_('Applied')} "${item.name}" -> ${selectedRuleSection || _('Global Default')}`,
|
`${_('Applied')} "${item.name}" -> ${targetLabel}`,
|
||||||
'success',
|
'success',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,4 +169,17 @@ describe('renderStrategyFuzzerModal', () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
expect(TachyonShellMethods.getFuzzerStatus).toHaveBeenCalled();
|
expect(TachyonShellMethods.getFuzzerStatus).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders rule options with human-readable labels and UCI section IDs', () => {
|
||||||
|
renderStrategyFuzzerModal([
|
||||||
|
{ id: 'direct', label: 'Провайдер Direct' },
|
||||||
|
{ id: 'jobvpn', label: 'Работа VPN' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(mocks.showModal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.showModal).toHaveBeenCalledWith(
|
||||||
|
'⚡ Strategy Fuzzer & Auto-Tuner',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,7 +182,11 @@ function formatEndpoint(address?: string, port?: string | number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayName(section: Tachyon.ConfigSection) {
|
function getDisplayName(section: Tachyon.ConfigSection) {
|
||||||
return normalizeString(section.label) || section['.name'];
|
return (
|
||||||
|
normalizeString(section.label) ||
|
||||||
|
normalizeString((section as any).name) ||
|
||||||
|
section['.name']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRouteDisplayNames(sections: Tachyon.ConfigSection[]) {
|
function buildRouteDisplayNames(sections: Tachyon.ConfigSection[]) {
|
||||||
|
|||||||
@@ -2029,6 +2029,33 @@ function renderComponentCard(card: ComponentCard) {
|
|||||||
return E('div', { class: 'tachyon_updates-page__component' }, cardChildren);
|
return E('div', { class: 'tachyon_updates-page__component' }, cardChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isComponentCardVisible(
|
||||||
|
card: ComponentCard,
|
||||||
|
systemInfo: any,
|
||||||
|
): boolean {
|
||||||
|
const component = card.component;
|
||||||
|
if (component === 'tachyon' || component === 'sing_box') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (component === 'fptn') {
|
||||||
|
const fptnInstalled = Boolean(systemInfo.fptn_installed);
|
||||||
|
const fptnSupported =
|
||||||
|
systemInfo.fptn_supported === undefined
|
||||||
|
? true
|
||||||
|
: Boolean(systemInfo.fptn_supported);
|
||||||
|
if (!fptnInstalled && !fptnSupported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const optKey = `show_component_${component}`;
|
||||||
|
const val = (systemInfo as Record<string, any>)[optKey];
|
||||||
|
if (val === undefined || val === null || val === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return String(val) === '1' || val === 1;
|
||||||
|
}
|
||||||
|
|
||||||
function renderUpdatesComponents() {
|
function renderUpdatesComponents() {
|
||||||
const container = document.getElementById('tachyon_updates-components');
|
const container = document.getElementById('tachyon_updates-components');
|
||||||
|
|
||||||
@@ -2036,9 +2063,24 @@ function renderUpdatesComponents() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const systemInfo = normalizeSingBoxVariantFields(
|
||||||
|
store.get().diagnosticsSystemInfo,
|
||||||
|
);
|
||||||
|
const visibleCards = getComponentCards().filter((card) =>
|
||||||
|
isComponentCardVisible(card, systemInfo),
|
||||||
|
);
|
||||||
|
|
||||||
const columns: Node[][] = [[], [], []];
|
const columns: Node[][] = [[], [], []];
|
||||||
getComponentCards().forEach((card) => {
|
const colCounts = [0, 0, 0];
|
||||||
columns[card.column]?.push(renderComponentCard(card));
|
visibleCards.forEach((card) => {
|
||||||
|
colCounts[card.column] = (colCounts[card.column] || 0) + 1;
|
||||||
|
});
|
||||||
|
const hasEmptyColumn =
|
||||||
|
colCounts.some((c) => c === 0) && visibleCards.length >= 3;
|
||||||
|
|
||||||
|
visibleCards.forEach((card, idx) => {
|
||||||
|
const colIdx = hasEmptyColumn ? idx % 3 : card.column;
|
||||||
|
columns[colIdx]?.push(renderComponentCard(card));
|
||||||
});
|
});
|
||||||
|
|
||||||
return preserveScrollForPage(() => {
|
return preserveScrollForPage(() => {
|
||||||
|
|||||||
@@ -661,6 +661,7 @@ export namespace Tachyon {
|
|||||||
tailscale_installed: 0 | 1;
|
tailscale_installed: 0 | 1;
|
||||||
fptn_version: string;
|
fptn_version: string;
|
||||||
fptn_installed: 0 | 1;
|
fptn_installed: 0 | 1;
|
||||||
|
fptn_supported?: 0 | 1;
|
||||||
fptn_backup_version?: string;
|
fptn_backup_version?: string;
|
||||||
fptn_backup_time?: number;
|
fptn_backup_time?: number;
|
||||||
direct_bypass_enabled?: 0 | 1;
|
direct_bypass_enabled?: 0 | 1;
|
||||||
@@ -670,6 +671,15 @@ export namespace Tachyon {
|
|||||||
torrserver_direct_available?: 0 | 1;
|
torrserver_direct_available?: 0 | 1;
|
||||||
torrserver_direct_enabled?: 0 | 1;
|
torrserver_direct_enabled?: 0 | 1;
|
||||||
torrserver_direct_active?: 0 | 1;
|
torrserver_direct_active?: 0 | 1;
|
||||||
|
show_component_zapret?: 0 | 1;
|
||||||
|
show_component_zapret2?: 0 | 1;
|
||||||
|
show_component_byedpi?: 0 | 1;
|
||||||
|
show_component_wdtt?: 0 | 1;
|
||||||
|
show_component_olcrtc?: 0 | 1;
|
||||||
|
show_component_fptn?: 0 | 1;
|
||||||
|
show_component_tailscale?: 0 | 1;
|
||||||
|
show_component_direct_bypass?: 0 | 1;
|
||||||
|
show_component_torrserver_direct?: 0 | 1;
|
||||||
openwrt_version: string;
|
openwrt_version: string;
|
||||||
device_model: string;
|
device_model: string;
|
||||||
generated_at?: number;
|
generated_at?: number;
|
||||||
|
|||||||
@@ -4489,7 +4489,7 @@ var CLASH_API_FETCH_TIMEOUT_MS = 2500;
|
|||||||
var directClashApiFailedAt = 0;
|
var directClashApiFailedAt = 0;
|
||||||
var DIRECT_CLASH_API_COOLDOWN_MS = 1e4;
|
var DIRECT_CLASH_API_COOLDOWN_MS = 1e4;
|
||||||
function getDisplayName(section) {
|
function getDisplayName(section) {
|
||||||
return section.label || section[".name"];
|
return section.label || section.name || section[".name"];
|
||||||
}
|
}
|
||||||
function getSettingsSection(configSections) {
|
function getSettingsSection(configSections) {
|
||||||
return configSections.find((section) => section[".type"] === "settings");
|
return configSections.find((section) => section[".type"] === "settings");
|
||||||
@@ -5676,7 +5676,7 @@ async function getDashboardSections(options = {}) {
|
|||||||
outbounds: [
|
outbounds: [
|
||||||
{
|
{
|
||||||
code: outbound?.code || sectionName,
|
code: outbound?.code || sectionName,
|
||||||
displayName: section.label || section.interface || customName || defaultLabel,
|
displayName: section.label || section.name || section.interface || customName || defaultLabel,
|
||||||
latency: outbound?.value?.history?.length ? outbound.value.history[0].delay > 0 ? outbound.value.history[0].delay : -1 : 0,
|
latency: outbound?.value?.history?.length ? outbound.value.history[0].delay > 0 ? outbound.value.history[0].delay : -1 : 0,
|
||||||
type: ACTION_DISPLAY_NAMES[sectionAction || ""] || outbound?.value?.type || (sectionAction || "").toUpperCase(),
|
type: ACTION_DISPLAY_NAMES[sectionAction || ""] || outbound?.value?.type || (sectionAction || "").toUpperCase(),
|
||||||
selected: true,
|
selected: true,
|
||||||
@@ -13023,6 +13023,9 @@ function renderAiChatModal() {
|
|||||||
|
|
||||||
// src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
|
// src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
|
||||||
function renderStrategyFuzzerModal(ruleNames = []) {
|
function renderStrategyFuzzerModal(ruleNames = []) {
|
||||||
|
const normalizedRules = ruleNames.map(
|
||||||
|
(r) => typeof r === "string" ? { id: r, label: r } : r
|
||||||
|
);
|
||||||
let pollingInterval = null;
|
let pollingInterval = null;
|
||||||
let isPolling = false;
|
let isPolling = false;
|
||||||
let activeTab2 = "benchmark";
|
let activeTab2 = "benchmark";
|
||||||
@@ -13263,7 +13266,9 @@ function renderStrategyFuzzerModal(ruleNames = []) {
|
|||||||
{ class: "cbi-input-select", style: "width: 100%;" },
|
{ class: "cbi-input-select", style: "width: 100%;" },
|
||||||
[
|
[
|
||||||
E("option", { value: "", selected: true }, _("Provider Global Default")),
|
E("option", { value: "", selected: true }, _("Provider Global Default")),
|
||||||
...ruleNames.map((r) => E("option", { value: r }, `${_("Rule:")} ${r}`))
|
...normalizedRules.map(
|
||||||
|
(r) => E("option", { value: r.id }, `${_("Rule:")} ${r.label}`)
|
||||||
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
ruleSelect.addEventListener("change", () => {
|
ruleSelect.addEventListener("change", () => {
|
||||||
@@ -14773,19 +14778,20 @@ function renderStrategyFuzzerModal(ruleNames = []) {
|
|||||||
selectedRuleSection
|
selectedRuleSection
|
||||||
);
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
const targetLabel = normalizedRules.find((r) => r.id === selectedRuleSection)?.label || selectedRuleSection || _("Global Default");
|
||||||
if (typeof ui?.addNotification === "function") {
|
if (typeof ui?.addNotification === "function") {
|
||||||
ui.addNotification(
|
ui.addNotification(
|
||||||
_("Tachyon"),
|
_("Tachyon"),
|
||||||
E(
|
E(
|
||||||
"p",
|
"p",
|
||||||
{},
|
{},
|
||||||
`${_("Strategy applied successfully and service reloaded!")} (${item.name} -> ${selectedRuleSection || _("Global Default")})`
|
`${_("Strategy applied successfully and service reloaded!")} (${item.name} -> ${targetLabel})`
|
||||||
),
|
),
|
||||||
"info"
|
"info"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
showToast(
|
showToast(
|
||||||
`${_("Applied")} "${item.name}" -> ${selectedRuleSection || _("Global Default")}`,
|
`${_("Applied")} "${item.name}" -> ${targetLabel}`,
|
||||||
"success"
|
"success"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -17620,8 +17626,11 @@ function handleOpenLeakCheck() {
|
|||||||
}
|
}
|
||||||
function handleOpenStrategyFuzzer() {
|
function handleOpenStrategyFuzzer() {
|
||||||
getConfigSections().then((sections) => {
|
getConfigSections().then((sections) => {
|
||||||
const ruleNames = sections.filter((s) => s[".type"] === "section" || s[".type"] === "rule").map((s) => s.name || s[".name"]).filter((n) => Boolean(n));
|
const ruleSections = sections.filter((s) => s[".type"] === "section" || s[".type"] === "rule").map((s) => ({
|
||||||
renderStrategyFuzzerModal(ruleNames);
|
id: s[".name"],
|
||||||
|
label: typeof s.label === "string" && s.label.trim() || typeof s.name === "string" && s.name.trim() || s[".name"]
|
||||||
|
})).filter((r) => Boolean(r.id));
|
||||||
|
renderStrategyFuzzerModal(ruleSections);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
renderStrategyFuzzerModal([]);
|
renderStrategyFuzzerModal([]);
|
||||||
});
|
});
|
||||||
@@ -18642,7 +18651,7 @@ function formatEndpoint(address, port) {
|
|||||||
return `${normalizedAddress}:${normalizedPort}`;
|
return `${normalizedAddress}:${normalizedPort}`;
|
||||||
}
|
}
|
||||||
function getDisplayName2(section) {
|
function getDisplayName2(section) {
|
||||||
return normalizeString(section.label) || section[".name"];
|
return normalizeString(section.label) || normalizeString(section.name) || section[".name"];
|
||||||
}
|
}
|
||||||
function buildRouteDisplayNames(sections) {
|
function buildRouteDisplayNames(sections) {
|
||||||
const map = {
|
const map = {
|
||||||
@@ -22646,14 +22655,45 @@ function renderComponentCard(card) {
|
|||||||
cardChildren.push(actionsContainer);
|
cardChildren.push(actionsContainer);
|
||||||
return E("div", { class: "tachyon_updates-page__component" }, cardChildren);
|
return E("div", { class: "tachyon_updates-page__component" }, cardChildren);
|
||||||
}
|
}
|
||||||
|
function isComponentCardVisible(card, systemInfo) {
|
||||||
|
const component = card.component;
|
||||||
|
if (component === "tachyon" || component === "sing_box") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (component === "fptn") {
|
||||||
|
const fptnInstalled = Boolean(systemInfo.fptn_installed);
|
||||||
|
const fptnSupported = systemInfo.fptn_supported === void 0 ? true : Boolean(systemInfo.fptn_supported);
|
||||||
|
if (!fptnInstalled && !fptnSupported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const optKey = `show_component_${component}`;
|
||||||
|
const val = systemInfo[optKey];
|
||||||
|
if (val === void 0 || val === null || val === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return String(val) === "1" || val === 1;
|
||||||
|
}
|
||||||
function renderUpdatesComponents() {
|
function renderUpdatesComponents() {
|
||||||
const container = document.getElementById("tachyon_updates-components");
|
const container = document.getElementById("tachyon_updates-components");
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const systemInfo = normalizeSingBoxVariantFields(
|
||||||
|
store.get().diagnosticsSystemInfo
|
||||||
|
);
|
||||||
|
const visibleCards = getComponentCards().filter(
|
||||||
|
(card) => isComponentCardVisible(card, systemInfo)
|
||||||
|
);
|
||||||
const columns = [[], [], []];
|
const columns = [[], [], []];
|
||||||
getComponentCards().forEach((card) => {
|
const colCounts = [0, 0, 0];
|
||||||
columns[card.column]?.push(renderComponentCard(card));
|
visibleCards.forEach((card) => {
|
||||||
|
colCounts[card.column] = (colCounts[card.column] || 0) + 1;
|
||||||
|
});
|
||||||
|
const hasEmptyColumn = colCounts.some((c) => c === 0) && visibleCards.length >= 3;
|
||||||
|
visibleCards.forEach((card, idx) => {
|
||||||
|
const colIdx = hasEmptyColumn ? idx % 3 : card.column;
|
||||||
|
columns[colIdx]?.push(renderComponentCard(card));
|
||||||
});
|
});
|
||||||
return preserveScrollForPage(() => {
|
return preserveScrollForPage(() => {
|
||||||
container.replaceChildren(
|
container.replaceChildren(
|
||||||
|
|||||||
@@ -295,7 +295,10 @@ function createProfileContent(section) {
|
|||||||
o.modalonly = false;
|
o.modalonly = false;
|
||||||
o.cfgvalue = function (sectionId) {
|
o.cfgvalue = function (sectionId) {
|
||||||
const avatar = uci.get(UCI_PACKAGE, sectionId, "avatar") || "👶";
|
const avatar = uci.get(UCI_PACKAGE, sectionId, "avatar") || "👶";
|
||||||
const label = uci.get(UCI_PACKAGE, sectionId, "label") || sectionId;
|
const label =
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "name") ||
|
||||||
|
sectionId;
|
||||||
const rawDevs = uci.get(UCI_PACKAGE, sectionId, "device_ip");
|
const rawDevs = uci.get(UCI_PACKAGE, sectionId, "device_ip");
|
||||||
const devCount = normalizeListValues(rawDevs).length;
|
const devCount = normalizeListValues(rawDevs).length;
|
||||||
const devBadge =
|
const devBadge =
|
||||||
@@ -313,7 +316,10 @@ function createProfileContent(section) {
|
|||||||
};
|
};
|
||||||
o.textvalue = function (sectionId) {
|
o.textvalue = function (sectionId) {
|
||||||
const avatar = uci.get(UCI_PACKAGE, sectionId, "avatar") || "👶";
|
const avatar = uci.get(UCI_PACKAGE, sectionId, "avatar") || "👶";
|
||||||
const label = uci.get(UCI_PACKAGE, sectionId, "label") || sectionId;
|
const label =
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "name") ||
|
||||||
|
sectionId;
|
||||||
return `${avatar} ${label}`;
|
return `${avatar} ${label}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ function getUciSectionName(section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUciSectionLabel(section) {
|
function getUciSectionLabel(section) {
|
||||||
return (section && section.label) || getUciSectionName(section);
|
return (section && section.label) || (section && section.name) || getUciSectionName(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOutboundDetourTargetSection(section, currentSectionId) {
|
function isOutboundDetourTargetSection(section, currentSectionId) {
|
||||||
@@ -7767,7 +7767,11 @@ function createSectionContent(section) {
|
|||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
o.load = function (section_id) {
|
o.load = function (section_id) {
|
||||||
return uci.get(UCI_PACKAGE, section_id, "label") || section_id;
|
return (
|
||||||
|
uci.get(UCI_PACKAGE, section_id, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, section_id, "name") ||
|
||||||
|
section_id
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
o = section.taboption(
|
o = section.taboption(
|
||||||
@@ -11891,7 +11895,9 @@ async function performTrace(query) {
|
|||||||
(type === "domain" && detourDomains.includes(queryForMatching));
|
(type === "domain" && detourDomains.includes(queryForMatching));
|
||||||
if (isDnsMatch) {
|
if (isDnsMatch) {
|
||||||
const label =
|
const label =
|
||||||
uci.get(UCI_PACKAGE, detourSection, "label") || detourSection;
|
uci.get(UCI_PACKAGE, detourSection, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, detourSection, "name") ||
|
||||||
|
detourSection;
|
||||||
const action =
|
const action =
|
||||||
uci.get(UCI_PACKAGE, detourSection, "action") || "connection";
|
uci.get(UCI_PACKAGE, detourSection, "action") || "connection";
|
||||||
return {
|
return {
|
||||||
@@ -11916,7 +11922,10 @@ async function performTrace(query) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = uci.get(UCI_PACKAGE, secName, "label") || secName;
|
const label =
|
||||||
|
uci.get(UCI_PACKAGE, secName, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, secName, "name") ||
|
||||||
|
secName;
|
||||||
const action = uci.get(UCI_PACKAGE, secName, "action") || "connection";
|
const action = uci.get(UCI_PACKAGE, secName, "action") || "connection";
|
||||||
|
|
||||||
if (type === "port") {
|
if (type === "port") {
|
||||||
@@ -12176,7 +12185,10 @@ async function performTrace(query) {
|
|||||||
const secName = sec[".name"];
|
const secName = sec[".name"];
|
||||||
if (uci.get(UCI_PACKAGE, secName, "enabled") === "0") continue;
|
if (uci.get(UCI_PACKAGE, secName, "enabled") === "0") continue;
|
||||||
|
|
||||||
const label = uci.get(UCI_PACKAGE, secName, "label") || secName;
|
const label =
|
||||||
|
uci.get(UCI_PACKAGE, secName, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, secName, "name") ||
|
||||||
|
secName;
|
||||||
const action = uci.get(UCI_PACKAGE, secName, "action") || "connection";
|
const action = uci.get(UCI_PACKAGE, secName, "action") || "connection";
|
||||||
|
|
||||||
const ipCidr = normalizeOptionValues(
|
const ipCidr = normalizeOptionValues(
|
||||||
@@ -12574,7 +12586,10 @@ function createTracerSearchWidget(sectionRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showSectionRulesModal(section_id) {
|
function showSectionRulesModal(section_id) {
|
||||||
const sectionLabel = uci.get(UCI_PACKAGE, section_id, "label") || section_id;
|
const sectionLabel =
|
||||||
|
uci.get(UCI_PACKAGE, section_id, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, section_id, "name") ||
|
||||||
|
section_id;
|
||||||
|
|
||||||
const getCleanList = (key) => {
|
const getCleanList = (key) => {
|
||||||
let val = uci.get(UCI_PACKAGE, section_id, key);
|
let val = uci.get(UCI_PACKAGE, section_id, key);
|
||||||
@@ -12822,7 +12837,7 @@ function configureSectionSection(sectionRef, options = {}) {
|
|||||||
setActionProvidersAvailabilityLoader(options.loadActionProvidersAvailability);
|
setActionProvidersAvailabilityLoader(options.loadActionProvidersAvailability);
|
||||||
|
|
||||||
sectionRef.sectiontitle = function (section_id) {
|
sectionRef.sectiontitle = function (section_id) {
|
||||||
return uci.get(UCI_PACKAGE, section_id, "label") || section_id;
|
return uci.get(UCI_PACKAGE, section_id, "label") || uci.get(UCI_PACKAGE, section_id, "name") || section_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
sectionRef.load = function () {
|
sectionRef.load = function () {
|
||||||
|
|||||||
@@ -596,7 +596,11 @@ function applyServerCapabilities(sectionRef, capabilities) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getServerName(sectionId) {
|
function getServerName(sectionId) {
|
||||||
return uci.get(UCI_PACKAGE, sectionId, "label") || sectionId;
|
return (
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "label") ||
|
||||||
|
uci.get(UCI_PACKAGE, sectionId, "name") ||
|
||||||
|
sectionId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSocksUsername(sectionId) {
|
function getSocksUsername(sectionId) {
|
||||||
|
|||||||
@@ -1288,6 +1288,200 @@ function createMenuTabsOrderWidget(option, section_id) {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createComponentsVisibilityWidget(option, section_id) {
|
||||||
|
const ALL_COMPONENTS = [
|
||||||
|
{
|
||||||
|
id: "tachyon",
|
||||||
|
label: "Tachyon",
|
||||||
|
desc: _("Core orchestration service and LuCI application"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sing_box",
|
||||||
|
label: "Sing-box",
|
||||||
|
desc: _("Universal proxy platform routing engine"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "zapret",
|
||||||
|
label: "Zapret",
|
||||||
|
desc: _("DPI bypass engine (nfqws v1)"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "zapret2",
|
||||||
|
label: "Zapret2",
|
||||||
|
desc: _("Advanced DPI bypass engine with Lua scripts (nfqws2)"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "byedpi",
|
||||||
|
label: "ByeDPI",
|
||||||
|
desc: _("Local SOCKS proxy desynchronizer (ciadpi)"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wdtt",
|
||||||
|
label: "WDTT",
|
||||||
|
desc: _("Western Digital Tunnel Transport"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "olcrtc",
|
||||||
|
label: "OlcRTC",
|
||||||
|
desc: _("Real-time WebRTC anti-censorship tunnel"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fptn",
|
||||||
|
label: "FPTN",
|
||||||
|
desc: _("Fast packet transport network client"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "tailscale",
|
||||||
|
label: "Tailscale",
|
||||||
|
desc: _("Zero config mesh VPN daemon"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "direct_bypass",
|
||||||
|
label: _("Direct Proxy"),
|
||||||
|
desc: _("Dedicated direct HTTP/SOCKS5 outbound proxy"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "torrserver_direct",
|
||||||
|
label: _("TorrServer Direct"),
|
||||||
|
desc: _("Direct cgroup bypass for TorrServer"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const visibility = {};
|
||||||
|
ALL_COMPONENTS.forEach(function (c) {
|
||||||
|
if (c.fixed) {
|
||||||
|
visibility[c.id] = true;
|
||||||
|
} else {
|
||||||
|
const optKey = "show_component_" + c.id;
|
||||||
|
const val = uci.get(UCI_PACKAGE, section_id, optKey);
|
||||||
|
if (val === undefined || val === null || val === "") {
|
||||||
|
visibility[c.id] = c.defaultVisible;
|
||||||
|
} else {
|
||||||
|
visibility[c.id] = val === "1" || val === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = E("div", { id: "components-visibility-widget-" + section_id });
|
||||||
|
const listEl = E("div", {
|
||||||
|
style:
|
||||||
|
"border:1px solid var(--border-color,#dee2e6);border-radius:6px;overflow:hidden;margin-bottom:8px;max-width:560px;background:var(--card-bg,transparent);",
|
||||||
|
});
|
||||||
|
|
||||||
|
function syncToUci() {
|
||||||
|
wrapper._vis = Object.assign({}, visibility);
|
||||||
|
ALL_COMPONENTS.forEach(function (c) {
|
||||||
|
if (!c.fixed) {
|
||||||
|
const optKey = "show_component_" + c.id;
|
||||||
|
uci.set(UCI_PACKAGE, section_id, optKey, visibility[c.id] ? "1" : "0");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderList() {
|
||||||
|
listEl.innerHTML = "";
|
||||||
|
ALL_COMPONENTS.forEach(function (c, idx) {
|
||||||
|
const isVisible = Boolean(visibility[c.id]);
|
||||||
|
const isFixed = Boolean(c.fixed);
|
||||||
|
|
||||||
|
const row = E("div", {
|
||||||
|
style: [
|
||||||
|
"display:flex;align-items:center;gap:12px;padding:8px 12px;",
|
||||||
|
idx < ALL_COMPONENTS.length - 1
|
||||||
|
? "border-bottom:1px solid var(--border-color,#dee2e6);"
|
||||||
|
: "",
|
||||||
|
isVisible ? "" : "opacity:0.5;background:rgba(0,0,0,0.02);",
|
||||||
|
].join(""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleBtn = E("button", {
|
||||||
|
type: "button",
|
||||||
|
title: isFixed
|
||||||
|
? _("Always visible")
|
||||||
|
: isVisible
|
||||||
|
? _("Click to hide")
|
||||||
|
: _("Click to show"),
|
||||||
|
style: [
|
||||||
|
"width:20px;height:20px;padding:0;border-radius:3px;border:2px solid;",
|
||||||
|
"display:flex;align-items:center;justify-content:center;",
|
||||||
|
"font-size:13px;font-weight:bold;line-height:1;cursor:",
|
||||||
|
isFixed ? "default;" : "pointer;",
|
||||||
|
"flex-shrink:0;",
|
||||||
|
isFixed || isVisible
|
||||||
|
? "border-color:var(--primary-color,#2196f3);background:var(--primary-color,#2196f3);color:#fff;"
|
||||||
|
: "border-color:var(--border-color,#aaa);background:transparent;color:transparent;",
|
||||||
|
].join(""),
|
||||||
|
});
|
||||||
|
toggleBtn.textContent = "✓";
|
||||||
|
if (isFixed) toggleBtn.disabled = true;
|
||||||
|
|
||||||
|
if (!isFixed) {
|
||||||
|
toggleBtn.addEventListener("click", function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
visibility[c.id] = !visibility[c.id];
|
||||||
|
syncToUci();
|
||||||
|
renderList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const textWrap = E(
|
||||||
|
"div",
|
||||||
|
{ style: "flex:1;min-width:0;line-height:1.3;" },
|
||||||
|
[
|
||||||
|
E(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style:
|
||||||
|
"font-weight:600;font-size:0.875rem;color:var(--text-color,#212529);",
|
||||||
|
},
|
||||||
|
c.label,
|
||||||
|
),
|
||||||
|
E(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style:
|
||||||
|
"font-size:0.75rem;color:var(--text-color-medium,#6c757d);",
|
||||||
|
},
|
||||||
|
c.desc,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
row.appendChild(toggleBtn);
|
||||||
|
row.appendChild(textWrap);
|
||||||
|
listEl.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList();
|
||||||
|
syncToUci();
|
||||||
|
|
||||||
|
wrapper.appendChild(listEl);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
function createSettingsContent(section, capabilities) {
|
function createSettingsContent(section, capabilities) {
|
||||||
section.tab("navigation", _("Menu & Navigation"));
|
section.tab("navigation", _("Menu & Navigation"));
|
||||||
section.tab("dns", _("DNS Settings"));
|
section.tab("dns", _("DNS Settings"));
|
||||||
@@ -1342,6 +1536,25 @@ function createSettingsContent(section, capabilities) {
|
|||||||
};
|
};
|
||||||
tabsOrderOpt.remove = function () {};
|
tabsOrderOpt.remove = function () {};
|
||||||
|
|
||||||
|
const compVisOpt = section.taboption(
|
||||||
|
"navigation",
|
||||||
|
form.Value,
|
||||||
|
"_components_visibility_widget",
|
||||||
|
_("Component Cards Visibility"),
|
||||||
|
_(
|
||||||
|
"Customize which component cards are displayed in the Components tab. Checkboxes show or hide components.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
compVisOpt.renderWidget = function (section_id) {
|
||||||
|
return createComponentsVisibilityWidget(this, section_id);
|
||||||
|
};
|
||||||
|
compVisOpt.formvalue = function (section_id) {
|
||||||
|
const el = document.getElementById("components-visibility-widget-" + section_id);
|
||||||
|
return el ? JSON.stringify(el._vis) : "";
|
||||||
|
};
|
||||||
|
compVisOpt.write = function (_section_id) {};
|
||||||
|
compVisOpt.remove = function () {};
|
||||||
|
|
||||||
// ── DNS Settings ────────────────────────────────────────────────────────
|
// ── DNS Settings ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const dnsBenchmarkBtn = section.taboption(
|
const dnsBenchmarkBtn = section.taboption(
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ function configureGridSection(sectionRef, type, title, addTitle) {
|
|||||||
sectionRef.rowcolors = true;
|
sectionRef.rowcolors = true;
|
||||||
sectionRef.nodescriptions = true;
|
sectionRef.nodescriptions = true;
|
||||||
sectionRef.modaltitle = function (section_id) {
|
sectionRef.modaltitle = function (section_id) {
|
||||||
const label = uci.get(UCI_PACKAGE, section_id, "label");
|
const label = uci.get(UCI_PACKAGE, section_id, "label") || uci.get(UCI_PACKAGE, section_id, "name");
|
||||||
return section_id ? `${title}: ${label || section_id}` : addTitle;
|
return section_id ? `${title}: ${label || section_id}` : addTitle;
|
||||||
};
|
};
|
||||||
sectionRef.sectiontitle = function (section_id) {
|
sectionRef.sectiontitle = function (section_id) {
|
||||||
return uci.get(UCI_PACKAGE, section_id, "label") || section_id;
|
return uci.get(UCI_PACKAGE, section_id, "label") || uci.get(UCI_PACKAGE, section_id, "name") || section_id;
|
||||||
};
|
};
|
||||||
sectionRef.renderSectionAdd = function (extra_class) {
|
sectionRef.renderSectionAdd = function (extra_class) {
|
||||||
return renderSectionAdd(sectionRef, extra_class);
|
return renderSectionAdd(sectionRef, extra_class);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: TACHYON\n"
|
"Project-Id-Version: TACHYON\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-09-16 15:18+0300\n"
|
"POT-Creation-Date: 2026-09-16 16:15+0300\n"
|
||||||
"PO-Revision-Date: 2026-09-16 15:18+0300\n"
|
"PO-Revision-Date: 2026-09-16 16:15+0300\n"
|
||||||
"Last-Translator: Dushnilin\n"
|
"Last-Translator: Dushnilin\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
@@ -311,6 +311,9 @@ msgstr "Chat ID администраторов"
|
|||||||
msgid "Adult content"
|
msgid "Adult content"
|
||||||
msgstr "Взрослый контент"
|
msgstr "Взрослый контент"
|
||||||
|
|
||||||
|
msgid "Advanced DPI bypass engine with Lua scripts (nfqws2)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Advanced System"
|
msgid "Advanced System"
|
||||||
msgstr "Расширенные настройки"
|
msgstr "Расширенные настройки"
|
||||||
|
|
||||||
@@ -1079,6 +1082,9 @@ msgstr "Community-списки"
|
|||||||
msgid "Component action timed out"
|
msgid "Component action timed out"
|
||||||
msgstr "Превышено время ожидания действия компонента"
|
msgstr "Превышено время ожидания действия компонента"
|
||||||
|
|
||||||
|
msgid "Component Cards Visibility"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Component update available"
|
msgid "Component update available"
|
||||||
msgstr "Доступно обновление компонента"
|
msgstr "Доступно обновление компонента"
|
||||||
|
|
||||||
@@ -1184,6 +1190,9 @@ msgstr "Скопировать отчет для поддержки"
|
|||||||
msgid "Core configuration and preferences"
|
msgid "Core configuration and preferences"
|
||||||
msgstr "Основные настройки и параметры"
|
msgstr "Основные настройки и параметры"
|
||||||
|
|
||||||
|
msgid "Core orchestration service and LuCI application"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Could not compare FakeIP and control public IPs"
|
msgid "Could not compare FakeIP and control public IPs"
|
||||||
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
|
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
|
||||||
|
|
||||||
@@ -1253,6 +1262,9 @@ msgstr "Пользовательская стратегия добавлена!"
|
|||||||
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
|
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
|
||||||
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
|
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
|
||||||
|
|
||||||
|
msgid "Customize which component cards are displayed in the Components tab. Checkboxes show or hide components."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Daily Quota"
|
msgid "Daily Quota"
|
||||||
msgstr "Дневная квота"
|
msgstr "Дневная квота"
|
||||||
|
|
||||||
@@ -1286,6 +1298,9 @@ msgstr "Дни недели"
|
|||||||
msgid "Dedicated cgroup unavailable"
|
msgid "Dedicated cgroup unavailable"
|
||||||
msgstr "Выделенная cgroup недоступна"
|
msgstr "Выделенная cgroup недоступна"
|
||||||
|
|
||||||
|
msgid "Dedicated direct HTTP/SOCKS5 outbound proxy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "По умолчанию"
|
msgstr "По умолчанию"
|
||||||
|
|
||||||
@@ -1370,6 +1385,9 @@ msgstr "Прямое (по умолчанию)"
|
|||||||
msgid "Direct Bypass"
|
msgid "Direct Bypass"
|
||||||
msgstr "Прямой WAN"
|
msgstr "Прямой WAN"
|
||||||
|
|
||||||
|
msgid "Direct cgroup bypass for TorrServer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Direct connection"
|
msgid "Direct connection"
|
||||||
msgstr "Прямое подключение"
|
msgstr "Прямое подключение"
|
||||||
|
|
||||||
@@ -1622,6 +1640,9 @@ msgstr "Загрузка и применение наборов правил и
|
|||||||
msgid "DPI bandwidth throttling or delay detected — connection throttled during data transfer."
|
msgid "DPI bandwidth throttling or delay detected — connection throttled during data transfer."
|
||||||
msgstr "Обнаружено замедление DPI — соединение заблокировано или задушено при передаче данных."
|
msgstr "Обнаружено замедление DPI — соединение заблокировано или задушено при передаче данных."
|
||||||
|
|
||||||
|
msgid "DPI bypass engine (nfqws v1)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "DPI Detected"
|
msgid "DPI Detected"
|
||||||
msgstr "DPI обнаружен"
|
msgstr "DPI обнаружен"
|
||||||
|
|
||||||
@@ -2126,6 +2147,9 @@ msgstr "Семейные профили"
|
|||||||
msgid "Family profiles and device grouping"
|
msgid "Family profiles and device grouping"
|
||||||
msgstr "Семейные профили и группировка устройств"
|
msgstr "Семейные профили и группировка устройств"
|
||||||
|
|
||||||
|
msgid "Fast packet transport network client"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
|
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
|
||||||
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
|
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
|
||||||
|
|
||||||
@@ -3071,6 +3095,9 @@ msgstr "Загрузка..."
|
|||||||
msgid "Local Address"
|
msgid "Local Address"
|
||||||
msgstr "Локальный адрес"
|
msgstr "Локальный адрес"
|
||||||
|
|
||||||
|
msgid "Local SOCKS proxy desynchronizer (ciadpi)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Местоположение"
|
msgstr "Местоположение"
|
||||||
|
|
||||||
@@ -3959,6 +3986,9 @@ msgstr "Ошибка чтения"
|
|||||||
msgid "Ready to benchmark DNS providers"
|
msgid "Ready to benchmark DNS providers"
|
||||||
msgstr "Готов к тестированию DNS провайдеров"
|
msgstr "Готов к тестированию DNS провайдеров"
|
||||||
|
|
||||||
|
msgid "Real-time WebRTC anti-censorship tunnel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Reality fake site"
|
msgid "Reality fake site"
|
||||||
msgstr "Фейковый сайт Reality"
|
msgstr "Фейковый сайт Reality"
|
||||||
|
|
||||||
@@ -5489,6 +5519,9 @@ msgstr "Великобритания (gb)"
|
|||||||
msgid "United States (us)"
|
msgid "United States (us)"
|
||||||
msgstr "США (us)"
|
msgstr "США (us)"
|
||||||
|
|
||||||
|
msgid "Universal proxy platform routing engine"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "unknown"
|
msgid "unknown"
|
||||||
msgstr "неизвестно"
|
msgstr "неизвестно"
|
||||||
|
|
||||||
@@ -5834,6 +5867,9 @@ msgstr "Будни (Пн-Пт)"
|
|||||||
msgid "Weekends (Sat-Sun)"
|
msgid "Weekends (Sat-Sun)"
|
||||||
msgstr "Выходные (Сб-Вс)"
|
msgstr "Выходные (Сб-Вс)"
|
||||||
|
|
||||||
|
msgid "Western Digital Tunnel Transport"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "What Tachyon should do when this section matches"
|
msgid "What Tachyon should do when this section matches"
|
||||||
msgstr "Что должен делать Tachyon при совпадении этой секции"
|
msgstr "Что должен делать Tachyon при совпадении этой секции"
|
||||||
|
|
||||||
@@ -5942,6 +5978,9 @@ msgstr "Правила маршрутизации sing-box для Zapret2 нас
|
|||||||
msgid "Zapret2 sing-box route rules are not configured"
|
msgid "Zapret2 sing-box route rules are not configured"
|
||||||
msgstr "Правила маршрутизации sing-box для Zapret2 не настроены"
|
msgstr "Правила маршрутизации sing-box для Zapret2 не настроены"
|
||||||
|
|
||||||
|
msgid "Zero config mesh VPN daemon"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Testing strategy"
|
msgid "Testing strategy"
|
||||||
msgstr "Тестирование стратегии"
|
msgstr "Тестирование стратегии"
|
||||||
|
|
||||||
|
|||||||
+1867
-1812
File diff suppressed because it is too large
Load Diff
@@ -135,7 +135,11 @@ function get_zapret2_lua_flags(args_str) {
|
|||||||
LIB_DIR + "/providers/zapret2/lua",
|
LIB_DIR + "/providers/zapret2/lua",
|
||||||
"/usr/lib/tachyon/providers/zapret2/lua",
|
"/usr/lib/tachyon/providers/zapret2/lua",
|
||||||
"/opt/zapret2/lua",
|
"/opt/zapret2/lua",
|
||||||
|
"/opt/zapret2/files/lua",
|
||||||
|
"/opt/zapret2/share/zapret/lua",
|
||||||
|
"/opt/zapret2/init.d/sysv/lua",
|
||||||
"/opt/zapret/lua",
|
"/opt/zapret/lua",
|
||||||
|
"/opt/zapret/files/lua",
|
||||||
"/usr/share/zapret2/lua",
|
"/usr/share/zapret2/lua",
|
||||||
"/usr/share/zapret/lua",
|
"/usr/share/zapret/lua",
|
||||||
"/etc/zapret2/lua",
|
"/etc/zapret2/lua",
|
||||||
@@ -2645,6 +2649,11 @@ function run_probe(engine, args_str, target_key, custom_url, job_id) {
|
|||||||
if (is_z2) {
|
if (is_z2) {
|
||||||
lua_init_flags = get_zapret2_lua_flags(args_str);
|
lua_init_flags = get_zapret2_lua_flags(args_str);
|
||||||
blob_flags = resolve_zapret2_blobs(args_str);
|
blob_flags = resolve_zapret2_blobs(args_str);
|
||||||
|
if (index(args_str, "--lua-desync") >= 0 && lua_init_flags == "" && index(args_str, "--lua-init") < 0) {
|
||||||
|
result.error = "Missing Zapret2 Lua library: zapret-antidpi.lua not found in /opt/zapret2/lua or system paths";
|
||||||
|
cleanup_temp_daemons(job_id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter_prefix = "";
|
let filter_prefix = "";
|
||||||
@@ -2664,9 +2673,9 @@ function run_probe(engine, args_str, target_key, custom_url, job_id) {
|
|||||||
fwmark_flag = sprintf("--dpi-desync-fwmark=%s ", FUZZER_FWMARK);
|
fwmark_flag = sprintf("--dpi-desync-fwmark=%s ", FUZZER_FWMARK);
|
||||||
}
|
}
|
||||||
|
|
||||||
let argv = fuzzer_runner.build_zapret_argv(bin, qnum, fwmark_flag, lua_init_flags, blob_flags, filter_prefix, tok_res.tokens, pid_path);
|
let argv = fuzzer_runner.build_zapret_argv(bin, qnum, fwmark_flag, lua_init_flags, blob_flags, filter_prefix, tok_res.tokens);
|
||||||
let spawn_cmd = "cd /tmp && " + common.command_from_args(argv) + " >" + shell_quote(stderr_log) + " 2>&1";
|
let spawn_cmd = "cd /tmp && " + common.command_from_args(argv) + " 2>" + shell_quote(stderr_log);
|
||||||
system(common.background_command(spawn_cmd));
|
system(common.background_command_with_pid(spawn_cmd, ">/dev/null", ">" + shell_quote(pid_path)));
|
||||||
|
|
||||||
let pid_running = false;
|
let pid_running = false;
|
||||||
for (let wait_i = 0; wait_i < 15; wait_i++) {
|
for (let wait_i = 0; wait_i < 15; wait_i++) {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ function build_byedpi_argv(bin, port, tokens) {
|
|||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
function build_zapret_argv(bin, qnum, fwmark_flag, lua_init_flags, blob_flags, filter_prefix, tokens, pid_path) {
|
function build_zapret_argv(bin, qnum, fwmark_flag, lua_init_flags, blob_flags, filter_prefix, tokens) {
|
||||||
let argv = [ bin, sprintf("--qnum=%d", qnum) ];
|
let argv = [ bin, sprintf("--qnum=%d", qnum) ];
|
||||||
|
|
||||||
let append_flags = function(flag_str) {
|
let append_flags = function(flag_str) {
|
||||||
@@ -113,13 +113,12 @@ function build_zapret_argv(bin, qnum, fwmark_flag, lua_init_flags, blob_flags, f
|
|||||||
append_flags(blob_flags);
|
append_flags(blob_flags);
|
||||||
append_flags(filter_prefix);
|
append_flags(filter_prefix);
|
||||||
|
|
||||||
for (let tok in tokens)
|
for (let tok in tokens) {
|
||||||
|
if (tok == "--daemon" || index(tok, "--pidfile") == 0)
|
||||||
|
continue;
|
||||||
push(argv, tok);
|
push(argv, tok);
|
||||||
|
}
|
||||||
|
|
||||||
if (pid_path && pid_path != "")
|
|
||||||
push(argv, sprintf("--pidfile=%s", pid_path));
|
|
||||||
|
|
||||||
push(argv, "--daemon");
|
|
||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1140,6 +1140,47 @@ function openwrt_release() {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_system_arch_candidates() {
|
||||||
|
let arch_list = "";
|
||||||
|
if (fs.stat("/etc/apk/arch") != null) {
|
||||||
|
arch_list += " " + trim(as_string(fs.readfile("/etc/apk/arch")));
|
||||||
|
}
|
||||||
|
if (fs.stat("/etc/openwrt_release") != null) {
|
||||||
|
let content = as_string(fs.readfile("/etc/openwrt_release"));
|
||||||
|
for (let line in split(content, "\n")) {
|
||||||
|
if (substr(line, 0, length("DISTRIB_ARCH=")) == "DISTRIB_ARCH=") {
|
||||||
|
let v = substr(line, length("DISTRIB_ARCH="));
|
||||||
|
if (length(v) >= 2) {
|
||||||
|
let q = substr(v, 0, 1);
|
||||||
|
if ((q == "\"" || q == "'") && substr(v, length(v) - 1) == q)
|
||||||
|
v = substr(v, 1, length(v) - 2);
|
||||||
|
}
|
||||||
|
arch_list += " " + v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (command_exists("uname")) {
|
||||||
|
arch_list += " " + trim(command_output_from_args([ "uname", "-m" ]));
|
||||||
|
}
|
||||||
|
return arch_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_fptn_supported() {
|
||||||
|
let arch_list = get_system_arch_candidates();
|
||||||
|
let supported = [
|
||||||
|
"x86_64", "amd64",
|
||||||
|
"aarch64", "arm64",
|
||||||
|
"arm_cortex-a7_neon-vfpv4",
|
||||||
|
"arm_cortex-a7"
|
||||||
|
];
|
||||||
|
for (let s in supported) {
|
||||||
|
if (index(arch_list, s) >= 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function build_system_info() {
|
function build_system_info() {
|
||||||
let tachyon_latest_version = first_line_value("/tmp/tachyon.latest-version.cache", "unknown");
|
let tachyon_latest_version = first_line_value("/tmp/tachyon.latest-version.cache", "unknown");
|
||||||
let luci_app_version = get_luci_app_version();
|
let luci_app_version = get_luci_app_version();
|
||||||
@@ -1177,6 +1218,7 @@ function build_system_info() {
|
|||||||
let tailscale_installed = provider_installed(TAILSCALE_RUNTIME_UC) ? 1 : 0;
|
let tailscale_installed = provider_installed(TAILSCALE_RUNTIME_UC) ? 1 : 0;
|
||||||
let tailscale_version = tailscale_installed ? provider_version(TAILSCALE_RUNTIME_UC) : "not installed";
|
let tailscale_version = tailscale_installed ? provider_version(TAILSCALE_RUNTIME_UC) : "not installed";
|
||||||
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
|
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
|
||||||
|
let fptn_supported = (fptn_installed == 1 || is_fptn_supported()) ? 1 : 0;
|
||||||
let fptn_version = fptn_installed ? provider_version(FPTN_RUNTIME_UC) : "not installed";
|
let fptn_version = fptn_installed ? provider_version(FPTN_RUNTIME_UC) : "not installed";
|
||||||
let device_model = first_line_value("/tmp/sysinfo/model", "unknown");
|
let device_model = first_line_value("/tmp/sysinfo/model", "unknown");
|
||||||
|
|
||||||
@@ -1189,6 +1231,16 @@ function build_system_info() {
|
|||||||
if (type(torrserver_direct_status) != "object")
|
if (type(torrserver_direct_status) != "object")
|
||||||
torrserver_direct_status = {};
|
torrserver_direct_status = {};
|
||||||
|
|
||||||
|
let show_component_zapret = bool_option(settings(), "show_component_zapret", true) ? 1 : 0;
|
||||||
|
let show_component_zapret2 = bool_option(settings(), "show_component_zapret2", true) ? 1 : 0;
|
||||||
|
let show_component_byedpi = bool_option(settings(), "show_component_byedpi", true) ? 1 : 0;
|
||||||
|
let show_component_wdtt = bool_option(settings(), "show_component_wdtt", true) ? 1 : 0;
|
||||||
|
let show_component_olcrtc = bool_option(settings(), "show_component_olcrtc", true) ? 1 : 0;
|
||||||
|
let show_component_fptn = bool_option(settings(), "show_component_fptn", true) ? 1 : 0;
|
||||||
|
let show_component_tailscale = bool_option(settings(), "show_component_tailscale", true) ? 1 : 0;
|
||||||
|
let show_component_direct_bypass = bool_option(settings(), "show_component_direct_bypass", true) ? 1 : 0;
|
||||||
|
let show_component_torrserver_direct = bool_option(settings(), "show_component_torrserver_direct", true) ? 1 : 0;
|
||||||
|
|
||||||
let base_bdir = getenv("TACHYON_COMPONENT_BACKUPS_DIR") || "/etc/tachyon/component-backups";
|
let base_bdir = getenv("TACHYON_COMPONENT_BACKUPS_DIR") || "/etc/tachyon/component-backups";
|
||||||
let read_backup_meta = function(comp) {
|
let read_backup_meta = function(comp) {
|
||||||
let path = base_bdir + "/" + comp + "/metadata.json";
|
let path = base_bdir + "/" + comp + "/metadata.json";
|
||||||
@@ -1241,6 +1293,7 @@ function build_system_info() {
|
|||||||
tailscale_backup_time: tailscale_meta ? int(tailscale_meta.timestamp || 0) : 0,
|
tailscale_backup_time: tailscale_meta ? int(tailscale_meta.timestamp || 0) : 0,
|
||||||
fptn_version,
|
fptn_version,
|
||||||
fptn_installed,
|
fptn_installed,
|
||||||
|
fptn_supported,
|
||||||
fptn_backup_version: fptn_meta ? as_string(fptn_meta.version) : "",
|
fptn_backup_version: fptn_meta ? as_string(fptn_meta.version) : "",
|
||||||
fptn_backup_time: fptn_meta ? int(fptn_meta.timestamp || 0) : 0,
|
fptn_backup_time: fptn_meta ? int(fptn_meta.timestamp || 0) : 0,
|
||||||
direct_bypass_enabled,
|
direct_bypass_enabled,
|
||||||
@@ -1250,6 +1303,15 @@ function build_system_info() {
|
|||||||
torrserver_direct_available: int(torrserver_direct_status.available || 0),
|
torrserver_direct_available: int(torrserver_direct_status.available || 0),
|
||||||
torrserver_direct_enabled: int(torrserver_direct_status.enabled || 0),
|
torrserver_direct_enabled: int(torrserver_direct_status.enabled || 0),
|
||||||
torrserver_direct_active: int(torrserver_direct_status.active || 0),
|
torrserver_direct_active: int(torrserver_direct_status.active || 0),
|
||||||
|
show_component_zapret,
|
||||||
|
show_component_zapret2,
|
||||||
|
show_component_byedpi,
|
||||||
|
show_component_wdtt,
|
||||||
|
show_component_olcrtc,
|
||||||
|
show_component_fptn,
|
||||||
|
show_component_tailscale,
|
||||||
|
show_component_direct_bypass,
|
||||||
|
show_component_torrserver_direct,
|
||||||
openwrt_version: openwrt_release(),
|
openwrt_version: openwrt_release(),
|
||||||
device_model,
|
device_model,
|
||||||
generated_at: int(clock()[0])
|
generated_at: int(clock()[0])
|
||||||
|
|||||||
@@ -1209,7 +1209,7 @@ function view_sections(token, chat_id, msg_id) {
|
|||||||
let keyboard = [];
|
let keyboard = [];
|
||||||
|
|
||||||
for (let s in sections) {
|
for (let s in sections) {
|
||||||
let label = s.label || s[".name"];
|
let label = s.label || s.name || s[".name"];
|
||||||
let status = (s.enabled == "1") ? "✅" : "❌";
|
let status = (s.enabled == "1") ? "✅" : "❌";
|
||||||
push(keyboard, [{ text: status + " " + label, callback_data: "/sec_view " + s[".name"] }]);
|
push(keyboard, [{ text: status + " " + label, callback_data: "/sec_view " + s[".name"] }]);
|
||||||
}
|
}
|
||||||
@@ -1227,7 +1227,7 @@ function view_section_editor(token, chat_id, msg_id, sec_name) {
|
|||||||
if (!s) return view_sections(token, chat_id, msg_id);
|
if (!s) return view_sections(token, chat_id, msg_id);
|
||||||
|
|
||||||
let status = (s.enabled == "1") ? t("status_enabled") : t("status_disabled");
|
let status = (s.enabled == "1") ? t("status_enabled") : t("status_disabled");
|
||||||
let text = "⚙️ <b>" + t("section_section") + ":</b> " + escape_html(s.label || sec_name) + "\n" +
|
let text = "⚙️ <b>" + t("section_section") + ":</b> " + escape_html(s.label || s.name || sec_name) + "\n" +
|
||||||
t("section_type") + ": <code>" + escape_html(s.action || "none") + "</code>\n" +
|
t("section_type") + ": <code>" + escape_html(s.action || "none") + "</code>\n" +
|
||||||
t("section_status") + ": <b>" + status + "</b>\n\n";
|
t("section_status") + ": <b>" + status + "</b>\n\n";
|
||||||
|
|
||||||
@@ -1340,7 +1340,7 @@ function view_sec_list(token, chat_id, msg_id, sec_name, list_type) {
|
|||||||
for (let x in rs) push(items, {type: "community_lists", val: x});
|
for (let x in rs) push(items, {type: "community_lists", val: x});
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = t("section_list_header") + " " + escape_html(s.label || sec_name) + "\n" +
|
let text = t("section_list_header") + " " + escape_html(s.label || s.name || sec_name) + "\n" +
|
||||||
"📋 <b>" + title + "</b>:\n\n";
|
"📋 <b>" + title + "</b>:\n\n";
|
||||||
|
|
||||||
let keyboard = [];
|
let keyboard = [];
|
||||||
|
|||||||
@@ -1471,16 +1471,47 @@ function outbound_supports_udp(config, tag_name, visited) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function push_section_route_rule(config, rule, target_outbound) {
|
function apply_excluded_source_ips(rule, excluded_cidrs) {
|
||||||
|
if (!rule || !excluded_cidrs || length(excluded_cidrs) == 0)
|
||||||
|
return rule;
|
||||||
|
|
||||||
|
let action_keys = {
|
||||||
|
action: true,
|
||||||
|
outbound: true,
|
||||||
|
server: true,
|
||||||
|
rewrite_ttl: true
|
||||||
|
};
|
||||||
|
let match_part = {};
|
||||||
|
let logical_rule = {
|
||||||
|
type: "logical",
|
||||||
|
mode: "and",
|
||||||
|
rules: [
|
||||||
|
match_part,
|
||||||
|
{
|
||||||
|
invert: true,
|
||||||
|
source_ip_cidr: single_or_array(excluded_cidrs)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
for (let k, v in rule) {
|
||||||
|
if (action_keys[k])
|
||||||
|
logical_rule[k] = v;
|
||||||
|
else
|
||||||
|
match_part[k] = v;
|
||||||
|
}
|
||||||
|
return logical_rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_section_route_rule(config, rule, target_outbound, excluded_cidrs) {
|
||||||
if (target_outbound && !outbound_supports_udp(config, target_outbound)) {
|
if (target_outbound && !outbound_supports_udp(config, target_outbound)) {
|
||||||
if (rule.network == "tcp") {
|
if (rule.network == "tcp") {
|
||||||
push(config.route.rules, rule);
|
push(config.route.rules, apply_excluded_source_ips(rule, excluded_cidrs));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rule.network == "udp") {
|
if (rule.network == "udp") {
|
||||||
delete rule.outbound;
|
delete rule.outbound;
|
||||||
rule.action = "reject";
|
rule.action = "reject";
|
||||||
push(config.route.rules, rule);
|
push(config.route.rules, apply_excluded_source_ips(rule, excluded_cidrs));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let udp_rule = {};
|
let udp_rule = {};
|
||||||
@@ -1489,11 +1520,11 @@ function push_section_route_rule(config, rule, target_outbound) {
|
|||||||
delete udp_rule.outbound;
|
delete udp_rule.outbound;
|
||||||
udp_rule.action = "reject";
|
udp_rule.action = "reject";
|
||||||
udp_rule.network = "udp";
|
udp_rule.network = "udp";
|
||||||
push(config.route.rules, udp_rule);
|
push(config.route.rules, apply_excluded_source_ips(udp_rule, excluded_cidrs));
|
||||||
|
|
||||||
rule.network = "tcp";
|
rule.network = "tcp";
|
||||||
}
|
}
|
||||||
push(config.route.rules, rule);
|
push(config.route.rules, apply_excluded_source_ips(rule, excluded_cidrs));
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_fully_routed_ips_rule(config, section) {
|
function add_fully_routed_ips_rule(config, section) {
|
||||||
@@ -1604,11 +1635,11 @@ function add_combined_route_for_section(config, section) {
|
|||||||
let domain_regex = domains.domain_regex;
|
let domain_regex = domains.domain_regex;
|
||||||
let ip_cidr = legacy_condition_values(section, "ip_cidr");
|
let ip_cidr = legacy_condition_values(section, "ip_cidr");
|
||||||
let source_ip_cidr = core_ip.normalize_to_cidrs(legacy_condition_values(section, "source_ip_cidr"));
|
let source_ip_cidr = core_ip.normalize_to_cidrs(legacy_condition_values(section, "source_ip_cidr"));
|
||||||
|
let excluded_cidrs = core_ip.normalize_to_cidrs(list_option(section, "excluded_ips"));
|
||||||
let rule_set_tags = [];
|
let rule_set_tags = [];
|
||||||
let dns_rule_set_tags = [];
|
let dns_rule_set_tags = [];
|
||||||
let section_name = section[".name"];
|
let section_name = section[".name"];
|
||||||
|
|
||||||
add_excluded_ips_rule(config, section);
|
|
||||||
add_excluded_protocol_rule(config, section);
|
add_excluded_protocol_rule(config, section);
|
||||||
add_fully_routed_ips_rule(config, section);
|
add_fully_routed_ips_rule(config, section);
|
||||||
|
|
||||||
@@ -1689,9 +1720,9 @@ function add_combined_route_for_section(config, section) {
|
|||||||
if (type(resolve) == "object" && resolve.warning)
|
if (type(resolve) == "object" && resolve.warning)
|
||||||
warn(resolve.warning, "\n");
|
warn(resolve.warning, "\n");
|
||||||
else if (type(resolve) == "object" && resolve.rule)
|
else if (type(resolve) == "object" && resolve.rule)
|
||||||
push(config.route.rules, resolve.rule);
|
push(config.route.rules, apply_excluded_source_ips(resolve.rule, excluded_cidrs));
|
||||||
|
|
||||||
push_section_route_rule(config, domain_rule, target.outbound);
|
push_section_route_rule(config, domain_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_ruleset) {
|
if (has_ruleset) {
|
||||||
@@ -1702,15 +1733,15 @@ function add_combined_route_for_section(config, section) {
|
|||||||
if (type(resolve) == "object" && resolve.warning)
|
if (type(resolve) == "object" && resolve.warning)
|
||||||
warn(resolve.warning, "\n");
|
warn(resolve.warning, "\n");
|
||||||
else if (type(resolve) == "object" && resolve.rule)
|
else if (type(resolve) == "object" && resolve.rule)
|
||||||
push(config.route.rules, resolve.rule);
|
push(config.route.rules, apply_excluded_source_ips(resolve.rule, excluded_cidrs));
|
||||||
|
|
||||||
push_section_route_rule(config, rs_rule, target.outbound);
|
push_section_route_rule(config, rs_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_ip_cidr) {
|
if (has_ip_cidr) {
|
||||||
let ip_rule = create_section_route_rule();
|
let ip_rule = create_section_route_rule();
|
||||||
ip_rule.ip_cidr = ip_cidr;
|
ip_rule.ip_cidr = ip_cidr;
|
||||||
push_section_route_rule(config, ip_rule, target.outbound);
|
push_section_route_rule(config, ip_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length(discord_cf_subnets) > 0) {
|
if (length(discord_cf_subnets) > 0) {
|
||||||
@@ -1718,7 +1749,7 @@ function add_combined_route_for_section(config, section) {
|
|||||||
voice_rule.network = "udp";
|
voice_rule.network = "udp";
|
||||||
voice_rule.ip_cidr = discord_cf_subnets;
|
voice_rule.ip_cidr = discord_cf_subnets;
|
||||||
voice_rule.port_range = core_ip.DISCORD_VOICE_PORT_RANGES || [ "5000:5020", "3478:3478", "19294:19344", "50000:65535" ];
|
voice_rule.port_range = core_ip.DISCORD_VOICE_PORT_RANGES || [ "5000:5020", "3478:3478", "19294:19344", "50000:65535" ];
|
||||||
push_section_route_rule(config, voice_rule, target.outbound);
|
push_section_route_rule(config, voice_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let country_list = connections.geoip_country_list(section);
|
let country_list = connections.geoip_country_list(section);
|
||||||
@@ -1744,7 +1775,7 @@ function add_combined_route_for_section(config, section) {
|
|||||||
if (country_mode == "exclude")
|
if (country_mode == "exclude")
|
||||||
geoip_route_rule.invert = true;
|
geoip_route_rule.invert = true;
|
||||||
|
|
||||||
push_section_route_rule(config, geoip_route_rule, target.outbound);
|
push_section_route_rule(config, geoip_route_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1754,7 +1785,7 @@ function add_combined_route_for_section(config, section) {
|
|||||||
fallback_rule.port != null || fallback_rule.port_range != null ||
|
fallback_rule.port != null || fallback_rule.port_range != null ||
|
||||||
fallback_rule.protocol != null || fallback_rule.dscp != null;
|
fallback_rule.protocol != null || fallback_rule.dscp != null;
|
||||||
if (has_any_matcher)
|
if (has_any_matcher)
|
||||||
push_section_route_rule(config, fallback_rule, target.outbound);
|
push_section_route_rule(config, fallback_rule, target.outbound, excluded_cidrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rewrite_ttl = int_option(ctx.runtime_settings(), "dns_rewrite_ttl", "60");
|
let rewrite_ttl = int_option(ctx.runtime_settings(), "dns_rewrite_ttl", "60");
|
||||||
@@ -1769,7 +1800,7 @@ function add_combined_route_for_section(config, section) {
|
|||||||
add_domain_array(dns_rule, "domain_keyword", domain_keyword);
|
add_domain_array(dns_rule, "domain_keyword", domain_keyword);
|
||||||
add_domain_array(dns_rule, "domain_regex", domain_regex);
|
add_domain_array(dns_rule, "domain_regex", domain_regex);
|
||||||
add_source_dns_matchers(dns_rule, source_ip_cidr);
|
add_source_dns_matchers(dns_rule, source_ip_cidr);
|
||||||
push_dns_matcher_rule(config, dns_rule);
|
push_dns_matcher_rule(config, apply_excluded_source_ips(dns_rule, excluded_cidrs));
|
||||||
}
|
}
|
||||||
if (length(dns_rule_set_tags) > 0) {
|
if (length(dns_rule_set_tags) > 0) {
|
||||||
let dns_rule = {
|
let dns_rule = {
|
||||||
@@ -1779,7 +1810,7 @@ function add_combined_route_for_section(config, section) {
|
|||||||
rule_set: single_or_array(dns_rule_set_tags)
|
rule_set: single_or_array(dns_rule_set_tags)
|
||||||
};
|
};
|
||||||
add_source_dns_matchers(dns_rule, source_ip_cidr);
|
add_source_dns_matchers(dns_rule, source_ip_cidr);
|
||||||
push_dns_matcher_rule(config, dns_rule);
|
push_dns_matcher_rule(config, apply_excluded_source_ips(dns_rule, excluded_cidrs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||||
|
GENERATOR_UC="$TACHYON_LIB/singbox/generator.uc"
|
||||||
|
WORK_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$WORK_DIR/fixture.json" <<'JSON'
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
".name": "settings",
|
||||||
|
".type": "settings",
|
||||||
|
"enabled": "1",
|
||||||
|
"dns_type": "udp",
|
||||||
|
"dns_server": "1.1.1.1",
|
||||||
|
"service_listen_address": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"section": [
|
||||||
|
{
|
||||||
|
".name": "sec_yt",
|
||||||
|
".type": "section",
|
||||||
|
"enabled": "1",
|
||||||
|
"action": "connection",
|
||||||
|
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_yt-out\"}" ],
|
||||||
|
"domain_suffix": [ "youtube.com" ],
|
||||||
|
"excluded_ips": [ "192.168.1.50" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
".name": "sec_gg",
|
||||||
|
".type": "section",
|
||||||
|
"enabled": "1",
|
||||||
|
"action": "connection",
|
||||||
|
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_gg-out\"}" ],
|
||||||
|
"domain_suffix": [ "google.com" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
|
||||||
|
output="$WORK_DIR/out.json"
|
||||||
|
mkdir -p "$output.section-cache" "$output.rulesets"
|
||||||
|
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
|
||||||
|
"$WORK_DIR/fixture.json" "$output" "127.0.0.1" "0" "1"
|
||||||
|
|
||||||
|
# Verify route rules in out.json
|
||||||
|
ucode -e '
|
||||||
|
let fs = require("fs");
|
||||||
|
let raw = fs.readfile("'"$output"'");
|
||||||
|
let cfg = json(raw);
|
||||||
|
let rules = cfg.route.rules || [];
|
||||||
|
|
||||||
|
let found_global_direct = false;
|
||||||
|
let found_scoped_logical = false;
|
||||||
|
let found_sec_gg = false;
|
||||||
|
|
||||||
|
for (let r in rules) {
|
||||||
|
// A global direct rule would have outbound direct-out, source_ip_cidr set, and no domain conditions
|
||||||
|
if (r.outbound == "direct-out" && r.source_ip_cidr && !r.domain_suffix && !r.domain && !r.rule_set) {
|
||||||
|
found_global_direct = true;
|
||||||
|
}
|
||||||
|
// Scoped logical rule for sec_yt-out
|
||||||
|
if (r.type == "logical" && r.outbound == "sec_yt-out" && r.mode == "and" && type(r.rules) == "array") {
|
||||||
|
let has_domain = false;
|
||||||
|
let has_inverted_src = false;
|
||||||
|
for (let sub in r.rules) {
|
||||||
|
if (sub.domain_suffix && index(sub.domain_suffix, "youtube.com") >= 0)
|
||||||
|
has_domain = true;
|
||||||
|
if (sub.invert == true && (sub.source_ip_cidr == "192.168.1.50/32" || index(sub.source_ip_cidr, "192.168.1.50/32") >= 0))
|
||||||
|
has_inverted_src = true;
|
||||||
|
}
|
||||||
|
if (has_domain && has_inverted_src)
|
||||||
|
found_scoped_logical = true;
|
||||||
|
}
|
||||||
|
if (r.outbound == "sec_gg-out") {
|
||||||
|
found_sec_gg = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_global_direct)
|
||||||
|
die("Found unconditional global direct-out rule for excluded IP, breaking subsequent sections!\n");
|
||||||
|
|
||||||
|
if (!found_scoped_logical)
|
||||||
|
die("Expected scoped logical rule for sec_yt-out with inverted source_ip_cidr not found!\n");
|
||||||
|
|
||||||
|
if (!found_sec_gg)
|
||||||
|
die("sec_gg-out rule not found in generated sing-box rules!\n");
|
||||||
|
' || fail "section_excluded_ips failed verification"
|
||||||
|
|
||||||
|
printf 'PASS: section_excluded_ips\n'
|
||||||
Reference in New Issue
Block a user