fix: dashboard test button layout, outbounds latency fix, and WDTT/OlcRTC component update cards
This commit is contained in:
@@ -7,6 +7,8 @@ export function renderLoaderCircleIcon24() {
|
||||
{
|
||||
xmlns: NS,
|
||||
viewBox: '0 0 24 24',
|
||||
width: '24',
|
||||
height: '24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '2',
|
||||
|
||||
@@ -1815,7 +1815,8 @@ async function renderSectionsWidget() {
|
||||
type: 'button',
|
||||
id: 'dashboard-test-all-sections-button',
|
||||
class: 'btn',
|
||||
style: 'padding: 4px 14px; height: 32px; font-size: 13px;',
|
||||
style:
|
||||
'display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; padding: 4px 14px; height: 32px; font-size: 13px;',
|
||||
disabled: isTestingAllSections ? true : undefined,
|
||||
click: () => {
|
||||
void handleTestAllSections();
|
||||
@@ -1824,11 +1825,7 @@ async function renderSectionsWidget() {
|
||||
isTestingAllSections
|
||||
? [
|
||||
renderLoaderCircleIcon24(),
|
||||
E(
|
||||
'span',
|
||||
{ style: 'margin-left: 6px;' },
|
||||
_('Testing all sections...'),
|
||||
),
|
||||
E('span', {}, _('Testing all sections...')),
|
||||
]
|
||||
: E('span', {}, _('Test all sections')),
|
||||
),
|
||||
|
||||
@@ -189,6 +189,27 @@ export const styles = `
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
#dashboard-test-all-sections-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#dashboard-test-all-sections-button svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
display: block;
|
||||
flex: 0 0 15px;
|
||||
}
|
||||
|
||||
#dashboard-test-all-sections-button[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.tachyon_dashboard-page__outbound-grid {
|
||||
margin-top: 5px;
|
||||
display: grid;
|
||||
|
||||
@@ -77,13 +77,27 @@ export async function runSectionsCheck() {
|
||||
section.latencyTestTimeout,
|
||||
);
|
||||
const proxySuccess =
|
||||
latencyProxy.success && !latencyProxy.data.message;
|
||||
latencyProxy.success && !latencyProxy.data?.message;
|
||||
|
||||
if (proxySuccess) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${latencyProxy.data.delay}ms`,
|
||||
};
|
||||
const delay = latencyProxy.data?.delay;
|
||||
if (typeof delay === 'number') {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${delay}ms`,
|
||||
};
|
||||
}
|
||||
|
||||
const groupDelays = Object.values(latencyProxy.data || {}).filter(
|
||||
(v): v is number => typeof v === 'number' && v > 0,
|
||||
);
|
||||
if (groupDelays.length > 0) {
|
||||
const minDelay = Math.min(...groupDelays);
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `[${selectedOutbound.displayName ?? ''}] ${minDelay}ms`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -95,7 +109,7 @@ export async function runSectionsCheck() {
|
||||
const latencyGroup = await TachyonShellMethods.getClashApiGroupLatency(
|
||||
section.code,
|
||||
);
|
||||
const success = latencyGroup.success && !latencyGroup.data.message;
|
||||
const success = latencyGroup.success && !latencyGroup.data?.message;
|
||||
|
||||
if (success) {
|
||||
const latencyValues = Object.values(latencyGroup.data);
|
||||
@@ -106,7 +120,7 @@ export async function runSectionsCheck() {
|
||||
const selectedProxyDelay =
|
||||
latencyGroup.data?.[selectedOutbound?.code ?? ''];
|
||||
|
||||
if (selectedProxyDelay) {
|
||||
if (typeof selectedProxyDelay === 'number') {
|
||||
return {
|
||||
state: sectionState,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${selectedProxyDelay}ms`,
|
||||
@@ -169,13 +183,26 @@ export async function runSectionsCheck() {
|
||||
section.latencyTestTimeout,
|
||||
);
|
||||
|
||||
const success = latencyProxy.success && !latencyProxy.data.message;
|
||||
const success = latencyProxy.success && !latencyProxy.data?.message;
|
||||
|
||||
if (success) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `${latencyProxy.data.delay} ms`,
|
||||
};
|
||||
const delay = latencyProxy.data?.delay;
|
||||
if (typeof delay === 'number') {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `${delay} ms`,
|
||||
};
|
||||
}
|
||||
|
||||
const groupDelays = Object.values(latencyProxy.data || {}).filter(
|
||||
(v): v is number => typeof v === 'number' && v > 0,
|
||||
);
|
||||
if (groupDelays.length > 0) {
|
||||
return {
|
||||
state: 'success',
|
||||
latency: `${Math.min(...groupDelays)} ms`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (section.action === 'vpn' && selectedOutbound?.runtimeAvailable) {
|
||||
|
||||
@@ -198,4 +198,90 @@ describe('runSectionsCheck', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts minimum delay from group latency map response without delay field', async () => {
|
||||
mocks.getDashboardSections.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
withTagSelect: true,
|
||||
code: 'main-out',
|
||||
sectionName: 'main',
|
||||
displayName: 'Main',
|
||||
outbounds: [
|
||||
{
|
||||
code: 'main-softportal-out',
|
||||
displayName: 'SoftPortal',
|
||||
latency: 0,
|
||||
type: 'URLTest',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getClashApiProxyLatency.mockResolvedValue({
|
||||
success: true,
|
||||
data: { 'node-1': 140, 'node-2': 45 },
|
||||
});
|
||||
|
||||
await expect(runSectionsCheck()).resolves.toBeUndefined();
|
||||
|
||||
expect(mocks.updateCheckStore).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
state: 'success',
|
||||
items: [
|
||||
{
|
||||
state: 'success',
|
||||
key: 'Main',
|
||||
value: '[SoftPortal] 45ms',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns Not responding instead of undefinedms when latency response has no valid delay', async () => {
|
||||
mocks.getDashboardSections.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
withTagSelect: true,
|
||||
code: 'main-out',
|
||||
sectionName: 'main',
|
||||
displayName: 'Main',
|
||||
outbounds: [
|
||||
{
|
||||
code: 'main-softportal-out',
|
||||
displayName: 'SoftPortal',
|
||||
latency: 0,
|
||||
type: 'URLTest',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
mocks.getClashApiProxyLatency.mockResolvedValue({
|
||||
success: true,
|
||||
data: {},
|
||||
});
|
||||
|
||||
await expect(runSectionsCheck()).rejects.toThrow(
|
||||
'Rule outbounds checks failed',
|
||||
);
|
||||
|
||||
expect(mocks.updateCheckStore).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
state: 'error',
|
||||
items: [
|
||||
{
|
||||
state: 'error',
|
||||
key: 'Main',
|
||||
value: '[SoftPortal] Not responding',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -213,6 +213,8 @@ export const initialDiagnosticStore: Pick<
|
||||
zapret: { status: null, latest_version: '', release_url: '' },
|
||||
zapret2: { status: null, latest_version: '', release_url: '' },
|
||||
byedpi: { status: null, latest_version: '', release_url: '' },
|
||||
wdtt: { status: null, latest_version: '', release_url: '' },
|
||||
olcrtc: { status: null, latest_version: '', release_url: '' },
|
||||
tailscale: { status: null, latest_version: '', release_url: '' },
|
||||
direct_bypass: { status: null, latest_version: '', release_url: '' },
|
||||
torrserver_direct: { status: null, latest_version: '', release_url: '' },
|
||||
|
||||
@@ -70,6 +70,10 @@ function getComponentCardTitle(component: Tachyon.ComponentName): string {
|
||||
return 'Zapret2';
|
||||
case 'byedpi':
|
||||
return 'ByeDPI';
|
||||
case 'wdtt':
|
||||
return 'WDTT';
|
||||
case 'olcrtc':
|
||||
return 'OlcRTC';
|
||||
case 'tailscale':
|
||||
return 'Tailscale';
|
||||
default:
|
||||
@@ -92,6 +96,10 @@ function getComponentCurrentVersion(
|
||||
return sys.zapret2_version;
|
||||
case 'byedpi':
|
||||
return sys.byedpi_version;
|
||||
case 'wdtt':
|
||||
return sys.wdtt_version;
|
||||
case 'olcrtc':
|
||||
return sys.olcrtc_version;
|
||||
case 'tailscale':
|
||||
return sys.tailscale_version;
|
||||
default:
|
||||
@@ -396,6 +404,8 @@ function notifyActionProvidersAvailabilityChanged(
|
||||
zapretInstalled: Boolean(systemInfo.zapret_installed),
|
||||
zapret2Installed: Boolean(systemInfo.zapret2_installed),
|
||||
byedpiInstalled: Boolean(systemInfo.byedpi_installed),
|
||||
wdttInstalled: Boolean(systemInfo.wdtt_installed),
|
||||
olcrtcInstalled: Boolean(systemInfo.olcrtc_installed),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -527,6 +537,30 @@ function patchSystemInfoAfterMutation(result: Tachyon.ComponentActionResult) {
|
||||
}
|
||||
}
|
||||
|
||||
if (result.component === 'wdtt') {
|
||||
nextSystemInfo.providerInfoLoaded = true;
|
||||
|
||||
if (result.action === 'remove') {
|
||||
nextSystemInfo.wdtt_installed = 0;
|
||||
nextSystemInfo.wdtt_version = 'not installed';
|
||||
} else {
|
||||
nextSystemInfo.wdtt_installed = 1;
|
||||
nextSystemInfo.wdtt_version = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.component === 'olcrtc') {
|
||||
nextSystemInfo.providerInfoLoaded = true;
|
||||
|
||||
if (result.action === 'remove') {
|
||||
nextSystemInfo.olcrtc_installed = 0;
|
||||
nextSystemInfo.olcrtc_version = 'not installed';
|
||||
} else {
|
||||
nextSystemInfo.olcrtc_installed = 1;
|
||||
nextSystemInfo.olcrtc_version = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.component === 'direct_bypass') {
|
||||
nextSystemInfo.direct_bypass_enabled = result.action === 'enable' ? 1 : 0;
|
||||
}
|
||||
@@ -546,7 +580,9 @@ function patchSystemInfoAfterMutation(result: Tachyon.ComponentActionResult) {
|
||||
if (
|
||||
result.component === 'zapret' ||
|
||||
result.component === 'zapret2' ||
|
||||
result.component === 'byedpi'
|
||||
result.component === 'byedpi' ||
|
||||
result.component === 'wdtt' ||
|
||||
result.component === 'olcrtc'
|
||||
) {
|
||||
notifyActionProvidersAvailabilityChanged(normalizedSystemInfo);
|
||||
}
|
||||
@@ -1043,6 +1079,10 @@ function getComponentInstallKey(
|
||||
return 'zapret2Install';
|
||||
case 'byedpi':
|
||||
return 'byedpiInstall';
|
||||
case 'wdtt':
|
||||
return 'wdttInstall';
|
||||
case 'olcrtc':
|
||||
return 'olcrtcInstall';
|
||||
case 'tailscale':
|
||||
return 'tailscaleInstall';
|
||||
default:
|
||||
@@ -1086,6 +1126,10 @@ function getComponentBackupVersion(component: Tachyon.ComponentName): string {
|
||||
return sys.zapret2_backup_version || '';
|
||||
case 'byedpi':
|
||||
return sys.byedpi_backup_version || '';
|
||||
case 'wdtt':
|
||||
return sys.wdtt_backup_version || '';
|
||||
case 'olcrtc':
|
||||
return sys.olcrtc_backup_version || '';
|
||||
case 'tailscale':
|
||||
return sys.tailscale_backup_version || '';
|
||||
default:
|
||||
@@ -1115,7 +1159,7 @@ function getOptionalComponentActions({
|
||||
removeKey,
|
||||
rollbackKey,
|
||||
}: {
|
||||
component: 'zapret' | 'zapret2' | 'byedpi' | 'tailscale';
|
||||
component: 'zapret' | 'zapret2' | 'byedpi' | 'wdtt' | 'olcrtc' | 'tailscale';
|
||||
installed: boolean;
|
||||
checkKey: UpdatesActionKey;
|
||||
installKey: UpdatesActionKey;
|
||||
@@ -1151,6 +1195,8 @@ const COMPONENT_REPO_URLS: Record<Tachyon.ComponentName, string> = {
|
||||
zapret: 'https://github.com/remittor/zapret-openwrt',
|
||||
zapret2: 'https://github.com/Dushnilin/zapret2-openwrt',
|
||||
byedpi: 'https://github.com/DPITrickster/ByeDPI-OpenWrt',
|
||||
wdtt: 'https://github.com/SpaceNeuroX/qwdtt-openwrt',
|
||||
olcrtc: 'https://github.com/alekvol/openwrt-olcrtc',
|
||||
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
|
||||
direct_bypass: '',
|
||||
torrserver_direct: '',
|
||||
@@ -1164,6 +1210,8 @@ function getComponentCards(): ComponentCard[] {
|
||||
const zapretInstalled = Boolean(systemInfo.zapret_installed);
|
||||
const zapret2Installed = Boolean(systemInfo.zapret2_installed);
|
||||
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
|
||||
const wdttInstalled = Boolean(systemInfo.wdtt_installed);
|
||||
const olcrtcInstalled = Boolean(systemInfo.olcrtc_installed);
|
||||
const tailscaleInstalled = Boolean(systemInfo.tailscale_installed);
|
||||
const singBoxInstalled = !isNotInstalled(systemInfo.sing_box_version);
|
||||
const singBoxStable =
|
||||
@@ -1275,6 +1323,22 @@ function getComponentCards(): ComponentCard[] {
|
||||
removeKey: 'byedpiRemove',
|
||||
rollbackKey: 'byedpiRollback',
|
||||
});
|
||||
const wdttActions = getOptionalComponentActions({
|
||||
component: 'wdtt',
|
||||
installed: wdttInstalled,
|
||||
checkKey: 'wdttCheck',
|
||||
installKey: 'wdttInstall',
|
||||
removeKey: 'wdttRemove',
|
||||
rollbackKey: 'wdttRollback',
|
||||
});
|
||||
const olcrtcActions = getOptionalComponentActions({
|
||||
component: 'olcrtc',
|
||||
installed: olcrtcInstalled,
|
||||
checkKey: 'olcrtcCheck',
|
||||
installKey: 'olcrtcInstall',
|
||||
removeKey: 'olcrtcRemove',
|
||||
rollbackKey: 'olcrtcRollback',
|
||||
});
|
||||
const tailscaleActions = getOptionalComponentActions({
|
||||
component: 'tailscale',
|
||||
installed: tailscaleInstalled,
|
||||
@@ -1443,6 +1507,36 @@ function getComponentCards(): ComponentCard[] {
|
||||
repoUrl: COMPONENT_REPO_URLS.byedpi,
|
||||
actions: byedpiActions,
|
||||
},
|
||||
{
|
||||
component: 'wdtt',
|
||||
column: 1,
|
||||
title: 'WDTT',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
: wdttInstalled
|
||||
? systemInfo.wdtt_version
|
||||
: _('Not installed'),
|
||||
latestVersion: getLatestVersion('wdtt'),
|
||||
releaseUrl: getGitHubReleaseUrl('wdtt'),
|
||||
repoUrl: COMPONENT_REPO_URLS.wdtt,
|
||||
actions: wdttActions,
|
||||
supportsVersions: true,
|
||||
},
|
||||
{
|
||||
component: 'olcrtc',
|
||||
column: 1,
|
||||
title: 'OlcRTC',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
: olcrtcInstalled
|
||||
? systemInfo.olcrtc_version
|
||||
: _('Not installed'),
|
||||
latestVersion: getLatestVersion('olcrtc'),
|
||||
releaseUrl: getGitHubReleaseUrl('olcrtc'),
|
||||
repoUrl: COMPONENT_REPO_URLS.olcrtc,
|
||||
actions: olcrtcActions,
|
||||
supportsVersions: true,
|
||||
},
|
||||
{
|
||||
component: 'tailscale',
|
||||
column: 1,
|
||||
|
||||
@@ -738,6 +738,8 @@ export namespace Tachyon {
|
||||
| 'zapret'
|
||||
| 'zapret2'
|
||||
| 'byedpi'
|
||||
| 'wdtt'
|
||||
| 'olcrtc'
|
||||
| 'tailscale'
|
||||
| 'direct_bypass'
|
||||
| 'torrserver_direct';
|
||||
|
||||
Reference in New Issue
Block a user