Move connection policy out of ProAccess, keep terminal uploads with their session, and share question interaction and highlight editing operations. Consolidate CPU usage and settings snapshot handling, and remove obsolete terminal selection, theme helpers, and hard-coded latest-schema assertions. Fix draining connection-pool recovery and stale pane upload bindings. Keep screenshot matrices opt-in and retain behavioral migration coverage. Validation: 2468 macOS tests passed serially, 31 iOS tests passed, and both SSH forwarding regressions passed against the local OpenSSH fixture. Both app builds, Swift formatting, AppStrings, and Chinese localization checks passed. Parallel test runs still exhibit intermittent timeouts.
117 lines
4.8 KiB
Swift
117 lines
4.8 KiB
Swift
import SwiftUI
|
|
import TermindDomain
|
|
|
|
struct MetricsCPUContent: View {
|
|
let point: MetricsPoint?
|
|
|
|
var body: some View {
|
|
HStack(alignment: .center, spacing: 10) {
|
|
Text(MetricsFormat.percent(point?.cpu))
|
|
.font(.system(size: 30, weight: .semibold, design: .rounded))
|
|
.monospacedDigit().lineLimit(1).minimumScaleFactor(0.7)
|
|
.frame(width: 104, height: 40, alignment: .leading)
|
|
cpuLegend
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
}
|
|
cpuBars
|
|
HStack {
|
|
MetricsFigure("Cores", value: point?.coreCount.map(String.init) ?? "—")
|
|
MetricsFigure("Idle", value: MetricsFormat.percent(point?.cpuUsage.idle))
|
|
MetricsFigure("Uptime", value: uptime)
|
|
}
|
|
LabeledContent(
|
|
"Load Average",
|
|
value: point?.load.map { $0.formatted(.number.precision(.fractionLength(2))) }.joined(
|
|
separator: " / ") ?? "—"
|
|
)
|
|
.font(.caption2).foregroundStyle(.secondary)
|
|
}
|
|
|
|
private var cpuLegend: some View {
|
|
ViewThatFits(in: .horizontal) {
|
|
HStack(spacing: 12) {
|
|
legend("System", value: point?.cpuUsage.system, color: Color.red)
|
|
legend("User", value: point?.cpuUsage.user, color: Color.green)
|
|
legend("I/O Wait", value: point?.cpuUsage.wait, color: .purple)
|
|
stealLegend
|
|
}
|
|
.fixedSize(horizontal: true, vertical: false)
|
|
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 8) {
|
|
GridRow {
|
|
legend("System", value: point?.cpuUsage.system, color: Color.red)
|
|
legend("User", value: point?.cpuUsage.user, color: Color.green)
|
|
}
|
|
GridRow {
|
|
legend("I/O Wait", value: point?.cpuUsage.wait, color: .purple)
|
|
stealLegend
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var stealLegend: some View {
|
|
legend("Steal", value: point?.cpuUsage.steal, color: Color.orange)
|
|
.help("Steal: time a virtual CPU is ready to run but waiting for the hypervisor.")
|
|
}
|
|
|
|
private func legend(_ title: LocalizedStringResource, value: Double?, color: Color) -> some View {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
HStack(spacing: 3) {
|
|
Rectangle().fill(color).frame(width: 3, height: 6)
|
|
Text(title).lineLimit(1)
|
|
}
|
|
.font(.system(size: 8)).foregroundStyle(.secondary)
|
|
Text(MetricsFormat.percent(value)).font(.system(size: 10, weight: .semibold, design: .monospaced))
|
|
}
|
|
// Reserve enough room for 100.0% so sampling never shifts the columns.
|
|
.frame(minWidth: 40, maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
|
|
private var cpuBars: some View {
|
|
Group {
|
|
if let cores = point?.cpuCores, !cores.isEmpty {
|
|
VStack(spacing: 3) {
|
|
ForEach(cores) { cpu in
|
|
cpuBar(cpu.usage)
|
|
.accessibilityElement(children: .ignore)
|
|
.accessibilityLabel(Text(AppStrings.Metrics.cpuCore(cpu.id)))
|
|
.accessibilityValue(MetricsFormat.percent(cpu.usage.utilization))
|
|
}
|
|
}
|
|
} else {
|
|
cpuBar(point?.cpuUsage ?? MetricsCPUUsage())
|
|
.accessibilityLabel(Text(AppStrings.Metrics.cpuTimeBreakdown))
|
|
.accessibilityValue(MetricsFormat.percent(point?.cpu))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func cpuBar(_ cpu: MetricsCPUUsage) -> some View {
|
|
HStack(spacing: 2) {
|
|
ForEach(0..<48, id: \.self) { index in
|
|
RoundedRectangle(cornerRadius: 1)
|
|
.fill(cpuColor(at: Double(index) / 48 * 100, cpu: cpu))
|
|
}
|
|
}.frame(height: 8)
|
|
}
|
|
|
|
private func cpuColor(at position: Double, cpu: MetricsCPUUsage) -> Color {
|
|
guard cpu.utilization != nil else { return Color.secondary.opacity(0.2) }
|
|
var boundary = cpu.system ?? 0
|
|
if position < boundary { return Color.red }
|
|
boundary += cpu.user ?? 0
|
|
if position < boundary { return Color.green }
|
|
boundary += cpu.wait ?? 0
|
|
if position < boundary { return .purple }
|
|
boundary += cpu.steal ?? 0
|
|
if position < boundary { return Color.orange }
|
|
return Color.secondary.opacity(0.2)
|
|
}
|
|
|
|
private var uptime: String {
|
|
guard let seconds = point?.uptime, seconds >= 0, seconds < Double(Int64.max) else { return "—" }
|
|
return Duration.seconds(seconds).formatted(
|
|
.units(allowed: [.days, .hours, .minutes], width: .abbreviated, maximumUnitCount: 2))
|
|
}
|
|
}
|