连接探测只看通不通:绿红两色 60 格,去掉延迟抖动丢包,前台固定每秒探测
- ProbeGrid 只分绿(通)、红(超时或不可达)、灰(尚无数据),不再用橙色表示延迟偏高 - 探测历史固定 60 次:弹窗 20 列 3 行,主窗口 30 列 2 行;去掉格子下面的延迟、抖动、丢包与 NetworkController 里对应的统计 - 网络详情打开时固定每秒探测一次(NetworkController.foregroundProbeSeconds),移除 AppSettings.probeSeconds、设置里的“探测间隔”与同步文档字段;后台低频探测仍每 10 秒 - 译文、README、官网与隐私页、更新日志同步
This commit is contained in:
1 parent
d68fbce42a
commit
9e4f92e366
16 files changed
+35
-93
No files matched your search
@@ -10,6 +10,7 @@
|
||||
#### 样式
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
- 连接探测只用来看网络通不通:格子通了是绿色、不通是红色(不再用橙色表示延迟偏高),固定 60 格,弹窗排成 3 行、主窗口排成 2 行;去掉格子下面的延迟、抖动、丢包三项。打开网络详情时固定每秒探测一次,「设置 · 网络」里的“探测间隔”选项随之去掉,后台低频探测仍是每 10 秒。
|
||||
- 分段切换改为苹果标准样式:灰色圆角底槽,各段等宽铺满,选中的一段是实心蓝色、白字,切换时蓝块滑过去;网络详情的 IPv4 / IPv6 切换同样是灰底蓝选中。
|
||||
|
||||
#### 新增
|
||||
|
||||
@@ -89,7 +89,7 @@ npm 缓存 npm Cache
|
||||
{} {} 英寸 {} {}-inch
|
||||
{} · {} 个 {} · {}
|
||||
{} · {} 核 {} · {} cores
|
||||
{} · 每 {} 秒 {} · every {}s
|
||||
{} · 每秒一次 {} · every second
|
||||
{} · 约 {} {} · about {}
|
||||
{} · 负载 {}/{} {} · load {}/{}
|
||||
{} 个 {}
|
||||
@@ -123,8 +123,8 @@ npm 缓存 npm Cache
|
||||
{}后充满 Full in {}
|
||||
{}电量低 {} battery low
|
||||
{}({}) {} ({})
|
||||
{},{} 秒,后台{} {}, {}s, background {}
|
||||
{},{},健康 {} {}, {}, health {}
|
||||
{},后台{} {}, background {}
|
||||
{}:{} {}: {}
|
||||
~/.npm/_cacache,安装依赖时自动重新下载 ~/.npm/_cacache; downloaded again when installing dependencies
|
||||
~/Library/Caches 中各应用的缓存,删除后会按需重建 App caches in ~/Library/Caches; rebuilt as needed
|
||||
@@ -157,7 +157,6 @@ npm 缓存 npm Cache
|
||||
不限 No limit
|
||||
不限时 Indefinitely
|
||||
与 30 秒前持平 Same as 30s ago
|
||||
丢包 Loss
|
||||
严重 Critical
|
||||
中等负载 Moderate load
|
||||
中等风险 Medium risk
|
||||
@@ -470,7 +469,6 @@ npm 缓存 npm Cache
|
||||
应用缓存 App Caches
|
||||
应用退出或断开连接时,自动恢复风扇与睡眠设置 Restores fan and sleep settings when the app quits or disconnects
|
||||
废纸篓 Trash
|
||||
延迟 Latency
|
||||
开 On
|
||||
开关防休眠 Toggle Keep Awake
|
||||
开发构建不支持在线升级 Development builds can't update online
|
||||
@@ -523,7 +521,6 @@ npm 缓存 npm Cache
|
||||
打开 OpenStats 主窗口 Open the OpenStats Window
|
||||
打开 SMC 失败 ({}) Failed to open SMC ({})
|
||||
打开登录项设置 Open Login Items Settings
|
||||
打开网络详情时使用 Used while network details are open
|
||||
打开网络详情时向 Cloudflare(1.1.1.1)或 ipify 查询一次公网地址,10 分钟内不重复请求;归属地、ASN、网络类型与纯净度由这台 Mac 直接向 cleanip.io 查询,只发送公网地址 When network details open, asks Cloudflare (1.1.1.1) or ipify for your public address, at most once every 10 minutes; location, ASN, network type and cleanliness are looked up by this Mac directly at cleanip.io, sending only the public address
|
||||
打开进程页 Show Processes
|
||||
打开通知设置 Open Notification Settings
|
||||
@@ -532,7 +529,6 @@ npm 缓存 npm Cache
|
||||
扫描中 Scanning
|
||||
扫描中… Scanning…
|
||||
扫描于 Scanned
|
||||
抖动 Jitter
|
||||
折线历史 Line History
|
||||
折线是最近 30 次采样(约 1 分钟)的走势 The line shows the last 30 samples (about a minute)
|
||||
拷贝 Copy
|
||||
@@ -550,7 +546,6 @@ npm 缓存 npm Cache
|
||||
掌托 Palm rest
|
||||
排队程度 Run Queue
|
||||
探测目标 Probe Target
|
||||
探测间隔 Probe Interval
|
||||
接口 Interface
|
||||
接通电源 Plugged in
|
||||
推出 Eject
|
||||
@@ -762,7 +757,6 @@ npm 缓存 npm Cache
|
||||
现在 Now
|
||||
现在没有本地快照 No local snapshots right now
|
||||
用 Apple 智能解释 Explain with Apple Intelligence
|
||||
用 ping 测量延迟与丢包,在网络详情里以格子显示 Measures latency and loss with ping, shown as a grid in network details
|
||||
用户 User
|
||||
用户 {} User {}
|
||||
用户资源库 User Library
|
||||
@@ -867,6 +861,7 @@ npm 缓存 npm Cache
|
||||
网络已恢复 Network Restored
|
||||
网络断开 Network Down
|
||||
网络类型 Network type
|
||||
网络详情打开时每秒 ping 一次,通了是绿格、不通是红格,显示最近 60 次 Pings once a second while network details are open: green when reachable, red when not, showing the last 60
|
||||
网络运营方 Network operator
|
||||
网络连接中断超过 20 秒,恢复后再提示一次 Network has been down for over 20 seconds; notifies again when it's back
|
||||
网络连接已中断超过 20 秒。可以打开网络详情查看接口与路由器状态。 The network has been down for over 20 seconds. Open network details to check the interface and router.
|
||||
|
||||
@@ -206,11 +206,10 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// 探测目标、间隔变化时清空历史重新探测;关闭公网 IP 查询时清除已显示的结果
|
||||
/// 探测目标变化时清空历史重新探测;关闭公网 IP 查询时清除已显示的结果
|
||||
private func observeProbeSettings() {
|
||||
withObservationTracking {
|
||||
_ = model.settings.probeTarget
|
||||
_ = model.settings.probeSeconds
|
||||
_ = model.settings.publicIPLookup
|
||||
} onChange: { [weak self] in
|
||||
Task { @MainActor in
|
||||
|
||||
@@ -464,13 +464,12 @@ struct MirroredRateChart: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 连接探测格子:按时间从左到右、从上到下排列,最新的一格在末尾。
|
||||
/// 绿色正常,橙色延迟偏高,红色超时,灰色尚无数据
|
||||
/// 连接探测格子:一格一次探测,从左上到右下按时间排,最新的在最后一格。
|
||||
/// 只表示通不通:通了是绿色,超时或不可达是红色,还没探测到的是灰色
|
||||
struct ProbeGrid: View {
|
||||
let samples: [ProbeSample]
|
||||
var columns = 20
|
||||
var rows = 3
|
||||
var slowThreshold: Double = 200
|
||||
|
||||
var body: some View {
|
||||
let capacity = columns * rows
|
||||
@@ -487,8 +486,8 @@ struct ProbeGrid: View {
|
||||
let color: Color
|
||||
if index < offset {
|
||||
color = DS.Palette.track
|
||||
} else if let latency = recent[index - offset].latency {
|
||||
color = latency >= slowThreshold ? DS.Palette.warning : DS.Palette.success
|
||||
} else if recent[index - offset].latency != nil {
|
||||
color = DS.Palette.success
|
||||
} else {
|
||||
color = DS.Palette.error
|
||||
}
|
||||
|
||||
@@ -100,17 +100,13 @@ private struct ProbeSection: View {
|
||||
|
||||
SectionCard(title: PopoverSection.networkProbe.title, trailing: {
|
||||
if settings.probeEnabled {
|
||||
Text(verbatim: tr("\(network.probeAddress ?? "—") · 每 \(settings.probeSeconds) 秒"))
|
||||
Text(verbatim: tr("\(network.probeAddress ?? "—") · 每秒一次"))
|
||||
}
|
||||
}) {
|
||||
if settings.probeEnabled {
|
||||
ProbeGrid(samples: network.probes.elements, columns: isDetailPage ? 40 : 20, rows: 3)
|
||||
HStack(spacing: DS.Space.s3) {
|
||||
stat(tr("延迟"), network.latency.map(milliseconds) ?? "—")
|
||||
stat(tr("抖动"), network.jitter.map(milliseconds) ?? "—")
|
||||
stat(tr("丢包"), network.lossRate.map { Format.percent($0) } ?? "—",
|
||||
tone: (network.lossRate ?? 0) > 0.05 ? .error : .neutral)
|
||||
}
|
||||
// 只用来看网络通不通:60 格是最近 60 次探测,主窗口里更宽,排成两行
|
||||
let columns = isDetailPage ? 30 : 20
|
||||
ProbeGrid(samples: network.probes.elements, columns: columns, rows: NetworkController.probeCapacity / columns)
|
||||
} else {
|
||||
HStack {
|
||||
Text(tr("定时 ping 一个地址,记录网络是否通畅")).dsFont(.xs).foregroundStyle(DS.Palette.textSecondary)
|
||||
@@ -121,21 +117,6 @@ private struct ProbeSection: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func milliseconds(_ value: Double) -> String {
|
||||
value < 10 ? "\(value.formatted(.number.precision(.fractionLength(1)))) ms" : "\(Int(value.rounded())) ms"
|
||||
}
|
||||
|
||||
private func stat(_ label: String, _ value: String, tone: Tone = .neutral) -> some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(label).dsFont(.xs).foregroundStyle(DS.Palette.textTertiary)
|
||||
Text(verbatim: value)
|
||||
.dsFont(.sm, weight: .semibold)
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(tone == .neutral ? DS.Palette.textPrimary : tone.color)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 接口
|
||||
|
||||
@@ -552,18 +552,10 @@ struct NetworkSettings: View {
|
||||
|
||||
SettingsGroup(caption: tr("连接探测")) {
|
||||
GroupRow(showsDivider: false) {
|
||||
SettingRow(title: tr("定时探测网络"), subtitle: tr("用 ping 测量延迟与丢包,在网络详情里以格子显示")) {
|
||||
SettingRow(title: tr("定时探测网络"), subtitle: tr("网络详情打开时每秒 ping 一次,通了是绿格、不通是红格,显示最近 60 次")) {
|
||||
DSToggle(isOn: $settings.probeEnabled, label: tr("定时探测网络"))
|
||||
}
|
||||
}
|
||||
GroupRow {
|
||||
SettingRow(title: tr("探测间隔"), subtitle: tr("打开网络详情时使用")) {
|
||||
SegmentedControl(selection: $settings.probeSeconds,
|
||||
options: AppSettings.probeOptions.map { ($0, tr("\($0) 秒")) })
|
||||
.frame(width: DS.Size.sidebarWidth)
|
||||
}
|
||||
}
|
||||
.disabled(!settings.probeEnabled)
|
||||
GroupRow {
|
||||
SettingRow(title: tr("探测目标"), subtitle: tr("国内网络建议选阿里云或腾讯;选路由器只检测本地连接")) {
|
||||
Picker(tr("探测目标"), selection: $settings.probeTarget) {
|
||||
|
||||
@@ -14,7 +14,6 @@ enum SnapshotRenderer {
|
||||
let defaults = UserDefaults(suiteName: "OpenStats.snapshot") ?? .standard
|
||||
let settings = AppSettings(defaults: defaults)
|
||||
settings.menuBarItems = [.cpu, .gpu, .memory, .network, .disk, .temperature, .battery]
|
||||
settings.probeSeconds = 1
|
||||
// 历史页用示例数据:最近 24 小时每分钟一条,中间留一段“睡眠”空档
|
||||
let historyURL = FileManager.default.temporaryDirectory.appendingPathComponent("openstats-snapshot-history.sqlite")
|
||||
try? FileManager.default.removeItem(at: historyURL)
|
||||
|
||||
@@ -435,9 +435,6 @@ public final class AppSettings {
|
||||
public var probeInBackground: Bool {
|
||||
didSet { defaults.set(probeInBackground, forKey: Keys.probeInBackground) }
|
||||
}
|
||||
public var probeSeconds: Int {
|
||||
didSet { defaults.set(probeSeconds, forKey: Keys.probeSeconds) }
|
||||
}
|
||||
public var probeTarget: ProbeTarget {
|
||||
didSet { defaults.set(probeTarget.rawValue, forKey: Keys.probeTarget) }
|
||||
}
|
||||
@@ -491,7 +488,6 @@ public final class AppSettings {
|
||||
}
|
||||
|
||||
public static let refreshOptions = [1, 2, 3, 5]
|
||||
public static let probeOptions = [1, 2, 5]
|
||||
public static let batteryFloorOptions = [10, 20, 30, 40]
|
||||
public static let fanSafetyOptions = [85, 90, 95, 100]
|
||||
public static let alertTemperatureOptions = [85, 90, 95, 100]
|
||||
@@ -526,8 +522,6 @@ public final class AppSettings {
|
||||
.map { Set($0.compactMap(PopoverSection.init(rawValue:))) } ?? PopoverSection.hiddenByDefault
|
||||
probeEnabled = defaults.object(forKey: Keys.probeEnabled) as? Bool ?? true
|
||||
probeInBackground = defaults.object(forKey: Keys.probeInBackground) as? Bool ?? true
|
||||
probeSeconds = Self.probeOptions.contains(defaults.integer(forKey: Keys.probeSeconds))
|
||||
? defaults.integer(forKey: Keys.probeSeconds) : 2
|
||||
probeTarget = defaults.string(forKey: Keys.probeTarget).flatMap(ProbeTarget.init(rawValue:)) ?? .cloudflare
|
||||
publicIPLookup = defaults.object(forKey: Keys.publicIPLookup) as? Bool ?? true
|
||||
autoCheckUpdates = defaults.object(forKey: Keys.autoCheckUpdates) as? Bool ?? true
|
||||
@@ -589,7 +583,6 @@ public final class AppSettings {
|
||||
static let menuBarLayout = "menuBarLayout"
|
||||
static let hiddenPopoverSections = "hiddenPopoverSections"
|
||||
static let probeEnabled = "probeEnabled"
|
||||
static let probeSeconds = "probeSeconds"
|
||||
static let probeInBackground = "probeInBackground"
|
||||
static let probeTarget = "probeTarget"
|
||||
static let publicIPLookup = "publicIPLookup"
|
||||
|
||||
@@ -110,7 +110,7 @@ final class DiagnosticsExporter {
|
||||
row(tr("菜单栏风格"), settings.menuBarStyle.rawValue)
|
||||
row(tr("刷新间隔"), tr("\(settings.refreshSeconds) 秒"))
|
||||
row(tr("外观"), settings.appearance.rawValue)
|
||||
row(tr("连接探测"), settings.probeEnabled ? tr("\(settings.probeTarget.rawValue),\(settings.probeSeconds) 秒,后台\(settings.probeInBackground ? tr("开启") : tr("关闭"))") : tr("关闭"))
|
||||
row(tr("连接探测"), settings.probeEnabled ? tr("\(settings.probeTarget.rawValue),后台\(settings.probeInBackground ? tr("开启") : tr("关闭"))") : tr("关闭"))
|
||||
row(tr("公网 IP 查询"), settings.publicIPLookup ? tr("开启") : tr("关闭"))
|
||||
row(tr("风扇安全温度"), "\(settings.fanSafetyTemperature)°C")
|
||||
row(tr("合盖电量下限"), "\(settings.lidModeBatteryFloor)%")
|
||||
|
||||
@@ -12,8 +12,8 @@ public struct ProbeSample: Sendable, Equatable {
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class NetworkController {
|
||||
/// 主窗口显示 120 次,弹窗显示最近 60 次
|
||||
public static let probeCapacity = 120
|
||||
/// 探测格子显示最近 60 次;前台每秒一次,也就是最近 60 秒
|
||||
public static let probeCapacity = 60
|
||||
|
||||
public private(set) var details: NetworkDetails?
|
||||
/// 公网 IP 信息按地址族各存一份:两份都含 IPv4 与 IPv6 地址,归属地、纯净度等是各自那个地址的
|
||||
@@ -50,7 +50,8 @@ public final class NetworkController {
|
||||
/// 正在运行的探测是否为前台频率;前后台切换时重启循环,不必等完后台的长间隔
|
||||
@ObservationIgnored private var probingForeground: Bool?
|
||||
|
||||
/// 详情关闭、只在菜单栏显示网速时的低频探测间隔
|
||||
/// 网络详情打开时每秒探测一次;详情关闭、只在菜单栏显示网速时低频探测
|
||||
static let foregroundProbeSeconds = 1
|
||||
static let backgroundProbeSeconds = 10
|
||||
|
||||
/// 上次查到的公网 IP 信息:点开弹窗时先显示它,只有超过 7 天或公网 IP 变了才重新查归属地
|
||||
@@ -113,24 +114,7 @@ public final class NetworkController {
|
||||
rate.totalUploaded >= baseline.upload ? rate.totalUploaded - baseline.upload : 0)
|
||||
}
|
||||
|
||||
// MARK: 统计
|
||||
|
||||
/// 最近一次成功探测的延迟
|
||||
var latency: Double? { probes.elements.last?.latency }
|
||||
|
||||
/// 抖动:相邻两次成功探测的延迟差的平均值
|
||||
var jitter: Double? {
|
||||
let values = probes.elements.suffix(20).compactMap(\.latency)
|
||||
guard values.count > 2 else { return nil }
|
||||
let differences = zip(values.dropFirst(), values).map { abs($0 - $1) }
|
||||
return differences.reduce(0, +) / Double(differences.count)
|
||||
}
|
||||
|
||||
var lossRate: Double? {
|
||||
let recent = probes.elements
|
||||
guard !recent.isEmpty else { return nil }
|
||||
return Double(recent.filter { $0.latency == nil }.count) / Double(recent.count)
|
||||
}
|
||||
// MARK: 探测目标
|
||||
|
||||
var probeAddress: String? {
|
||||
settings.probeTarget.address(router: details?.physical?.router)
|
||||
@@ -316,7 +300,7 @@ public final class NetworkController {
|
||||
let clock = ContinuousClock()
|
||||
while !Task.isCancelled {
|
||||
guard let self else { return }
|
||||
let interval = Double(foreground ? self.settings.probeSeconds : Self.backgroundProbeSeconds)
|
||||
let interval = Double(foreground ? Self.foregroundProbeSeconds : Self.backgroundProbeSeconds)
|
||||
if self.details == nil { await self.refreshDetails() }
|
||||
let started = clock.now
|
||||
guard let address = self.probeAddress else {
|
||||
|
||||
@@ -21,7 +21,6 @@ public struct SettingsDocument: Codable, Equatable, Sendable {
|
||||
public var hiddenPopoverSections: [String]?
|
||||
public var probeEnabled: Bool?
|
||||
public var probeInBackground: Bool?
|
||||
public var probeSeconds: Int?
|
||||
public var probeTarget: String?
|
||||
public var publicIPLookup: Bool?
|
||||
public var enabledAlerts: [String]?
|
||||
@@ -55,7 +54,6 @@ extension AppSettings {
|
||||
doc.hiddenPopoverSections = hiddenPopoverSections.map(\.rawValue).sorted()
|
||||
doc.probeEnabled = probeEnabled
|
||||
doc.probeInBackground = probeInBackground
|
||||
doc.probeSeconds = probeSeconds
|
||||
doc.probeTarget = probeTarget.rawValue
|
||||
doc.publicIPLookup = publicIPLookup
|
||||
doc.enabledAlerts = enabledAlerts.map(\.rawValue).sorted()
|
||||
@@ -99,7 +97,6 @@ extension AppSettings {
|
||||
assign(\.hiddenPopoverSections, doc.hiddenPopoverSections.map { Set($0.compactMap(PopoverSection.init(rawValue:))) })
|
||||
assign(\.probeEnabled, doc.probeEnabled)
|
||||
assign(\.probeInBackground, doc.probeInBackground)
|
||||
assign(\.probeSeconds, option(doc.probeSeconds, in: Self.probeOptions))
|
||||
assign(\.probeTarget, doc.probeTarget.flatMap(ProbeTarget.init(rawValue:)))
|
||||
assign(\.publicIPLookup, doc.publicIPLookup)
|
||||
assign(\.enabledAlerts, doc.enabledAlerts.map { Set($0.compactMap(AlertKind.init(rawValue:))) })
|
||||
|
||||
@@ -59,14 +59,15 @@ system language by default; switch it in Settings → General → Language.
|
||||
<!-- changelog:start -->
|
||||
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
|
||||
|
||||
Latest release **0.3.2** (2026-09-16) · **3** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
|
||||
Latest release **0.3.2** (2026-09-16) · **4** changes in development · [full changelog](CHANGELOG.md) (kept in Chinese)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-16</b> · Unreleased · 2 style · 1 added</summary>
|
||||
<summary><b>2026-09-16</b> · Unreleased · 3 style · 1 added</summary>
|
||||
|
||||
**Style**
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
- 连接探测只用来看网络通不通:格子通了是绿色、不通是红色(不再用橙色表示延迟偏高),固定 60 格,弹窗排成 3 行、主窗口排成 2 行;去掉格子下面的延迟、抖动、丢包三项。打开网络详情时固定每秒探测一次,「设置 · 网络」里的“探测间隔”选项随之去掉,后台低频探测仍是每 10 秒。
|
||||
- 分段切换改为苹果标准样式:灰色圆角底槽,各段等宽铺满,选中的一段是实心蓝色、白字,切换时蓝块滑过去;网络详情的 IPv4 / IPv6 切换同样是灰底蓝选中。
|
||||
|
||||
**Added**
|
||||
@@ -138,7 +139,7 @@ Choose which sections each popover shows in Settings. `Esc` closes it.
|
||||
queueing (load average per core, rising or falling); usage summed by app.
|
||||
- **Memory.** What is still available with a pressure timeline; a waterline bar; memory saved by compression and
|
||||
live swap I/O; usage summed by app.
|
||||
- **Network.** Traffic history; a connection-probe grid (latency, jitter, loss); interface,
|
||||
- **Network.** Traffic history; a connection-probe grid (green when reachable, red when not, last 60 pings); interface,
|
||||
Wi-Fi signal, VPN / proxy; local and public IPv4 / IPv6 with a flag, region and ASN; an IP cleanliness score with an F to A+
|
||||
grade and risk flags (VPN, proxy, Tor, data center, abuse); DNS flush
|
||||
and one-click switching to Cloudflare, Google, Tencent, Alibaba Cloud or manual servers;
|
||||
@@ -244,7 +245,7 @@ check in Settings → About:
|
||||
come from `cleanip.io`, queried directly from each Mac with nothing but the public address and never
|
||||
through our servers; a result is kept for a week unless the address changes or you refresh.
|
||||
- **Connection probe**: an ICMP ping to the target you pick (Cloudflare, Google, Alibaba Cloud,
|
||||
Tencent or your router) every 1, 2 or 5 seconds, only while the network item is in the menu bar
|
||||
Tencent or your router) once a second while network details are open (every 10 seconds in the background), only while the network item is in the menu bar
|
||||
or network details are open.
|
||||
- **Update check**: at launch and once a day, the app reads a version manifest from getopenstats.com — the
|
||||
manifest only. When a new version is out it asks; nothing installs without your click.
|
||||
|
||||
+5
-4
@@ -55,14 +55,15 @@ CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
|
||||
<!-- changelog:start -->
|
||||
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
|
||||
|
||||
最新版本 **0.3.2**(2026-09-16) · 开发中 **3** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
最新版本 **0.3.2**(2026-09-16) · 开发中 **4** 项改动尚未发布 · [完整更新日志](CHANGELOG.md)
|
||||
|
||||
<details open>
|
||||
<summary><b>2026-09-16</b> · 未发布 · 样式 2 · 新增 1</summary>
|
||||
<summary><b>2026-09-16</b> · 未发布 · 样式 3 · 新增 1</summary>
|
||||
|
||||
**样式**
|
||||
|
||||
- 主窗口在 macOS 26 上改用液态玻璃:侧边栏是一块浮在窗口里的玻璃面板,红绿灯按钮落在面板里,选中项垫一块淡蓝色圆角底并随切换滑动;顶栏右侧“在菜单栏显示”一组收进玻璃胶囊。主按钮是蓝色玻璃胶囊、次要按钮与图标按钮是透明玻璃,风扇模式等快捷切换是玻璃胶囊,开关换成系统开关(拖动时滑块变成玻璃)。设置分组与数据卡片保持实色,与系统设置一致。macOS 14、15 上保持原来的样式。
|
||||
- 连接探测只用来看网络通不通:格子通了是绿色、不通是红色(不再用橙色表示延迟偏高),固定 60 格,弹窗排成 3 行、主窗口排成 2 行;去掉格子下面的延迟、抖动、丢包三项。打开网络详情时固定每秒探测一次,「设置 · 网络」里的“探测间隔”选项随之去掉,后台低频探测仍是每 10 秒。
|
||||
- 分段切换改为苹果标准样式:灰色圆角底槽,各段等宽铺满,选中的一段是实心蓝色、白字,切换时蓝块滑过去;网络详情的 IPv4 / IPv6 切换同样是灰底蓝选中。
|
||||
|
||||
**新增**
|
||||
@@ -129,7 +130,7 @@ CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
|
||||
**详情弹窗**——每个指标一个菜单栏图标,点击弹出该项的窄详情,显示哪些区块可在设置里勾选;按 `Esc` 关闭。
|
||||
- **CPU**:占用与状态、与 30 秒前的变化、CPU 温度与余量、1 / 3 / 5 分钟走势;核心热力图与此刻各核心占用;核心分工与频率;排队程度(每核平均负载与趋势);按应用汇总。
|
||||
- **内存**:还可用多少与压力走势;内存水位条;压缩省下的内存与交换区读写;按应用汇总。
|
||||
- **网络**:流量历史;连接探测格子(延迟、抖动、丢包);接口、Wi-Fi 信号、VPN / 代理;本地与公网 IPv4 / IPv6,
|
||||
- **网络**:流量历史;连接探测格子(通了是绿格、不通是红格,最近 60 次);接口、Wi-Fi 信号、VPN / 代理;本地与公网 IPv4 / IPv6,
|
||||
归属地国旗、ASN;IP 纯净度评分与 F 到 A+ 等级、风险标记(VPN、代理、Tor、机房、滥用记录);DNS 一键刷新,一键切换 Cloudflare、Google、腾讯、阿里云或手动填写;各进程流量。
|
||||
- **磁盘**:启动磁盘容量分段条(已用、可清除、可用);读写速度与 60 秒走势;SSD 健康;读写最多的应用。
|
||||
- **GPU**、**温度与风扇**:使用历史、各组温度、风扇转速与快捷模式。
|
||||
@@ -211,7 +212,7 @@ CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
|
||||
会联网的功能都可以关闭。前两项在“设置 → 网络”中,检查更新在“设置 · 关于”中:
|
||||
|
||||
- **公网 IP**:打开网络详情时向 Cloudflare `1.1.1.1`(回退 ipify)请求一次公网地址,10 分钟内不重复请求。归属地、ASN、网络类型与纯净度评分由这台 Mac 直接向 `cleanip.io` 查询,只发送公网地址、不经过我们的服务器;地址没变、不满 7 天就沿用上次结果,点刷新才重查。
|
||||
- **连接探测**:每 1、2 或 5 秒向你选择的目标(Cloudflare、Google、阿里云、腾讯或路由器)发送一次 ICMP ping,只在菜单栏显示网络项或打开网络详情时运行。
|
||||
- **连接探测**:打开网络详情时每秒(后台每 10 秒)向你选择的目标(Cloudflare、Google、阿里云、腾讯或路由器)发送一次 ICMP ping,只在菜单栏显示网络项或打开网络详情时运行。
|
||||
- **检查更新**:启动时与之后每天向 getopenstats.com 读取一次版本清单,只下载清单本身;发现新版本时提示,由你决定是否安装。
|
||||
|
||||
账号是可选的,只在登录后同步偏好设置,不含任何监控数据。
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ settings:
|
||||
SWIFT_VERSION: "6.0"
|
||||
MARKETING_VERSION: "0.3.2"
|
||||
# 构建号四位数字,每次 make build 加一;营销版本只在发布时用 Scripts/version.sh patch / minor 修改
|
||||
CURRENT_PROJECT_VERSION: "0067"
|
||||
CURRENT_PROJECT_VERSION: "0068"
|
||||
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
||||
# 本地开发使用 ad-hoc 签名;发布时改为 Developer ID(见 docs/PLAN.md)
|
||||
CODE_SIGN_STYLE: Manual
|
||||
|
||||
+2
-2
@@ -248,7 +248,7 @@
|
||||
<h2>网络,一眼看到底</h2>
|
||||
<ul class="check-list">
|
||||
<li>上下镜像的流量历史,上传绿、下载蓝</li>
|
||||
<li>连接探测格子:每 1 / 2 / 5 秒 ping 一次,延迟、抖动、丢包一目了然</li>
|
||||
<li>连接探测格子:每秒 ping 一次,通了是绿格、不通是红格,最近 60 次一眼看清</li>
|
||||
<li>本地与公网 IPv4 / IPv6、路由器、Wi‑Fi 信号,点一下就拷贝</li>
|
||||
<li>归属地显示国旗、城市、ASN 与网络运营方,原生 / 广播、住宅 / 机房直接标出</li>
|
||||
<li>IP 纯净度:CleanIP.io 评分与 F 到 A+ 等级色带,列出 VPN、代理、Tor、机房、滥用记录等风险标记</li>
|
||||
@@ -326,7 +326,7 @@
|
||||
<li><b>统计上报</b><span>没有</span></li>
|
||||
<li><b>公网 IP</b><span>打开网络详情时向 Cloudflare 查询一次公网地址,10 分钟内不重复,可关闭</span></li>
|
||||
<li><b>归属地</b><span>归属地、ASN、网络类型与纯净度评分来自 cleanip.io,这台 Mac 直接向它查自己的公网 IP,只发送这个地址</span></li>
|
||||
<li><b>连接探测</b><span>按设定间隔 ping 你选择的目标,只在显示网络项时运行,可关闭</span></li>
|
||||
<li><b>连接探测</b><span>打开网络详情时每秒 ping 一次你选择的目标(后台每 10 秒),只在显示网络项时运行,可关闭</span></li>
|
||||
<li><b>偏好设置</b><span>保存在本机;登录后同步的那一份不含任何监控数据</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
<ul class="fact-list">
|
||||
<li><b>公网 IP</b><span>打开网络详情时向 Cloudflare 查询一次公网地址,10 分钟内不重复,可在设置中关闭</span></li>
|
||||
<li><b>归属地</b><span>归属地、ASN、网络类型与纯净度评分由 cleanip.io 提供:这台 Mac 直接向它查询自己的公网 IP,只发送这个地址、不带其他信息,不经过 OpenStats 的服务器</span></li>
|
||||
<li><b>连接探测</b><span>按设定间隔 ping 你选择的目标,只在显示网络项时运行,可关闭</span></li>
|
||||
<li><b>连接探测</b><span>打开网络详情时每秒 ping 一次你选择的目标(后台每 10 秒),只在显示网络项时运行,可关闭</span></li>
|
||||
<li><b>检查更新</b><span>启动时与每天一次读取 getopenstats.com 上的版本清单,请求不带任何标识</span></li>
|
||||
</ul>
|
||||
|
||||
|
||||
Reference in new issue
Block a user