清理死代码、死译文与重复代码
- 删掉没人调用的函数、属性与解码字段:网络弹窗的两个标签函数、supportsStyleOverride、probedAddress、countryEn、辅助工具客户端的单风扇恢复自动封装(协议与辅助工具端保留,不动 XPC 接口) - 译文表删掉 19 条不再使用的条目,补上测速窗口“开始”按钮漏掉的英文 - 网络弹窗与出口窗口各自一份的归属地格式化和 IP 类型徽章合并为 GeoText;两个折线图里重复的参考线绘制提成 ChartGrid - 官网样式删掉没人用的 .mb-drop__ext - server/sync/sync 是误提交的 Go 编译产物,从仓库移除并忽略
This commit is contained in:
1 parent
1022e4e541
commit
645d161cbd
12 files changed
+53
-96
No files matched your search
@@ -7,3 +7,5 @@ xcuserdata/
|
|||||||
Packages/OpenStatsKit/Package.resolved
|
Packages/OpenStatsKit/Package.resolved
|
||||||
dist/
|
dist/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
# go build 在同步服务目录下生成的本地二进制
|
||||||
|
server/sync/sync
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#### 修复
|
#### 修复
|
||||||
|
|
||||||
- 账号登录后应用直接退出:在登录窗口里选完 GitHub、Google 或 Apple 账号,应用立即闪退,重新打开也没有登录状态。原因是系统在后台队列回调登录结果,而回调闭包带着主线程隔离,被运行时当场中止,令牌还没来得及换取和保存。现在回调改为不带隔离、收到后再回到主线程处理,并补了回归测试。
|
- 账号登录后应用直接退出:在登录窗口里选完 GitHub、Google 或 Apple 账号,应用立即闪退,重新打开也没有登录状态。原因是系统在后台队列回调登录结果,而回调闭包带着主线程隔离,被运行时当场中止,令牌还没来得及换取和保存。现在回调改为不带隔离、收到后再回到主线程处理,并补了回归测试。
|
||||||
|
- 英文界面下测速窗口的“开始”按钮没有翻译,仍显示中文。
|
||||||
|
|
||||||
## 0.6.1 · 2026-09-18
|
## 0.6.1 · 2026-09-18
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ GPU 占用 GPU Usage
|
|||||||
Globalping 返回了错误(HTTP {}) Globalping returned an error (HTTP {})
|
Globalping 返回了错误(HTTP {}) Globalping returned an error (HTTP {})
|
||||||
Google(8.8.8.8) Google (8.8.8.8)
|
Google(8.8.8.8) Google (8.8.8.8)
|
||||||
IP 地址 IP Addresses
|
IP 地址 IP Addresses
|
||||||
IP 类型 IP type
|
|
||||||
IP 纯净度 IP cleanliness
|
IP 纯净度 IP cleanliness
|
||||||
IPv6 没有经过代理:只走 IPv6 的连接会直接暴露本机地址 {}。 IPv6 isn't going through the proxy: IPv6-only connections expose your address {}.
|
IPv6 没有经过代理:只走 IPv6 的连接会直接暴露本机地址 {}。 IPv6 isn't going through the proxy: IPv6-only connections expose your address {}.
|
||||||
IPv6 连接也经过代理。 IPv6 connections also go through the proxy.
|
IPv6 连接也经过代理。 IPv6 connections also go through the proxy.
|
||||||
@@ -104,7 +103,6 @@ npm 缓存 npm Cache
|
|||||||
{} 个应用,系统自带的不列出 {} apps; built-in apps are not listed
|
{} 个应用,系统自带的不列出 {} apps; built-in apps are not listed
|
||||||
{} 个网站 {} sites
|
{} 个网站 {} sites
|
||||||
{} 个进程 {} processes
|
{} 个进程 {} processes
|
||||||
{} 中位延迟 {} median
|
|
||||||
{} 分钟 {} min
|
{} 分钟 {} min
|
||||||
{} 可用 {} · 共 {} {} {} free · {} total
|
{} 可用 {} · 共 {} {} {} free · {} total
|
||||||
{} 天 {} d
|
{} 天 {} d
|
||||||
@@ -117,7 +115,6 @@ npm 缓存 npm Cache
|
|||||||
{} 正在运行,卸载前需要先退出。 {} is running. Quit it before uninstalling.
|
{} 正在运行,卸载前需要先退出。 {} is running. Quit it before uninstalling.
|
||||||
{} 测 at {}
|
{} 测 at {}
|
||||||
{} 秒 {}s
|
{} 秒 {}s
|
||||||
{} 秒 / {} MB {}s / {} MB
|
|
||||||
{} 秒·{} MB {}s · {} MB
|
{} 秒·{} MB {}s · {} MB
|
||||||
{} 组 {} groups
|
{} 组 {} groups
|
||||||
{} 缓存 {} Cache
|
{} 缓存 {} Cache
|
||||||
@@ -127,7 +124,6 @@ npm 缓存 npm Cache
|
|||||||
{} 项 · 需要管理员权限,只读 {} items · needs admin, read-only
|
{} 项 · 需要管理员权限,只读 {} items · needs admin, read-only
|
||||||
{} 项失败 {} failed
|
{} 项失败 {} failed
|
||||||
{}%(阈值 {}%) {}% (threshold {}%)
|
{}%(阈值 {}%) {}% (threshold {}%)
|
||||||
{}/{} 个节点连得上 {}/{} nodes reachable
|
|
||||||
{}{}只剩 {}%,记得充电。 {}{} is down to {}%. Time to charge.
|
{}{}只剩 {}%,记得充电。 {}{} is down to {}%. Time to charge.
|
||||||
{}{}({}) {}{} ({})
|
{}{}({}) {}{} ({})
|
||||||
{}。系统会自动分配任务,不用你操心 {}. macOS assigns work between them automatically.
|
{}。系统会自动分配任务,不用你操心 {}. macOS assigns work between them automatically.
|
||||||
@@ -205,7 +201,6 @@ npm 缓存 npm Cache
|
|||||||
以后再说 Later
|
以后再说 Later
|
||||||
以系统权限运行的后台服务,只接受本应用的请求 A background service with system privileges that only accepts requests from this app
|
以系统权限运行的后台服务,只接受本应用的请求 A background service with system privileges that only accepts requests from this app
|
||||||
仪表盘 Dashboard
|
仪表盘 Dashboard
|
||||||
企业 Business
|
|
||||||
企业 IP Business IP
|
企业 IP Business IP
|
||||||
企业网络 Business network
|
企业网络 Business network
|
||||||
会一并找出它留在资源库里的缓存、偏好设置、容器与登录启动项,全部移到废纸篓,可以放回 Also finds its caches, preferences, containers and launch agents in your Library and moves them all to the Trash, where you can restore them
|
会一并找出它留在资源库里的缓存、偏好设置、容器与登录启动项,全部移到废纸篓,可以放回 Also finds its caches, preferences, containers and launch agents in your Library and moves them all to the Trash, where you can restore them
|
||||||
@@ -217,8 +212,6 @@ npm 缓存 npm Cache
|
|||||||
低风险 Low risk
|
低风险 Low risk
|
||||||
住宅 IP Residential IP
|
住宅 IP Residential IP
|
||||||
住宅代理 Residential proxy
|
住宅代理 Residential proxy
|
||||||
住宅宽带 Residential
|
|
||||||
住宅概率 Residential probability
|
|
||||||
余量 {} Headroom {}
|
余量 {} Headroom {}
|
||||||
使用 {} 登录 Sign in with {}
|
使用 {} 登录 Sign in with {}
|
||||||
使用云端设置 Use cloud settings
|
使用云端设置 Use cloud settings
|
||||||
@@ -311,14 +304,12 @@ npm 缓存 npm Cache
|
|||||||
剪切 Cut
|
剪切 Cut
|
||||||
功耗 Power
|
功耗 Power
|
||||||
包名是 {} Bundle ID is {}
|
包名是 {} Bundle ID is {}
|
||||||
包含城市数据 Include City Data
|
|
||||||
协议版本 Protocol version
|
协议版本 Protocol version
|
||||||
单个节点一次测速最多花的时间与流量,先到哪个停哪个 Time and data cap for one node's speed test, whichever comes first
|
单个节点一次测速最多花的时间与流量,先到哪个停哪个 Time and data cap for one node's speed test, whichever comes first
|
||||||
单行 Inline
|
单行 Inline
|
||||||
单行文字 Inline Text
|
单行文字 Inline Text
|
||||||
占整机 CPU 的比例:{} 个核心全部跑满为 100% Share of total CPU: all {} cores fully busy is 100%
|
占整机 CPU 的比例:{} 个核心全部跑满为 100% Share of total CPU: all {} cores fully busy is 100%
|
||||||
占用 Usage
|
占用 Usage
|
||||||
占用超过 85% 时数值与图形显示为红色 Show values and graphs in red above 85%
|
|
||||||
占用超过 85%(电池电量低于 20%)时数值与图形显示为红色 Values and graphics turn red above 85% usage (below 20% battery)
|
占用超过 85%(电池电量低于 20%)时数值与图形显示为红色 Values and graphics turn red above 85% usage (below 20% battery)
|
||||||
占用高于 Usage above
|
占用高于 Usage above
|
||||||
卷 Volume
|
卷 Volume
|
||||||
@@ -339,14 +330,12 @@ npm 缓存 npm Cache
|
|||||||
压缩包里应当只有一个 .app The archive should contain exactly one .app
|
压缩包里应当只有一个 .app The archive should contain exactly one .app
|
||||||
压缩比 Compression ratio
|
压缩比 Compression ratio
|
||||||
原因 Reason
|
原因 Reason
|
||||||
原生 / 广播 Native / broadcast
|
|
||||||
原生 IP Native IP
|
原生 IP Native IP
|
||||||
去批准 Approve
|
去批准 Approve
|
||||||
去授权 Grant Access
|
去授权 Grant Access
|
||||||
双行圆点 Dots
|
双行圆点 Dots
|
||||||
双行文字 Stacked Text
|
双行文字 Stacked Text
|
||||||
双行箭头 Arrows
|
双行箭头 Arrows
|
||||||
反向解析 Reverse DNS
|
|
||||||
反馈问题 Report a Problem
|
反馈问题 Report a Problem
|
||||||
发现新版本 {} Version {} available
|
发现新版本 {} Version {} available
|
||||||
发现问题 Problems found
|
发现问题 Problems found
|
||||||
@@ -378,7 +367,6 @@ npm 缓存 npm Cache
|
|||||||
可用 {} {} available
|
可用 {} {} available
|
||||||
可用 {} / 共 {} {1} free of {2}
|
可用 {} / 共 {} {1} free of {2}
|
||||||
可用内存只剩 {},系统正在压缩和交换内存。关闭不用的应用可以缓解。 Only {} of memory is available and the system is compressing and swapping. Quitting unused apps will help.
|
可用内存只剩 {},系统正在压缩和交换内存。关闭不用的应用可以缓解。 Only {} of memory is available and the system is compressing and swapping. Quitting unused apps will help.
|
||||||
可用空间包含系统可以随时清除的缓存,与访达显示一致 Available space includes purgeable caches, matching Finder
|
|
||||||
可访问 Reachable
|
可访问 Reachable
|
||||||
右侧风扇 Right Fan
|
右侧风扇 Right Fan
|
||||||
右耳 Right
|
右耳 Right
|
||||||
@@ -536,6 +524,7 @@ npm 缓存 npm Cache
|
|||||||
开启 On
|
开启 On
|
||||||
开启 / 关闭“合盖不睡眠”(等同 pmset disablesleep) Turn lid-closed sleep prevention on or off (same as pmset disablesleep)
|
开启 / 关闭“合盖不睡眠”(等同 pmset disablesleep) Turn lid-closed sleep prevention on or off (same as pmset disablesleep)
|
||||||
开启后图标出现在菜单栏;按住 ⌘ 键拖动图标可以调整位置 Adds the icon to the menu bar; hold ⌘ and drag the icon to move it
|
开启后图标出现在菜单栏;按住 ⌘ 键拖动图标可以调整位置 Adds the icon to the menu bar; hold ⌘ and drag the icon to move it
|
||||||
|
开始 Start
|
||||||
开始分析 Analyze
|
开始分析 Analyze
|
||||||
开始探测 Run
|
开始探测 Run
|
||||||
开机以来 Since boot
|
开机以来 Since boot
|
||||||
@@ -582,7 +571,6 @@ npm 缓存 npm Cache
|
|||||||
打包版本、系统与辅助工具状态、主要设置、最近 3 天的运行日志和崩溃报告,反馈问题时附上;会去掉序列号、IP 与硬件地址 Bundles version, system and helper status, main settings, and the last 3 days of logs and crash reports to attach to a bug report. Serial numbers, IP and hardware addresses are removed.
|
打包版本、系统与辅助工具状态、主要设置、最近 3 天的运行日志和崩溃报告,反馈问题时附上;会去掉序列号、IP 与硬件地址 Bundles version, system and helper status, main settings, and the last 3 days of logs and crash reports to attach to a bug report. Serial numbers, IP and hardware addresses are removed.
|
||||||
打包生成的 .xcarchive,删除后无法重新符号化旧版本崩溃日志 .xcarchive bundles; without them old crash logs can't be symbolicated
|
打包生成的 .xcarchive,删除后无法重新符号化旧版本崩溃日志 .xcarchive bundles; without them old crash logs can't be symbolicated
|
||||||
打开 OpenStats Open OpenStats
|
打开 OpenStats Open OpenStats
|
||||||
打开 OpenStats 主窗口 Open the OpenStats Window
|
|
||||||
打开 SMC 失败 ({}) Failed to open SMC ({})
|
打开 SMC 失败 ({}) Failed to open SMC ({})
|
||||||
打开登录项设置 Open Login Items Settings
|
打开登录项设置 Open Login Items Settings
|
||||||
打开网络详情时向 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
|
打开网络详情时向 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
|
||||||
@@ -632,7 +620,6 @@ npm 缓存 npm Cache
|
|||||||
教育网 Education
|
教育网 Education
|
||||||
散热模式 Cooling
|
散热模式 Cooling
|
||||||
数据 Data
|
数据 Data
|
||||||
数据来源 Source
|
|
||||||
整体风格 Overall style
|
整体风格 Overall style
|
||||||
整体风格套用到所有项目。想让某个项目不一样,在下面“显示项目”里给它单独选一种,比如 CPU 用圆环、风扇用数字。 The overall style applies to every item. To make one item different, pick a style for it under Items below, say a ring for CPU and plain numbers for the fan.
|
整体风格套用到所有项目。想让某个项目不一样,在下面“显示项目”里给它单独选一种,比如 CPU 用圆环、风扇用数字。 The overall style applies to every item. To make one item different, pick a style for it under Items below, say a ring for CPU and plain numbers for the fan.
|
||||||
整机 System
|
整机 System
|
||||||
@@ -908,7 +895,6 @@ npm 缓存 npm Cache
|
|||||||
社交 Social
|
社交 Social
|
||||||
移到废纸篓 Move to Trash
|
移到废纸篓 Move to Trash
|
||||||
移到废纸篓? Move to the Trash?
|
移到废纸篓? Move to the Trash?
|
||||||
移动网络 Mobile
|
|
||||||
移动网络 IP Mobile IP
|
移动网络 IP Mobile IP
|
||||||
移动运营商 Mobile carrier
|
移动运营商 Mobile carrier
|
||||||
空 empty
|
空 empty
|
||||||
@@ -972,7 +958,6 @@ npm 缓存 npm Cache
|
|||||||
网络断开 Network Down
|
网络断开 Network Down
|
||||||
网络测速 Network Speed Test
|
网络测速 Network Speed Test
|
||||||
网络环境变了,下面是之前的结果,正在重新检测。 The network changed. These are the previous results; checking again.
|
网络环境变了,下面是之前的结果,正在重新检测。 The network changed. These are the previous results; checking again.
|
||||||
网络类型 Network type
|
|
||||||
网络详情打开时每秒 ping 一次,通了是绿格、不通是红格,显示最近 60 次 Pings once a second while network details are open: green when reachable, red when not, showing the last 60
|
网络详情打开时每秒 ping 一次,通了是绿格、不通是红格,显示最近 60 次 Pings once a second while network details are open: green when reachable, red when not, showing the last 60
|
||||||
网络运营方 Network operator
|
网络运营方 Network operator
|
||||||
网络连接中断超过 20 秒,恢复后再提示一次 Network has been down for over 20 seconds; notifies again when it's back
|
网络连接中断超过 20 秒,恢复后再提示一次 Network has been down for over 20 seconds; notifies again when it's back
|
||||||
@@ -1046,7 +1031,6 @@ npm 缓存 npm Cache
|
|||||||
超时 Timed out
|
超时 Timed out
|
||||||
超级核 Super
|
超级核 Super
|
||||||
超过 1 天未更新的 .crdownload / .part / .download .crdownload / .part / .download files untouched for over a day
|
超过 1 天未更新的 .crdownload / .part / .download .crdownload / .part / .download files untouched for over a day
|
||||||
跟随整体 Follow global
|
|
||||||
跟随整体({}) Follow overall ({})
|
跟随整体({}) Follow overall ({})
|
||||||
跟随系统 System
|
跟随系统 System
|
||||||
路径不可读 Path not readable
|
路径不可读 Path not readable
|
||||||
@@ -1113,7 +1097,6 @@ npm 缓存 npm Cache
|
|||||||
重新分析 Analyze again
|
重新分析 Analyze again
|
||||||
重新安装 Reinstall
|
重新安装 Reinstall
|
||||||
重新扫描 Rescan
|
重新扫描 Rescan
|
||||||
重新查询公网 IP Look Up Public IP Again
|
|
||||||
重新检测 Check Again
|
重新检测 Check Again
|
||||||
重新生成 Regenerate
|
重新生成 Regenerate
|
||||||
重新读取蓝牙设备电量 Read Bluetooth device batteries again
|
重新读取蓝牙设备电量 Read Bluetooth device batteries again
|
||||||
@@ -1143,7 +1126,6 @@ npm 缓存 npm Cache
|
|||||||
风扇模式 Fan mode
|
风扇模式 Fan mode
|
||||||
风扇设置 Fan Settings
|
风扇设置 Fan Settings
|
||||||
风扇转速 Fan Speed
|
风扇转速 Fan Speed
|
||||||
风格 Style
|
|
||||||
风险标记 Risk flags
|
风险标记 Risk flags
|
||||||
风险评分 Risk score
|
风险评分 Risk score
|
||||||
饼图 Pie
|
饼图 Pie
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ public enum PublicAddressLookup {
|
|||||||
struct Geo: Decodable {
|
struct Geo: Decodable {
|
||||||
let countryCode: String?
|
let countryCode: String?
|
||||||
let country: String?
|
let country: String?
|
||||||
let countryEn: String?
|
|
||||||
let region: String?
|
let region: String?
|
||||||
let city: String?
|
let city: String?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ struct BarHistoryChart: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 图表里 25% / 50% / 75% 三条淡淡的参考线
|
||||||
|
enum ChartGrid {
|
||||||
|
static func draw(in context: GraphicsContext, size: CGSize) {
|
||||||
|
let usable = size.height - DS.Size.chartLine * 2
|
||||||
|
for fraction in [0.25, 0.5, 0.75] {
|
||||||
|
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
|
||||||
|
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
|
||||||
|
with: .color(DS.Palette.border))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 历史折线(可带实色半透明填充)
|
/// 历史折线(可带实色半透明填充)
|
||||||
struct LineHistoryChart: View {
|
struct LineHistoryChart: View {
|
||||||
let values: [Double]
|
let values: [Double]
|
||||||
@@ -55,14 +67,7 @@ struct LineHistoryChart: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Canvas { context, size in
|
Canvas { context, size in
|
||||||
if grid {
|
if grid { ChartGrid.draw(in: context, size: size) }
|
||||||
let usable = size.height - DS.Size.chartLine * 2
|
|
||||||
for fraction in [0.25, 0.5, 0.75] {
|
|
||||||
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
|
|
||||||
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
|
|
||||||
with: .color(DS.Palette.border))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let line = LineHistoryChart.path(values: values, capacity: capacity, maxValue: maxValue, in: size)
|
let line = LineHistoryChart.path(values: values, capacity: capacity, maxValue: maxValue, in: size)
|
||||||
guard let line else { return }
|
guard let line else { return }
|
||||||
if filled {
|
if filled {
|
||||||
@@ -112,14 +117,7 @@ struct TimedLineChart: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Canvas { context, size in
|
Canvas { context, size in
|
||||||
if grid {
|
if grid { ChartGrid.draw(in: context, size: size) }
|
||||||
let usable = size.height - DS.Size.chartLine * 2
|
|
||||||
for fraction in [0.25, 0.5, 0.75] {
|
|
||||||
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
|
|
||||||
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
|
|
||||||
with: .color(DS.Palette.border))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for segment in Self.segments(points, duration: duration, end: end, in: size) {
|
for segment in Self.segments(points, duration: duration, end: end, in: size) {
|
||||||
guard let first = segment.first, let last = segment.last else { continue }
|
guard let first = segment.first, let last = segment.last else { continue }
|
||||||
var line = Path()
|
var line = Path()
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import Foundation
|
||||||
|
import Localization
|
||||||
|
|
||||||
|
/// 归属地与 IP 类型的文字:网络弹窗与出口窗口共用
|
||||||
|
enum GeoText {
|
||||||
|
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
|
||||||
|
static func location(countryCode: String, region: String?, regionEnglish: String?, city: String?, cityEnglish: String?) -> String {
|
||||||
|
let country = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: countryCode) ?? countryCode
|
||||||
|
let region = L10n.isEnglish ? (regionEnglish ?? region) : region
|
||||||
|
let city = L10n.isEnglish ? (cityEnglish ?? city) : city
|
||||||
|
var parts = [country]
|
||||||
|
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
|
||||||
|
return parts.joined(separator: " · ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 数据源给的 IP 类型(residential ip、datacenter ip……)对应的徽章;不认识的返回 nil
|
||||||
|
static func ipTypeBadge(_ raw: String) -> (icon: String, text: String, tone: TagBadge.Tone)? {
|
||||||
|
let type = raw.lowercased()
|
||||||
|
if type.contains("residential") { return ("house.fill", tr("住宅 IP"), .success) }
|
||||||
|
if type.contains("mobile") { return ("iphone.radiowaves.left.and.right", tr("移动网络 IP"), .success) }
|
||||||
|
if type.contains("business") { return ("building.2.fill", tr("企业 IP"), .success) }
|
||||||
|
if type.contains("idc") || type.contains("datacenter") || type.contains("hosting") { return ("server.rack", tr("机房 IP"), .warning) }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -306,7 +306,7 @@ private struct ExitNode: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
}
|
}
|
||||||
if let type = geo?.ipType.flatMap(EgressText.ipTypeBadge) {
|
if let type = geo?.ipType.flatMap(GeoText.ipTypeBadge) {
|
||||||
TagBadge(text: type.text, tone: type.tone, compact: true)
|
TagBadge(text: type.text, tone: type.tone, compact: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,24 +744,11 @@ private enum EgressText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ipTypeBadge(_ raw: String) -> (text: String, tone: TagBadge.Tone)? {
|
|
||||||
let type = raw.lowercased()
|
|
||||||
if type.contains("residential") { return (tr("住宅 IP"), .success) }
|
|
||||||
if type.contains("mobile") { return (tr("移动网络 IP"), .success) }
|
|
||||||
if type.contains("business") { return (tr("企业 IP"), .success) }
|
|
||||||
if type.contains("idc") || type.contains("datacenter") || type.contains("hosting") { return (tr("机房 IP"), .warning) }
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
|
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
|
||||||
static func location(_ geo: EgressGeo?, fallback country: String?) -> String {
|
static func location(_ geo: EgressGeo?, fallback country: String?) -> String {
|
||||||
guard let code = geo?.countryCode ?? country else { return "" }
|
guard let code = geo?.countryCode ?? country else { return "" }
|
||||||
let name = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: code) ?? code
|
return GeoText.location(countryCode: code, region: geo?.region, regionEnglish: geo?.regionEnglish,
|
||||||
let region = L10n.isEnglish ? (geo?.regionEnglish ?? geo?.region) : geo?.region
|
city: geo?.city, cityEnglish: geo?.cityEnglish)
|
||||||
let city = L10n.isEnglish ? (geo?.cityEnglish ?? geo?.city) : geo?.city
|
|
||||||
var parts = [name]
|
|
||||||
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
|
|
||||||
return parts.joined(separator: " · ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func checkedAt(_ date: Date, now: Date) -> String {
|
static func checkedAt(_ date: Date, now: Date) -> String {
|
||||||
|
|||||||
@@ -244,12 +244,8 @@ private struct AddressSection: View {
|
|||||||
|
|
||||||
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
|
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
|
||||||
private func location(_ addresses: PublicAddresses, code: String) -> String {
|
private func location(_ addresses: PublicAddresses, code: String) -> String {
|
||||||
let country = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: code) ?? code
|
GeoText.location(countryCode: code, region: addresses.region, regionEnglish: addresses.regionEnglish,
|
||||||
let region = L10n.isEnglish ? (addresses.regionEnglish ?? addresses.region) : addresses.region
|
city: addresses.city, cityEnglish: addresses.cityEnglish)
|
||||||
let city = L10n.isEnglish ? (addresses.cityEnglish ?? addresses.city) : addresses.city
|
|
||||||
var parts: [String] = [country]
|
|
||||||
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
|
|
||||||
return parts.joined(separator: " · ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 标题行:IP 地址 ……… [IPv4 | IPv6] ⟳;数据来源的字标只放在下面的纯净度区块里
|
/// 标题行:IP 地址 ……… [IPv4 | IPv6] ⟳;数据来源的字标只放在下面的纯净度区块里
|
||||||
@@ -292,13 +288,11 @@ private struct AddressSection: View {
|
|||||||
badges.append(isNative ? Badge(icon: "checkmark.shield.fill", text: tr("原生 IP"), tone: .success)
|
badges.append(isNative ? Badge(icon: "checkmark.shield.fill", text: tr("原生 IP"), tone: .success)
|
||||||
: Badge(icon: "antenna.radiowaves.left.and.right", text: tr("广播 IP"), tone: .warning))
|
: Badge(icon: "antenna.radiowaves.left.and.right", text: tr("广播 IP"), tone: .warning))
|
||||||
}
|
}
|
||||||
if let ipType = addresses.ipType?.lowercased() {
|
if let ipType = addresses.ipType {
|
||||||
switch ipType {
|
if let known = GeoText.ipTypeBadge(ipType) {
|
||||||
case "residential ip": badges.append(Badge(icon: "house.fill", text: tr("住宅 IP"), tone: .success))
|
badges.append(Badge(icon: known.icon, text: known.text, tone: known.tone))
|
||||||
case "datacenter ip", "hosting ip": badges.append(Badge(icon: "server.rack", text: tr("机房 IP"), tone: .warning))
|
} else {
|
||||||
case "mobile ip": badges.append(Badge(icon: "iphone.radiowaves.left.and.right", text: tr("移动网络 IP"), tone: .success))
|
badges.append(Badge(icon: "questionmark.circle", text: ipType, tone: .neutral))
|
||||||
case "business ip": badges.append(Badge(icon: "building.2.fill", text: tr("企业 IP"), tone: .success))
|
|
||||||
default: badges.append(Badge(icon: "questionmark.circle", text: addresses.ipType ?? "", tone: .neutral))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let asnType = addresses.asnType {
|
if let asnType = addresses.asnType {
|
||||||
@@ -314,28 +308,6 @@ private struct AddressSection: View {
|
|||||||
}
|
}
|
||||||
return badges
|
return badges
|
||||||
}
|
}
|
||||||
|
|
||||||
static func networkTypeLabel(_ raw: String) -> String {
|
|
||||||
switch raw.lowercased() {
|
|
||||||
case "residential", "isp": tr("住宅宽带")
|
|
||||||
case "business": tr("企业")
|
|
||||||
case "hosting", "datacenter", "data center": tr("机房")
|
|
||||||
case "mobile", "cellular": tr("移动网络")
|
|
||||||
case "education": tr("教育网")
|
|
||||||
case "government": tr("政府")
|
|
||||||
default: raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ipTypeLabel(_ raw: String) -> String {
|
|
||||||
switch raw.lowercased() {
|
|
||||||
case "residential ip": tr("住宅 IP")
|
|
||||||
case "datacenter ip", "hosting ip": tr("机房 IP")
|
|
||||||
case "mobile ip": tr("移动网络 IP")
|
|
||||||
case "business ip": tr("企业 IP")
|
|
||||||
default: raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - IP 纯净度
|
// MARK: - IP 纯净度
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ public enum MenuBarItem: String, CaseIterable, Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 可以单独指定风格的指标(网速有自己的样式)
|
|
||||||
var supportsStyleOverride: Bool { self != .network }
|
|
||||||
|
|
||||||
var symbol: String {
|
var symbol: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .cpu: "cpu"
|
case .cpu: "cpu"
|
||||||
|
|||||||
@@ -140,10 +140,6 @@ public final class HelperClient {
|
|||||||
await call { proxy, reply in proxy.setFanTarget(fan: fan, rpm: rpm, reply: reply) }
|
await call { proxy, reply in proxy.setFanTarget(fan: fan, rpm: rpm, reply: reply) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFanAutomatic(fan: Int) async -> String? {
|
|
||||||
await call { proxy, reply in proxy.setFanAutomatic(fan: fan, reply: reply) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetAllFans() async -> String? {
|
func resetAllFans() async -> String? {
|
||||||
await call { proxy, reply in proxy.resetAllFans(reply: reply) }
|
await call { proxy, reply in proxy.resetAllFans(reply: reply) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ public final class NetworkController {
|
|||||||
@ObservationIgnored private let settings: AppSettings
|
@ObservationIgnored private let settings: AppSettings
|
||||||
@ObservationIgnored private var probeTask: Task<Void, Never>?
|
@ObservationIgnored private var probeTask: Task<Void, Never>?
|
||||||
@ObservationIgnored private var detailTask: Task<Void, Never>?
|
@ObservationIgnored private var detailTask: Task<Void, Never>?
|
||||||
@ObservationIgnored private var probedAddress: String?
|
|
||||||
@ObservationIgnored private var lastPublicLookup: (date: Date, localIPv4: [String])?
|
@ObservationIgnored private var lastPublicLookup: (date: Date, localIPv4: [String])?
|
||||||
@ObservationIgnored private var isPaused = false
|
@ObservationIgnored private var isPaused = false
|
||||||
@ObservationIgnored private var wantsDetail = false
|
@ObservationIgnored private var wantsDetail = false
|
||||||
|
|||||||
@@ -155,7 +155,6 @@
|
|||||||
}
|
}
|
||||||
.mb-drop a:hover { background: var(--color-primary); color: #fff; }
|
.mb-drop a:hover { background: var(--color-primary); color: #fff; }
|
||||||
.mb-drop hr { border: 0; border-top: 1px solid var(--drop-border); margin: var(--space-1) var(--space-2); }
|
.mb-drop hr { border: 0; border-top: 1px solid var(--drop-border); margin: var(--space-1) var(--space-2); }
|
||||||
.mb-drop__ext { opacity: 0.6; }
|
|
||||||
|
|
||||||
.menubar__right { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }
|
.menubar__right { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }
|
||||||
.menubar__clock { font-variant-numeric: tabular-nums; }
|
.menubar__clock { font-variant-numeric: tabular-nums; }
|
||||||
|
|||||||
Reference in new issue
Block a user