From d57dfc4e6bda992b4b2cd4b7251353c776cc215f Mon Sep 17 00:00:00 2001 From: GreyElaina Date: Fri, 19 Sep 2025 23:42:47 +0800 Subject: [PATCH] feat: enhance health check and speed test result handling --- .../Views/ProxyGroupSpeedTestMenuItem.swift | 80 ++++++++++++++++++- ClashX/Views/ProxyItemView.swift | 11 ++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/ClashX/Views/ProxyGroupSpeedTestMenuItem.swift b/ClashX/Views/ProxyGroupSpeedTestMenuItem.swift index 969cf6cc8..a46d3e4d6 100644 --- a/ClashX/Views/ProxyGroupSpeedTestMenuItem.swift +++ b/ClashX/Views/ProxyGroupSpeedTestMenuItem.swift @@ -44,7 +44,39 @@ class ProxyGroupSpeedTestMenuItem: NSMenuItem { @objc func healthCheck() { guard testType == .reTest else { return } - ApiRequest.getGroupDelay(groupName: proxyGroup.name) { _ in } + + // Clear existing speed test results for all proxies in this group + ApiRequest.getMergedProxyData { [weak self] proxyResp in + guard let self = self else { return } + var proxiesToClear = [ClashProxyName]() + + // Get all proxy names in this group + if let allProxies = self.proxyGroup.all { + proxiesToClear.append(contentsOf: allProxies) + } + + // Clear speed test results by posting notifications with empty values + for proxyName in proxiesToClear { + NotificationCenter.default.post(name: .speedTestFinishForProxy, + object: nil, + userInfo: ["proxyName": proxyName, "delay": "", "rawValue": 0]) + } + + // Start actual testing after clearing results + ApiRequest.healthCheck(proxy: self.proxyGroup.name) + ApiRequest.getMergedProxyData { [weak self] proxyResp in + guard let self = self else { return } + var providers = Set() + self.proxyGroup.all?.compactMap{ + proxyResp?.proxiesMap[$0]?.enclosingProvider?.name + }.forEach{ + providers.insert($0) + } + providers.forEach{ + ApiRequest.healthCheck(proxy: $0) + } + } + } menu?.cancelTracking() } } @@ -89,11 +121,57 @@ private class ProxyGroupSpeedTestMenuItemView: MenuItemBaseView { private func startBenchmark() { guard let group = (enclosingMenuItem as? ProxyGroupSpeedTestMenuItem)?.proxyGroup else { return } + let testGroup = DispatchGroup() + + var proxies = [ClashProxyName]() + var providers = Set() + for testable in group.speedtestAble { + switch testable { + case let .provider(_, provider): + providers.insert(provider) + case let .proxy(name): + proxies.append(name) + default: + continue + } + } + // First, show "Testing" state and disable the menu item label.stringValue = NSLocalizedString("Testing", comment: "") enclosingMenuItem?.isEnabled = false setNeedsDisplay() + // Then clear existing speed test results for all proxies in this group + ApiRequest.getMergedProxyData { [weak self] proxyResp in + guard let self = self else { return } + + var proxiesToClear = [ClashProxyName]() + + // Get all proxy names in this group + if let allProxies = group.all { + proxiesToClear.append(contentsOf: allProxies) + } + + // Clear speed test results by posting notifications with empty values + for proxyName in proxiesToClear { + NotificationCenter.default.post(name: .speedTestFinishForProxy, + object: nil, + userInfo: ["proxyName": proxyName, "delay": "", "rawValue": 0]) + } + + // Start actual testing after clearing results + for proxyName in proxies { + testGroup.enter() + ApiRequest.getProxyDelay(proxyName: proxyName) { delay in + let delayStr = delay == 0 ? NSLocalizedString("fail", comment: "") : "\(delay) ms" + NotificationCenter.default.post(name: .speedTestFinishForProxy, + object: nil, + userInfo: ["proxyName": proxyName, "delay": delayStr, "rawValue": delay]) + testGroup.leave() + } + } + } + ApiRequest.getGroupDelay(groupName: group.name) { [weak self] delays in guard let self = self, let menu = self.enclosingMenuItem else { return } diff --git a/ClashX/Views/ProxyItemView.swift b/ClashX/Views/ProxyItemView.swift index 890bbdb85..d02f934e5 100644 --- a/ClashX/Views/ProxyItemView.swift +++ b/ClashX/Views/ProxyItemView.swift @@ -83,10 +83,18 @@ class ProxyItemView: MenuItemBaseView { delayLabel.stringValue = str ?? "" needsLayout = true - guard let delay = value, str != nil else { + guard let delay = value, let delayStr = str else { delayLabel.layer?.backgroundColor = NSColor.clear.cgColor return } + + if delayStr.isEmpty { + // Testing state - show gray badge + delayLabel.stringValue = NSLocalizedString("Testing", comment: "") + delayLabel.layer?.backgroundColor = CGColor.testing + return + } + switch delay { case 0: delayLabel.layer?.backgroundColor = CGColor.fail @@ -134,4 +142,5 @@ private extension CGColor { static let good = CGColor(red: 30.0 / 255, green: 181.0 / 255, blue: 30.0 / 255, alpha: 1) static let meduim = CGColor(red: 1, green: 135.0 / 255, blue: 0, alpha: 1) static let fail = CGColor(red: 218.0 / 255, green: 0.0, blue: 3.0 / 255, alpha: 1) + static let testing = CGColor(red: 128.0 / 255, green: 128.0 / 255, blue: 128.0 / 255, alpha: 1) }