feat: added bootable disks to the Dashboard

This commit is contained in:
Serhiy Mytrovtsiy
2025-08-15 16:42:12 +02:00
parent c44bc7e750
commit 7d49169122
43 changed files with 184 additions and 6 deletions

View File

@@ -386,7 +386,7 @@ class WebSocketManager: NSObject {
var wsHost = Remote.host.absoluteString
wsHost = wsHost.replacingOccurrences(of: "https", with: "wss").replacingOccurrences(of: "http", with: "ws")
let url = URL(string: "\(wsHost)/remote?jwt=\(Remote.shared.auth.accessToken)&device_id=\(Remote.shared.id.uuidString)")!
let url = URL(string: "\(wsHost)/remote?jwt=\(Remote.shared.auth.accessToken)&machine_id=\(Remote.shared.id.uuidString)")!
self.webSocket = self.session?.webSocketTask(with: url)
self.webSocket?.resume()
@@ -434,12 +434,14 @@ class WebSocketManager: NSObject {
let model: String?
let modelID: String?
let os: OS
let arch: String?
}
struct Hardware: Codable {
let cpu: cpu_s?
let gpu: [gpu_s]?
let ram: [dimm_s]?
let disk: [disk_s]?
}
let details = Details(
@@ -453,12 +455,14 @@ class WebSocketManager: NSObject {
name: SystemKit.shared.device.os?.name,
version: SystemKit.shared.device.os?.version.getFullVersion(),
build: SystemKit.shared.device.os?.build
)
),
arch: SystemKit.shared.device.arch
),
hardware: Hardware(
cpu: SystemKit.shared.device.info.cpu,
gpu: SystemKit.shared.device.info.gpu,
ram: SystemKit.shared.device.info.ram?.dimms
ram: SystemKit.shared.device.info.ram?.dimms,
disk: SystemKit.shared.device.info.disk,
)
)
let jsonData = try? JSONEncoder().encode(details)

View File

@@ -133,14 +133,26 @@ public struct gpu_s: Codable {
public var frequencies: [Int32]? = nil
}
public struct disk_s: Codable {
public var id: String? = nil
public var name: String? = nil
public var size: Int64? = nil
}
public struct info_s {
public var cpu: cpu_s? = nil
public var ram: ram_s? = nil
public var gpu: [gpu_s]? = nil
public var disk: [disk_s]? = nil
}
public struct device_s {
public var model: model_s = model_s(name: localizedString("Unknown"), year: Calendar.current.component(.year, from: Date()), type: .unknown)
public var model: model_s = model_s(
name: localizedString("Unknown"),
year: Calendar.current.component(.year, from: Date()),
type: .unknown,
)
public var arch: String = "unknown"
public var serialNumber: String? = nil
public var bootDate: Date? = nil
@@ -167,6 +179,12 @@ public class SystemKit {
self.device.model = model
}
#if arch(x86_64)
self.device.arch = "x86_64"
#elseif arch(arm64)
self.device.arch = "arm64"
#endif
self.device.bootDate = self.bootDate()
let procInfo = ProcessInfo()
@@ -184,6 +202,7 @@ public class SystemKit {
self.device.info.cpu = self.getCPUInfo()
self.device.info.ram = self.getRamInfo()
self.device.info.gpu = self.getGPUInfo()
self.device.info.disk = self.getDiskInfo()
self.device.platform = self.getPlatform()
}
@@ -389,6 +408,104 @@ public class SystemKit {
return list
}
private func getDiskInfo() -> [disk_s]? {
var bootableDisks: [disk_s] = []
guard let output = process(path: "/usr/sbin/diskutil", arguments: ["list", "-plist"]) else {
return nil
}
do {
if let data = output.data(using: .utf8),
let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any],
let allDisksAndPartitions = plist["AllDisksAndPartitions"] as? [[String: Any]] {
for disk in allDisksAndPartitions {
if let partitions = disk["Partitions"] as? [[String: Any]] {
for partition in partitions {
if let bootable = partition["Bootable"] as? Bool, bootable {
var bootableDisk = disk_s()
if let id = partition["DiskUUID"] as? String {
bootableDisk.id = id
} else if let deviceIdentifier = partition["DeviceIdentifier"] as? String {
bootableDisk.id = "/dev/" + deviceIdentifier
}
if let volumeName = partition["VolumeName"] as? String {
bootableDisk.name = volumeName
}
if let size = partition["Size"] as? Int64 {
bootableDisk.size = size
}
if bootableDisk.id != nil {
bootableDisks.append(bootableDisk)
}
}
}
}
if let contentType = disk["Content"] as? String, contentType == "Apple_APFS_Container" || contentType == "Apple_CoreStorage" {
if let deviceIdentifier = disk["DeviceIdentifier"] as? String,
let infoOutput = process(path: "/usr/sbin/diskutil", arguments: ["info", "-plist", deviceIdentifier]),
let infoData = infoOutput.data(using: .utf8),
let info = try PropertyListSerialization.propertyList(from: infoData, options: [], format: nil) as? [String: Any] {
if let isBootDisk = info["BootableVolume"] as? Bool, isBootDisk {
var bootableDisk = disk_s()
if let id = info["DiskUUID"] as? String {
bootableDisk.id = id
} else {
bootableDisk.id = "/dev/" + deviceIdentifier
}
if let name = info["VolumeName"] as? String {
bootableDisk.name = name
} else if let name = disk["DeviceIdentifier"] as? String {
bootableDisk.name = name
}
if let size = disk["Size"] as? Int64 {
bootableDisk.size = size
} else if let total = info["TotalSize"] as? Int64 {
bootableDisk.size = total
}
if bootableDisk.id != nil {
bootableDisks.append(bootableDisk)
}
}
}
}
}
}
} catch {
print("Error parsing diskutil output: \(error)")
return nil
}
if bootableDisks.isEmpty {
if let startupDiskInfo = process(path: "/usr/sbin/diskutil", arguments: ["info", "-plist", "/"]) {
if let data = startupDiskInfo.data(using: .utf8),
let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] {
var bootDisk = disk_s()
if let id = plist["DiskUUID"] as? String {
bootDisk.id = id
} else if let deviceNode = plist["DeviceNode"] as? String {
bootDisk.id = deviceNode
}
if let volumeName = plist["VolumeName"] as? String {
bootDisk.name = volumeName
}
if let totalSize = plist["TotalSize"] as? Int64 {
bootDisk.size = totalSize
}
if bootDisk.id != nil {
bootableDisks.append(bootDisk)
}
}
}
}
return bootableDisks
}
private func getFrequencies(cpuName: String) -> ([Int32], [Int32])? {
var iterator = io_iterator_t()
let result = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMIODevice"), &iterator)

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 خيوط";
"Number of e-cores" = "%0 نوى كفاءة";
"Number of p-cores" = "%0 نوى أداء";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "تم تثبيت أحدث إصدار من الإحصاءات";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 нишки";
"Number of e-cores" = "%0 ефикасни ядра";
"Number of p-cores" = "%0 производителни ядра";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Имате най-новата версия на Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 fils";
"Number of e-cores" = "%0 nuclis d'eficiència";
"Number of p-cores" = "%0 nuclis de rendiment";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "L'última versió d'Stats està instal·lada";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 vláken";
"Number of e-cores" = "%0 efektivních jader";
"Number of p-cores" = "%0 výkonných jader";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Byla nainstalována nejnovější verze Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 tråde";
"Number of e-cores" = "%0 efficiency kerner";
"Number of p-cores" = "%0 performance kerner";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Den nyeste udgave af Stats er installeret";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 Threads";
"Number of e-cores" = "%0 Effizienz-Kerne";
"Number of p-cores" = "%0 Per­for­mance-Kerne";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Die neueste Version von Stats ist installiert";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 πυρήνες απόδοσης";
"Number of p-cores" = "%0 πυρήνες επίδοσης";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Η τελευταία έκδοση του Stats είναι εγκατεστημένη";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "The latest version of Stats is installed";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "The latest version of Stats is installed";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "The latest version of Stats is installed";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "Número de hilos";
"Number of e-cores" = "Número de núcleos de eficiencia";
"Number of p-cores" = "Número de núcleos de rendimiento";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "La última versión de Stats está instalada";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 lõimed";
"Number of e-cores" = "%0 tõhusatuumad";
"Number of p-cores" = "%0 jõudlustuumad";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Uusim versioon on installitud";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 ترد‌ها";
"Number of e-cores" = "%0 هسته‌های کم‌مصرف";
"Number of p-cores" = "%0 هسته‌های پرقدرت";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "آخرین نسخه‌ی Stats نصب شده است";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 säiettä";
"Number of e-cores" = "%0 e-ydintä";
"Number of p-cores" = "%0 p-ydintä";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Stats on päivitetty viimeisimpään versioon";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 cœurs à haute effica­cité éner­gétique";
"Number of p-cores" = "%0 cœurs de perfor­mance";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "La dernière version de Stats est installée";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 טרדים";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "מותקנת Stats הגרסא האחרונה של";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 थ्रेड्स";
"Number of e-cores" = "%0 दक्षता कोर";
"Number of p-cores" = "%0 प्रदर्शन कोर";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "आँकड़े का नवीनतम संस्करण स्थापित है";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "Broj komponenti procesa: %0";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Instalirana je najnovija verzija programa Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 szál";
"Number of e-cores" = "%0 energiatakarékos mag";
"Number of p-cores" = "%0 teljesítményre optimalizált mag";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "A Stats legújabb verziója van telepítve";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 thread";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Versi terbaru dari Stats telah dipasang";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 thread";
"Number of e-cores" = "%0 efficiency core";
"Number of p-cores" = "%0 performance core";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "L'ultima versione di Stats è installata";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 スレッド";
"Number of e-cores" = "%0 高効率コア";
"Number of p-cores" = "%0 高性能コア";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "最新の Stats がインストールされています";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 스레드";
"Number of e-cores" = "%0 효율 코어";
"Number of p-cores" = "%0 성능 코어";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "최신 버전의 Stats가 설치되어 있습니다";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 tråder";
"Number of e-cores" = "%0 effektivitetskjerner";
"Number of p-cores" = "%0 ytelseskjerner";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Du har den nyeste versjonen av Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "De laatste versie van Stats is geïnstalleerd.";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 wątków";
"Number of e-cores" = "%0 rdzeni energooszczędnych";
"Number of p-cores" = "%0 rdzeni wydajnościowych";
"Disks" = "Dyski";
// Update
"The latest version of Stats installed" = "Najnowsza wersja Stats zainstalowana";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 núcleos de eficiência";
"Number of p-cores" = "%0 núcleos de desempenho";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "A versão mais recente do Stats está instalada";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "A versão mais recente do Stats instalada";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 threads";
"Number of e-cores" = "%0 efficiency cores";
"Number of p-cores" = "%0 performance cores";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Cea mai recentă versiune de Stats instalată";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 потоков";
"Number of e-cores" = "%0 энергоэффективных ядер";
"Number of p-cores" = "%0 производительных ядер";
"Disks" = "Диски";
// Update
"The latest version of Stats installed" = "Установлена последняя версия";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 vlákien";
"Number of e-cores" = "%0 efektívnych jadier";
"Number of p-cores" = "%0 výkonných jadier";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Bola nainštalovaná najnovšia verzia Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 niti";
"Number of e-cores" = "%0 učinkovitih jeder";
"Number of p-cores" = "%0 performančnih jeder";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Nameščena je najnovejša različica programa Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 trådar";
"Number of e-cores" = "%0 effektivitetskärnor";
"Number of p-cores" = "%0 prestandakärnor";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Den senaste versionen av Stats är installerad";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 thread";
"Number of e-cores" = "%0 cores ประสิทธิภาพ";
"Number of p-cores" = "%0 cores ประสิทธิผล";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "เวอร์ชันล่าสุดของ Stats ได้รับการติดตั้งแล้ว";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 iş parçacığı";
"Number of e-cores" = "%0 verimlilik çekirdeği";
"Number of p-cores" = "%0 performans çekirdeği";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Stats'ın son sürümü kurulu";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 потоків";
"Number of e-cores" = "%0 енергоефективних ядер";
"Number of p-cores" = "%0 високопродуктивних ядер";
"Disks" = "Диски";
// Update
"The latest version of Stats installed" = "Встановлено останню версію";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 luồng";
"Number of e-cores" = "%0 nhân tiết kiệm";
"Number of p-cores" = "%0 nhân hiệu năng";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "Phiên bản Stats mới nhất đã được cài đặt";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 线程";
"Number of e-cores" = "%0 能效核心";
"Number of p-cores" = "%0 性能核心";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "已安装最新版 Stats";

View File

@@ -177,6 +177,7 @@
"Number of threads" = "%0 執行緒";
"Number of e-cores" = "%0 個節能核心";
"Number of p-cores" = "%0 個效能核心";
"Disks" = "Disks";
// Update
"The latest version of Stats installed" = "已安裝最新版本";

View File

@@ -141,6 +141,23 @@ class Dashboard: NSStackView {
}
return value
}
private var disksValue: String {
guard let disks = SystemKit.shared.device.info.disk else {
return localizedString("Unknown")
}
var value = ""
for i in 0..<disks.count {
var row = disks[i].name != nil ? disks[i].name! : localizedString("Unknown")
if let size = disks[i].size {
let value = ByteCountFormatter.string(fromByteCount: size, countStyle: .file)
row += " (\(value))"
}
value += "\(row)\(i == disks.count-1 ? "" : "\n")"
}
return value
}
private var uptimeValue: String {
let form = DateComponentsFormatter()
form.maximumUnitCount = 2
@@ -176,7 +193,8 @@ class Dashboard: NSStackView {
scrollView.stackView.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Processor"), "", component: textView(self.processorValue)),
PreferencesRow(localizedString("Memory"), component: textView(self.memoryValue)),
PreferencesRow(localizedString("Graphics"), component: textView(self.graphicsValue))
PreferencesRow(localizedString("Graphics"), component: textView(self.graphicsValue)),
PreferencesRow(localizedString("Disks"), component: textView(self.disksValue))
]))
scrollView.stackView.addArrangedSubview(PreferencesSection([

View File

@@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>2.11.49</string>
<key>CFBundleVersion</key>
<string>709</string>
<string>710</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>