fix: resolve -1ms latency display, diagnostic outbounds check timeout, country.is fallback, and docker AGH false positive
- Fix -1ms badge and green coloring in dashboard sections when node latency check fails or returns -1 - Handle Clash API group delay responses properly in dashboard single and batch latency tests - Target active outbound node in diagnostics outbounds check instead of group tag to prevent router timeout - Add nslookup fallback in country.uc resolve_service_host and retain system DNS resolution - Ensure flag emoji country deduction acts as fallback in URLTest groups when exclude_countries is specified - Ignore processes in container network namespaces when diagnosing system port 53 / DNS conflicts for AdGuardHome - Bump fe-app-tachyon version to 1.3.25 and rebuild LuCI bundle
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.24",
|
||||
"version": "1.3.25",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1282,7 +1282,11 @@ function buildProxyGroupOutbounds(
|
||||
)?.latency ||
|
||||
0;
|
||||
|
||||
const latency = item?.value.history?.[0]?.delay || activeMemberLatency || 0;
|
||||
const itemDelay = Number(item?.value.history?.[0]?.delay);
|
||||
const validItemDelay =
|
||||
Number.isFinite(itemDelay) && itemDelay > 0 ? itemDelay : 0;
|
||||
const latency =
|
||||
validItemDelay || (activeMemberLatency > 0 ? activeMemberLatency : 0);
|
||||
|
||||
const isGroupType = Boolean(
|
||||
priorityConfig || urlTestConfig || isRuntimeUrlTest,
|
||||
|
||||
@@ -763,7 +763,18 @@ async function handleTestLatency(
|
||||
timeout,
|
||||
);
|
||||
if (response.success && response.data) {
|
||||
customProxyLatencies.set(tag, response.data.delay || -1);
|
||||
const delay = (response.data as any)?.delay;
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
customProxyLatencies.set(tag, delay);
|
||||
} else {
|
||||
const groupDelays = Object.values(response.data || {}).filter(
|
||||
(v): v is number => typeof v === 'number' && v > 0,
|
||||
);
|
||||
customProxyLatencies.set(
|
||||
tag,
|
||||
groupDelays.length > 0 ? Math.min(...groupDelays) : -1,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
customProxyLatencies.set(tag, -1);
|
||||
}
|
||||
@@ -825,7 +836,18 @@ async function handleTestSingleOutbound(
|
||||
'2000',
|
||||
);
|
||||
if (response.success && response.data) {
|
||||
customProxyLatencies.set(outboundCode, response.data.delay || -1);
|
||||
const delay = (response.data as any)?.delay;
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
customProxyLatencies.set(outboundCode, delay);
|
||||
} else {
|
||||
const groupDelays = Object.values(response.data || {}).filter(
|
||||
(v): v is number => typeof v === 'number' && v > 0,
|
||||
);
|
||||
customProxyLatencies.set(
|
||||
outboundCode,
|
||||
groupDelays.length > 0 ? Math.min(...groupDelays) : -1,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
customProxyLatencies.set(outboundCode, -1);
|
||||
}
|
||||
@@ -1753,14 +1775,44 @@ async function renderSectionsWidget() {
|
||||
|
||||
const sectionsWithCustomLatencies = sectionsWidget.data.map((section) => ({
|
||||
...section,
|
||||
outbounds: section.outbounds.map((outbound) => ({
|
||||
...outbound,
|
||||
latency:
|
||||
SERVICE_TYPES.has(outbound.type) ||
|
||||
!customProxyLatencies.has(outbound.code)
|
||||
outbounds: section.outbounds.map((outbound) => {
|
||||
const customLatency = customProxyLatencies.get(outbound.code);
|
||||
const latency =
|
||||
SERVICE_TYPES.has(outbound.type) || customLatency == null
|
||||
? outbound.latency
|
||||
: customProxyLatencies.get(outbound.code)!,
|
||||
})),
|
||||
: customLatency;
|
||||
|
||||
const urlTestInfo = outbound.urlTestInfo
|
||||
? {
|
||||
...outbound.urlTestInfo,
|
||||
outbounds: outbound.urlTestInfo.outbounds.map((member) => ({
|
||||
...member,
|
||||
latency: customProxyLatencies.has(member.code)
|
||||
? customProxyLatencies.get(member.code)!
|
||||
: member.latency,
|
||||
})),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const priorityInfo = outbound.priorityInfo
|
||||
? {
|
||||
...outbound.priorityInfo,
|
||||
outbounds: outbound.priorityInfo.outbounds.map((member) => ({
|
||||
...member,
|
||||
latency: customProxyLatencies.has(member.code)
|
||||
? customProxyLatencies.get(member.code)!
|
||||
: member.latency,
|
||||
})),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...outbound,
|
||||
latency,
|
||||
urlTestInfo,
|
||||
priorityInfo,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
if (sectionsWidget.loading || sectionsWidget.failed) {
|
||||
|
||||
@@ -467,6 +467,10 @@ function renderDefaultState({
|
||||
return 'tachyon_dashboard-page__outbound-grid__item__latency--empty';
|
||||
}
|
||||
|
||||
if (outbound.latency === -1 || outbound.latency < 0) {
|
||||
return 'tachyon_dashboard-page__outbound-grid__item__latency--red';
|
||||
}
|
||||
|
||||
if (outbound.latency < 800) {
|
||||
return 'tachyon_dashboard-page__outbound-grid__item__latency--green';
|
||||
}
|
||||
@@ -763,9 +767,11 @@ function renderDefaultState({
|
||||
? [renderLoaderCircleIcon24(), E('span', {}, _('Checking...'))]
|
||||
: isConnectionNode
|
||||
? connectionStatusText
|
||||
: outbound.latency
|
||||
: outbound.latency && outbound.latency > 0
|
||||
? `${outbound.latency}ms`
|
||||
: 'N/A',
|
||||
: outbound.latency === -1 || outbound.latency < 0
|
||||
? _('Not responding')
|
||||
: 'N/A',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -886,6 +892,11 @@ function renderDefaultState({
|
||||
|
||||
if (!selectedOutbound!.latency)
|
||||
return 'var(--primary-color-low, lightgray)';
|
||||
if (
|
||||
selectedOutbound!.latency === -1 ||
|
||||
selectedOutbound!.latency < 0
|
||||
)
|
||||
return 'var(--error-color-medium, red)';
|
||||
if (selectedOutbound!.latency < 800)
|
||||
return 'var(--success-color-medium, green)';
|
||||
if (selectedOutbound!.latency < 1500)
|
||||
@@ -899,15 +910,20 @@ function renderDefaultState({
|
||||
? _('Checking...')
|
||||
: selectedOutbound.latency && selectedOutbound.latency > 0
|
||||
? `${selectedOutbound.latency}ms`
|
||||
: selectedOutbound.latency === -1
|
||||
: selectedOutbound.latency === -1 ||
|
||||
selectedOutbound.latency < 0
|
||||
? _('Not responding')
|
||||
: selectedOutbound.runtimeAvailable
|
||||
? _('Connected')
|
||||
: _('Not connected');
|
||||
} else {
|
||||
latencyText = selectedOutbound.latency
|
||||
? `${selectedOutbound.latency}ms`
|
||||
: '';
|
||||
latencyText =
|
||||
selectedOutbound.latency && selectedOutbound.latency > 0
|
||||
? `${selectedOutbound.latency}ms`
|
||||
: selectedOutbound.latency === -1 ||
|
||||
selectedOutbound.latency < 0
|
||||
? _('Not responding')
|
||||
: '';
|
||||
}
|
||||
|
||||
return E(
|
||||
|
||||
@@ -435,4 +435,57 @@ describe('renderSections', () => {
|
||||
expect(testingBadge).not.toBeNull();
|
||||
expect(testingBadge?.textContent).toContain('Checking...');
|
||||
});
|
||||
|
||||
it('renders -1 latency as red Not responding instead of green -1ms', () => {
|
||||
const section: any = {
|
||||
code: 'proxy',
|
||||
sectionName: 'proxy',
|
||||
displayName: 'Proxy Section',
|
||||
action: 'proxy',
|
||||
withTagSelect: true,
|
||||
outbounds: [
|
||||
{
|
||||
code: 'vless-failed',
|
||||
displayName: 'Failed Node',
|
||||
latency: -1,
|
||||
type: 'VLESS',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
code: 'vless-ok',
|
||||
displayName: 'OK Node',
|
||||
latency: 150,
|
||||
type: 'VLESS',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const el = renderSections({
|
||||
loading: false,
|
||||
failed: false,
|
||||
section,
|
||||
onTestLatency: vi.fn(),
|
||||
onChooseOutbound: vi.fn(),
|
||||
onCopyOutbound: vi.fn(),
|
||||
onShowUrlTestInfo: vi.fn(),
|
||||
onShowPriorityInfo: vi.fn(),
|
||||
onUpdateSubscription: vi.fn(),
|
||||
latencyFetching: false,
|
||||
subscriptionUpdating: false,
|
||||
});
|
||||
|
||||
const redBadge = el.querySelector(
|
||||
'.tachyon_dashboard-page__outbound-grid__item__latency--red',
|
||||
);
|
||||
expect(redBadge).not.toBeNull();
|
||||
expect(redBadge?.textContent).toBe('Not responding');
|
||||
expect(redBadge?.textContent).not.toContain('-1');
|
||||
|
||||
const greenBadge = el.querySelector(
|
||||
'.tachyon_dashboard-page__outbound-grid__item__latency--green',
|
||||
);
|
||||
expect(greenBadge).not.toBeNull();
|
||||
expect(greenBadge?.textContent).toBe('150ms');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,10 +70,15 @@ export async function runSectionsCheck() {
|
||||
|
||||
const isSubscription = section.proxyConfigType === 'subscription';
|
||||
|
||||
if (selectedOutbound?.code) {
|
||||
const activeNodeCode =
|
||||
selectedOutbound?.urlTestInfo?.selectedCode ||
|
||||
selectedOutbound?.priorityInfo?.selectedCode ||
|
||||
selectedOutbound?.code;
|
||||
|
||||
if (activeNodeCode) {
|
||||
const latencyProxy =
|
||||
await TachyonShellMethods.getClashApiProxyLatency(
|
||||
selectedOutbound.code,
|
||||
activeNodeCode,
|
||||
section.latencyTestTimeout,
|
||||
);
|
||||
const proxySuccess =
|
||||
@@ -81,10 +86,10 @@ export async function runSectionsCheck() {
|
||||
|
||||
if (proxySuccess) {
|
||||
const delay = latencyProxy.data?.delay;
|
||||
if (typeof delay === 'number') {
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${delay}ms`,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${delay}ms`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,14 +100,21 @@ export async function runSectionsCheck() {
|
||||
const minDelay = Math.min(...groupDelays);
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${minDelay}ms`,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${minDelay}ms`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedOutbound?.latency && selectedOutbound.latency > 0) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${selectedOutbound.latency}ms`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'error',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${_('Not responding')}`,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${_('Not responding')}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,6 +224,13 @@ export async function runSectionsCheck() {
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedOutbound?.latency && selectedOutbound.latency > 0) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `${selectedOutbound.latency} ms`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'error',
|
||||
latency: _('Not responding'),
|
||||
|
||||
@@ -284,4 +284,97 @@ describe('runSectionsCheck', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('targets active child node code when outbound has urlTestInfo.selectedCode', async () => {
|
||||
mocks.getDashboardSections.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
withTagSelect: true,
|
||||
code: 'ai-out',
|
||||
sectionName: 'ai',
|
||||
displayName: 'AI',
|
||||
outbounds: [
|
||||
{
|
||||
code: 'ai-urltest',
|
||||
displayName: 'Fastest',
|
||||
latency: 158,
|
||||
type: 'URLTest',
|
||||
selected: true,
|
||||
urlTestInfo: {
|
||||
selectedCode: 'ai-node-fastest',
|
||||
selectedName: 'Fast Node',
|
||||
outbounds: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getClashApiProxyLatency.mockResolvedValue({
|
||||
success: true,
|
||||
data: { delay: 158 },
|
||||
});
|
||||
|
||||
await expect(runSectionsCheck()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.getClashApiProxyLatency).toHaveBeenCalledWith(
|
||||
'ai-node-fastest',
|
||||
undefined,
|
||||
);
|
||||
expect(mocks.updateCheckStore).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
state: 'success',
|
||||
items: [
|
||||
{
|
||||
state: 'success',
|
||||
key: 'AI',
|
||||
value: '[Fastest] 158ms',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to verified outbound latency when live probe fails', async () => {
|
||||
mocks.getDashboardSections.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
withTagSelect: true,
|
||||
code: 'ai-out',
|
||||
sectionName: 'ai',
|
||||
displayName: 'AI',
|
||||
outbounds: [
|
||||
{
|
||||
code: 'ai-urltest',
|
||||
displayName: 'Fastest',
|
||||
latency: 158,
|
||||
type: 'URLTest',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getClashApiProxyLatency.mockResolvedValue({
|
||||
success: false,
|
||||
error: 'timeout',
|
||||
});
|
||||
|
||||
await expect(runSectionsCheck()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.updateCheckStore).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
state: 'success',
|
||||
items: [
|
||||
{
|
||||
state: 'success',
|
||||
key: 'AI',
|
||||
value: '[Fastest] 158ms',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2597,6 +2597,9 @@ function renderDefaultState({
|
||||
if (!outbound.latency) {
|
||||
return "tachyon_dashboard-page__outbound-grid__item__latency--empty";
|
||||
}
|
||||
if (outbound.latency === -1 || outbound.latency < 0) {
|
||||
return "tachyon_dashboard-page__outbound-grid__item__latency--red";
|
||||
}
|
||||
if (outbound.latency < 800) {
|
||||
return "tachyon_dashboard-page__outbound-grid__item__latency--green";
|
||||
}
|
||||
@@ -2815,7 +2818,7 @@ function renderDefaultState({
|
||||
);
|
||||
}
|
||||
},
|
||||
isTestingSingle ? [renderLoaderCircleIcon24(), E("span", {}, _("Checking..."))] : isConnectionNode ? connectionStatusText : outbound.latency ? `${outbound.latency}ms` : "N/A"
|
||||
isTestingSingle ? [renderLoaderCircleIcon24(), E("span", {}, _("Checking..."))] : isConnectionNode ? connectionStatusText : outbound.latency && outbound.latency > 0 ? `${outbound.latency}ms` : outbound.latency === -1 || outbound.latency < 0 ? _("Not responding") : "N/A"
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -2916,6 +2919,8 @@ function renderDefaultState({
|
||||
}
|
||||
if (!selectedOutbound.latency)
|
||||
return "var(--primary-color-low, lightgray)";
|
||||
if (selectedOutbound.latency === -1 || selectedOutbound.latency < 0)
|
||||
return "var(--error-color-medium, red)";
|
||||
if (selectedOutbound.latency < 800)
|
||||
return "var(--success-color-medium, green)";
|
||||
if (selectedOutbound.latency < 1500)
|
||||
@@ -2924,9 +2929,9 @@ function renderDefaultState({
|
||||
}
|
||||
let latencyText = "";
|
||||
if (isConnectionNode2) {
|
||||
latencyText = latencyFetching ? _("Checking...") : selectedOutbound.latency && selectedOutbound.latency > 0 ? `${selectedOutbound.latency}ms` : selectedOutbound.latency === -1 ? _("Not responding") : selectedOutbound.runtimeAvailable ? _("Connected") : _("Not connected");
|
||||
latencyText = latencyFetching ? _("Checking...") : selectedOutbound.latency && selectedOutbound.latency > 0 ? `${selectedOutbound.latency}ms` : selectedOutbound.latency === -1 || selectedOutbound.latency < 0 ? _("Not responding") : selectedOutbound.runtimeAvailable ? _("Connected") : _("Not connected");
|
||||
} else {
|
||||
latencyText = selectedOutbound.latency ? `${selectedOutbound.latency}ms` : "";
|
||||
latencyText = selectedOutbound.latency && selectedOutbound.latency > 0 ? `${selectedOutbound.latency}ms` : selectedOutbound.latency === -1 || selectedOutbound.latency < 0 ? _("Not responding") : "";
|
||||
}
|
||||
return E(
|
||||
"span",
|
||||
@@ -5670,7 +5675,9 @@ function buildProxyGroupOutbounds(section, proxies, outboundMetadata, urltestGro
|
||||
)?.latency || priorityInfo?.outbounds.find(
|
||||
(m) => m.selected || m.code === priorityInfo.selectedCode
|
||||
)?.latency || 0;
|
||||
const latency = item?.value.history?.[0]?.delay || activeMemberLatency || 0;
|
||||
const itemDelay = Number(item?.value.history?.[0]?.delay);
|
||||
const validItemDelay = Number.isFinite(itemDelay) && itemDelay > 0 ? itemDelay : 0;
|
||||
const latency = validItemDelay || (activeMemberLatency > 0 ? activeMemberLatency : 0);
|
||||
const isGroupType = Boolean(
|
||||
priorityConfig || urlTestConfig || isRuntimeUrlTest
|
||||
);
|
||||
@@ -8302,7 +8309,18 @@ async function handleTestLatency(latencyType, sectionName, tag, timeout) {
|
||||
timeout
|
||||
);
|
||||
if (response.success && response.data) {
|
||||
customProxyLatencies.set(tag, response.data.delay || -1);
|
||||
const delay = response.data?.delay;
|
||||
if (typeof delay === "number" && delay > 0) {
|
||||
customProxyLatencies.set(tag, delay);
|
||||
} else {
|
||||
const groupDelays = Object.values(response.data || {}).filter(
|
||||
(v) => typeof v === "number" && v > 0
|
||||
);
|
||||
customProxyLatencies.set(
|
||||
tag,
|
||||
groupDelays.length > 0 ? Math.min(...groupDelays) : -1
|
||||
);
|
||||
}
|
||||
} else {
|
||||
customProxyLatencies.set(tag, -1);
|
||||
}
|
||||
@@ -8355,7 +8373,18 @@ async function handleTestSingleOutbound(_sectionName, outboundCode) {
|
||||
"2000"
|
||||
);
|
||||
if (response.success && response.data) {
|
||||
customProxyLatencies.set(outboundCode, response.data.delay || -1);
|
||||
const delay = response.data?.delay;
|
||||
if (typeof delay === "number" && delay > 0) {
|
||||
customProxyLatencies.set(outboundCode, delay);
|
||||
} else {
|
||||
const groupDelays = Object.values(response.data || {}).filter(
|
||||
(v) => typeof v === "number" && v > 0
|
||||
);
|
||||
customProxyLatencies.set(
|
||||
outboundCode,
|
||||
groupDelays.length > 0 ? Math.min(...groupDelays) : -1
|
||||
);
|
||||
}
|
||||
} else {
|
||||
customProxyLatencies.set(outboundCode, -1);
|
||||
}
|
||||
@@ -9079,10 +9108,30 @@ async function renderSectionsWidget() {
|
||||
const SERVICE_TYPES = /* @__PURE__ */ new Set(["SING_BOX", "ZAPRET", "ZAPRET2", "BYEDPI"]);
|
||||
const sectionsWithCustomLatencies = sectionsWidget.data.map((section) => ({
|
||||
...section,
|
||||
outbounds: section.outbounds.map((outbound) => ({
|
||||
...outbound,
|
||||
latency: SERVICE_TYPES.has(outbound.type) || !customProxyLatencies.has(outbound.code) ? outbound.latency : customProxyLatencies.get(outbound.code)
|
||||
}))
|
||||
outbounds: section.outbounds.map((outbound) => {
|
||||
const customLatency = customProxyLatencies.get(outbound.code);
|
||||
const latency = SERVICE_TYPES.has(outbound.type) || customLatency == null ? outbound.latency : customLatency;
|
||||
const urlTestInfo = outbound.urlTestInfo ? {
|
||||
...outbound.urlTestInfo,
|
||||
outbounds: outbound.urlTestInfo.outbounds.map((member) => ({
|
||||
...member,
|
||||
latency: customProxyLatencies.has(member.code) ? customProxyLatencies.get(member.code) : member.latency
|
||||
}))
|
||||
} : void 0;
|
||||
const priorityInfo = outbound.priorityInfo ? {
|
||||
...outbound.priorityInfo,
|
||||
outbounds: outbound.priorityInfo.outbounds.map((member) => ({
|
||||
...member,
|
||||
latency: customProxyLatencies.has(member.code) ? customProxyLatencies.get(member.code) : member.latency
|
||||
}))
|
||||
} : void 0;
|
||||
return {
|
||||
...outbound,
|
||||
latency,
|
||||
urlTestInfo,
|
||||
priorityInfo
|
||||
};
|
||||
})
|
||||
}));
|
||||
if (sectionsWidget.loading || sectionsWidget.failed) {
|
||||
const renderedWidget = renderSections({
|
||||
@@ -15646,18 +15695,19 @@ async function runSectionsCheck() {
|
||||
(item) => item.type?.toLowerCase() === "urltest"
|
||||
) ?? section.outbounds[0];
|
||||
const isSubscription = section.proxyConfigType === "subscription";
|
||||
if (selectedOutbound2?.code) {
|
||||
const activeNodeCode = selectedOutbound2?.urlTestInfo?.selectedCode || selectedOutbound2?.priorityInfo?.selectedCode || selectedOutbound2?.code;
|
||||
if (activeNodeCode) {
|
||||
const latencyProxy2 = await TachyonShellMethods.getClashApiProxyLatency(
|
||||
selectedOutbound2.code,
|
||||
activeNodeCode,
|
||||
section.latencyTestTimeout
|
||||
);
|
||||
const proxySuccess = latencyProxy2.success && !latencyProxy2.data?.message;
|
||||
if (proxySuccess) {
|
||||
const delay = latencyProxy2.data?.delay;
|
||||
if (typeof delay === "number") {
|
||||
if (typeof delay === "number" && delay > 0) {
|
||||
return {
|
||||
state: "success",
|
||||
latency: `[${selectedOutbound2.displayName ?? ""}] ${delay}ms`
|
||||
latency: `[${selectedOutbound2?.displayName ?? ""}] ${delay}ms`
|
||||
};
|
||||
}
|
||||
const groupDelays = Object.values(latencyProxy2.data || {}).filter(
|
||||
@@ -15667,13 +15717,19 @@ async function runSectionsCheck() {
|
||||
const minDelay = Math.min(...groupDelays);
|
||||
return {
|
||||
state: "success",
|
||||
latency: `[${selectedOutbound2.displayName ?? ""}] ${minDelay}ms`
|
||||
latency: `[${selectedOutbound2?.displayName ?? ""}] ${minDelay}ms`
|
||||
};
|
||||
}
|
||||
}
|
||||
if (selectedOutbound2?.latency && selectedOutbound2.latency > 0) {
|
||||
return {
|
||||
state: "success",
|
||||
latency: `[${selectedOutbound2.displayName ?? ""}] ${selectedOutbound2.latency}ms`
|
||||
};
|
||||
}
|
||||
return {
|
||||
state: "error",
|
||||
latency: `[${selectedOutbound2.displayName ?? ""}] ${_("Not responding")}`
|
||||
latency: `[${selectedOutbound2?.displayName ?? ""}] ${_("Not responding")}`
|
||||
};
|
||||
}
|
||||
const latencyGroup = await TachyonShellMethods.getClashApiGroupLatency(
|
||||
@@ -15762,6 +15818,12 @@ async function runSectionsCheck() {
|
||||
latency: `[${selectedOutbound.displayName || section.code}] ${_("Connectivity probe failed")}`
|
||||
};
|
||||
}
|
||||
if (selectedOutbound?.latency && selectedOutbound.latency > 0) {
|
||||
return {
|
||||
state: "success",
|
||||
latency: `${selectedOutbound.latency} ms`
|
||||
};
|
||||
}
|
||||
return {
|
||||
state: "error",
|
||||
latency: _("Not responding")
|
||||
|
||||
@@ -2142,11 +2142,25 @@ function find_process_pid(name) {
|
||||
return length(pids) > 0 && pids[0] != "" ? pids[0] : "";
|
||||
}
|
||||
|
||||
function is_container_process(pid) {
|
||||
pid = as_string(pid);
|
||||
if (pid == "")
|
||||
return false;
|
||||
let host_net = fs.readlink("/proc/1/ns/net");
|
||||
let proc_net = fs.readlink("/proc/" + pid + "/ns/net");
|
||||
if (host_net != null && proc_net != null && host_net != proc_net)
|
||||
return true;
|
||||
let cgroup = fs.readfile("/proc/" + pid + "/cgroup") || "";
|
||||
if (index(cgroup, "docker") >= 0 || index(cgroup, "containerd") >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function is_adguardhome_primary_dns(cfg) {
|
||||
let agh_pid = find_process_pid("AdGuardHome");
|
||||
if (agh_pid == "")
|
||||
agh_pid = find_process_pid("adguardhome");
|
||||
if (agh_pid == "")
|
||||
if (agh_pid == "" || is_container_process(agh_pid))
|
||||
return false;
|
||||
|
||||
let dnsmasq_port = uci_core.get("dhcp.@dnsmasq[0].port");
|
||||
@@ -4337,6 +4351,8 @@ function diagnose_system_conflicts(cfg, lang, causes, fn_add_fix) {
|
||||
if (pid == "" && svc == "adguardhome")
|
||||
pid = find_process_pid("AdGuardHome");
|
||||
if (pid != "") {
|
||||
if (is_container_process(pid))
|
||||
continue;
|
||||
if ((svc == "adguardhome" || svc == "AdGuardHome") && is_adguardhome_primary_dns(cfg))
|
||||
continue;
|
||||
|
||||
@@ -4355,6 +4371,8 @@ function diagnose_system_conflicts(cfg, lang, causes, fn_add_fix) {
|
||||
for (let pkg in competing_pkgs) {
|
||||
let pid = find_process_pid(pkg);
|
||||
if (pid != "") {
|
||||
if (is_container_process(pid))
|
||||
continue;
|
||||
push(causes, {
|
||||
probability: 88,
|
||||
cause: lang == "en" ? sprintf("Conflicting proxy framework running: %s (PID %s) causes routing/nftables rule clashes", pkg, pid)
|
||||
|
||||
@@ -135,12 +135,25 @@ function normalized_resolver(value) {
|
||||
|
||||
function resolve_service_host(host, resolver) {
|
||||
resolver = normalized_resolver(resolver);
|
||||
if (host == "" || resolver == "")
|
||||
if (host == "")
|
||||
return "";
|
||||
|
||||
return first_ipv4_line(command_output_from_args([
|
||||
"dig", "+short", "@" + resolver, host, "A", "+time=2", "+tries=1"
|
||||
]));
|
||||
let ip = "";
|
||||
if (resolver != "") {
|
||||
ip = first_ipv4_line(command_output_from_args([
|
||||
"dig", "+short", "@" + resolver, host, "A", "+time=2", "+tries=1"
|
||||
]));
|
||||
if (ip == "")
|
||||
ip = first_nslookup_address(command_output_from_args([ "nslookup", host, resolver ]));
|
||||
}
|
||||
if (ip == "") {
|
||||
ip = first_ipv4_line(command_output_from_args([
|
||||
"dig", "+short", host, "A", "+time=2", "+tries=1"
|
||||
]));
|
||||
if (ip == "")
|
||||
ip = first_nslookup_address(command_output_from_args([ "nslookup", host ]));
|
||||
}
|
||||
return public_ipv4(ip) ? ip : "";
|
||||
}
|
||||
|
||||
function resolve_server(value) {
|
||||
@@ -174,11 +187,6 @@ function lookup_ip_batch(ips, resolver) {
|
||||
let endpoint = country_is_endpoint();
|
||||
if (endpoint.host != "" && !valid_ipv4(endpoint.host) && !valid_ipv6(endpoint.host)) {
|
||||
let service_ip = resolve_service_host(endpoint.host, resolver);
|
||||
if (normalized_resolver(resolver) != "" && !public_ipv4(service_ip)) {
|
||||
remove_file(body_path);
|
||||
warn("Server country service host resolution failed\n");
|
||||
return { countries: {}, stop: true };
|
||||
}
|
||||
if (public_ipv4(service_ip)) {
|
||||
push(args, "--resolve");
|
||||
push(args, endpoint.host + ":" + endpoint.port + ":" + service_ip);
|
||||
|
||||
@@ -191,11 +191,19 @@ function section_has_direct_priority_level(section) {
|
||||
}
|
||||
|
||||
function urltest_country_metadata(section, urltest_id, state) {
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let names = object_or_empty(object_or_empty(state.outboundMetadata).names);
|
||||
let from_flags = runtime_urltest.countries_from_flag_names(names);
|
||||
let detect_method = connections.urltest_detect_server_country(section, urltest_id);
|
||||
if (detect_method == "flag_emoji")
|
||||
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
|
||||
return metadata;
|
||||
return from_flags;
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let result = {};
|
||||
for (let tag, c in from_flags)
|
||||
result[tag] = c;
|
||||
for (let tag, c in metadata)
|
||||
if (c != "")
|
||||
result[tag] = c;
|
||||
return result;
|
||||
}
|
||||
|
||||
function array_contains(values, needle) {
|
||||
@@ -409,11 +417,19 @@ function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags,
|
||||
}
|
||||
|
||||
function priority_level_country_metadata(group_id, level_id, state) {
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let names = object_or_empty(object_or_empty(state.outboundMetadata).names);
|
||||
let from_flags = runtime_urltest.countries_from_flag_names(names);
|
||||
let detect_method = connections.priority_level_detect_server_country(group_id, level_id);
|
||||
if (detect_method == "flag_emoji")
|
||||
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
|
||||
return metadata;
|
||||
return from_flags;
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let result = {};
|
||||
for (let tag, c in from_flags)
|
||||
result[tag] = c;
|
||||
for (let tag, c in metadata)
|
||||
if (c != "")
|
||||
result[tag] = c;
|
||||
return result;
|
||||
}
|
||||
|
||||
function priority_level_filtered_outbounds(group_id, level_id, urltest_candidate_tags, state) {
|
||||
@@ -444,10 +460,18 @@ function priority_level_filtered_outbounds(group_id, level_id, urltest_candidate
|
||||
}
|
||||
|
||||
function dashboard_country_metadata(section, state) {
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let names = object_or_empty(object_or_empty(state.outboundMetadata).names);
|
||||
let from_flags = runtime_urltest.countries_from_flag_names(names);
|
||||
if (connections.dashboard_detect_server_country(section) == "flag_emoji")
|
||||
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
|
||||
return metadata;
|
||||
return from_flags;
|
||||
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
|
||||
let result = {};
|
||||
for (let tag, c in from_flags)
|
||||
result[tag] = c;
|
||||
for (let tag, c in metadata)
|
||||
if (c != "")
|
||||
result[tag] = c;
|
||||
return result;
|
||||
}
|
||||
|
||||
function selected_group_outbounds(group_names, group_outbounds) {
|
||||
|
||||
Reference in New Issue
Block a user