feat(instances): show OS version
This commit is contained in:
@@ -67,6 +67,9 @@ struct InstancesSettings: View {
|
||||
if let version = inst.version {
|
||||
self.label(icon: "shippingbox", text: version)
|
||||
}
|
||||
if let platform = inst.platform, let prettyPlatform = self.prettyPlatform(platform) {
|
||||
self.label(icon: self.platformIcon(platform), text: prettyPlatform)
|
||||
}
|
||||
self.label(icon: "clock", text: inst.lastInputDescription)
|
||||
if let mode = inst.mode { self.label(icon: "network", text: mode) }
|
||||
if let reason = inst.reason, !reason.isEmpty {
|
||||
@@ -94,6 +97,52 @@ struct InstancesSettings: View {
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
private func platformIcon(_ raw: String) -> String {
|
||||
let (prefix, _) = self.parsePlatform(raw)
|
||||
switch prefix {
|
||||
case "macos":
|
||||
return "laptopcomputer"
|
||||
case "ios":
|
||||
return "iphone"
|
||||
case "ipados":
|
||||
return "ipad"
|
||||
case "tvos":
|
||||
return "appletv"
|
||||
case "watchos":
|
||||
return "applewatch"
|
||||
default:
|
||||
return "cpu"
|
||||
}
|
||||
}
|
||||
|
||||
private func prettyPlatform(_ raw: String) -> String? {
|
||||
let (prefix, version) = self.parsePlatform(raw)
|
||||
if prefix.isEmpty { return nil }
|
||||
let name: String = switch prefix {
|
||||
case "macos": "macOS"
|
||||
case "ios": "iOS"
|
||||
case "ipados": "iPadOS"
|
||||
case "tvos": "tvOS"
|
||||
case "watchos": "watchOS"
|
||||
default: prefix.prefix(1).uppercased() + prefix.dropFirst()
|
||||
}
|
||||
guard let version, !version.isEmpty else { return name }
|
||||
let parts = version.split(separator: ".").map(String.init)
|
||||
if parts.count >= 2 {
|
||||
return "\(name) \(parts[0]).\(parts[1])"
|
||||
}
|
||||
return "\(name) \(version)"
|
||||
}
|
||||
|
||||
private func parsePlatform(_ raw: String) -> (prefix: String, version: String?) {
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.isEmpty { return ("", nil) }
|
||||
let parts = trimmed.split(whereSeparator: { $0 == " " || $0 == "\t" }).map(String.init)
|
||||
let prefix = parts.first?.lowercased() ?? ""
|
||||
let versionToken = parts.dropFirst().first
|
||||
return (prefix, versionToken)
|
||||
}
|
||||
|
||||
private func presenceUpdateSourceText(_ reason: String) -> String {
|
||||
let trimmed = reason.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
switch trimmed {
|
||||
|
||||
@@ -8,6 +8,7 @@ struct InstanceInfo: Identifiable, Codable {
|
||||
let host: String?
|
||||
let ip: String?
|
||||
let version: String?
|
||||
let platform: String?
|
||||
let lastInputSeconds: Int?
|
||||
let mode: String?
|
||||
let reason: String?
|
||||
@@ -145,6 +146,8 @@ final class InstancesStore: ObservableObject {
|
||||
let host = Host.current().localizedName ?? "this-mac"
|
||||
let ip = Self.primaryIPv4Address()
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let platform = "macos \(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
|
||||
let text = "Local node: \(host)\(ip.map { " (\($0))" } ?? "") · app \(version ?? "dev")"
|
||||
let ts = Date().timeIntervalSince1970 * 1000
|
||||
return InstanceInfo(
|
||||
@@ -152,6 +155,7 @@ final class InstancesStore: ObservableObject {
|
||||
host: host,
|
||||
ip: ip,
|
||||
version: version,
|
||||
platform: platform,
|
||||
lastInputSeconds: Self.lastInputSeconds(),
|
||||
mode: "local",
|
||||
reason: reason,
|
||||
@@ -228,6 +232,7 @@ final class InstancesStore: ObservableObject {
|
||||
host: "gateway (health)",
|
||||
ip: nil,
|
||||
version: nil,
|
||||
platform: nil,
|
||||
lastInputSeconds: nil,
|
||||
mode: "health",
|
||||
reason: "health probe",
|
||||
@@ -276,6 +281,7 @@ final class InstancesStore: ObservableObject {
|
||||
host: entry.host,
|
||||
ip: entry.ip,
|
||||
version: entry.version,
|
||||
platform: entry.platform,
|
||||
lastInputSeconds: entry.lastinputseconds,
|
||||
mode: entry.mode,
|
||||
reason: entry.reason,
|
||||
@@ -299,6 +305,7 @@ extension InstancesStore {
|
||||
host: "steipete-mac",
|
||||
ip: "10.0.0.12",
|
||||
version: "1.2.3",
|
||||
platform: "macos 26.2.0",
|
||||
lastInputSeconds: 12,
|
||||
mode: "local",
|
||||
reason: "preview",
|
||||
@@ -309,6 +316,7 @@ extension InstancesStore {
|
||||
host: "gateway",
|
||||
ip: "100.64.0.2",
|
||||
version: "1.2.3",
|
||||
platform: "linux 6.6.0",
|
||||
lastInputSeconds: 45,
|
||||
mode: "remote",
|
||||
reason: "preview",
|
||||
|
||||
@@ -167,6 +167,7 @@ public struct PresenceEntry: Codable {
|
||||
public let host: String?
|
||||
public let ip: String?
|
||||
public let version: String?
|
||||
public let platform: String?
|
||||
public let mode: String?
|
||||
public let lastinputseconds: Int?
|
||||
public let reason: String?
|
||||
@@ -179,6 +180,7 @@ public struct PresenceEntry: Codable {
|
||||
host: String?,
|
||||
ip: String?,
|
||||
version: String?,
|
||||
platform: String?,
|
||||
mode: String?,
|
||||
lastinputseconds: Int?,
|
||||
reason: String?,
|
||||
@@ -190,6 +192,7 @@ public struct PresenceEntry: Codable {
|
||||
self.host = host
|
||||
self.ip = ip
|
||||
self.version = version
|
||||
self.platform = platform
|
||||
self.mode = mode
|
||||
self.lastinputseconds = lastinputseconds
|
||||
self.reason = reason
|
||||
@@ -202,6 +205,7 @@ public struct PresenceEntry: Codable {
|
||||
case host
|
||||
case ip
|
||||
case version
|
||||
case platform
|
||||
case mode
|
||||
case lastinputseconds = "lastInputSeconds"
|
||||
case reason
|
||||
|
||||
@@ -7,6 +7,7 @@ export const PresenceEntrySchema = Type.Object(
|
||||
host: Type.Optional(NonEmptyString),
|
||||
ip: Type.Optional(NonEmptyString),
|
||||
version: Type.Optional(NonEmptyString),
|
||||
platform: Type.Optional(NonEmptyString),
|
||||
mode: Type.Optional(NonEmptyString),
|
||||
lastInputSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||
reason: Type.Optional(NonEmptyString),
|
||||
|
||||
@@ -819,11 +819,13 @@ export async function startGatewayServer(
|
||||
const host = node.displayName?.trim() || node.nodeId;
|
||||
const ip = node.remoteIp?.trim();
|
||||
const version = node.version?.trim() || "unknown";
|
||||
const platform = node.platform?.trim() || undefined;
|
||||
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-connected`;
|
||||
upsertPresence(node.nodeId, {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
mode: "remote",
|
||||
reason: "iris-connected",
|
||||
lastInputSeconds: 0,
|
||||
@@ -847,11 +849,13 @@ export async function startGatewayServer(
|
||||
const host = node.displayName?.trim() || node.nodeId;
|
||||
const ip = node.remoteIp?.trim();
|
||||
const version = node.version?.trim() || "unknown";
|
||||
const platform = node.platform?.trim() || undefined;
|
||||
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-disconnected`;
|
||||
upsertPresence(node.nodeId, {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
mode: "remote",
|
||||
reason: "iris-disconnected",
|
||||
lastInputSeconds: 0,
|
||||
@@ -1206,6 +1210,7 @@ export async function startGatewayServer(
|
||||
host: connectParams.client.name || os.hostname(),
|
||||
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
version: connectParams.client.version,
|
||||
platform: connectParams.client.platform,
|
||||
mode: connectParams.client.mode,
|
||||
instanceId: connectParams.client.instanceId,
|
||||
reason: "connect",
|
||||
@@ -1824,6 +1829,8 @@ export async function startGatewayServer(
|
||||
typeof params.mode === "string" ? params.mode : undefined;
|
||||
const version =
|
||||
typeof params.version === "string" ? params.version : undefined;
|
||||
const platform =
|
||||
typeof params.platform === "string" ? params.platform : undefined;
|
||||
const lastInputSeconds =
|
||||
typeof params.lastInputSeconds === "number" &&
|
||||
Number.isFinite(params.lastInputSeconds)
|
||||
@@ -1843,6 +1850,7 @@ export async function startGatewayServer(
|
||||
ip,
|
||||
mode,
|
||||
version,
|
||||
platform,
|
||||
lastInputSeconds,
|
||||
reason,
|
||||
tags,
|
||||
|
||||
@@ -4,6 +4,7 @@ export type SystemPresence = {
|
||||
host?: string;
|
||||
ip?: string;
|
||||
version?: string;
|
||||
platform?: string;
|
||||
lastInputSeconds?: number;
|
||||
mode?: string;
|
||||
reason?: string;
|
||||
@@ -46,11 +47,19 @@ function initSelfPresence() {
|
||||
const ip = resolvePrimaryIPv4() ?? undefined;
|
||||
const version =
|
||||
process.env.CLAWDIS_VERSION ?? process.env.npm_package_version ?? "unknown";
|
||||
const platform = (() => {
|
||||
const p = os.platform();
|
||||
const rel = os.release();
|
||||
if (p === "darwin") return `macos ${rel}`;
|
||||
if (p === "win32") return `windows ${rel}`;
|
||||
return `${p} ${rel}`;
|
||||
})();
|
||||
const text = `Gateway: ${host}${ip ? ` (${ip})` : ""} · app ${version} · mode gateway · reason self`;
|
||||
const selfEntry: SystemPresence = {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
mode: "gateway",
|
||||
reason: "self",
|
||||
text,
|
||||
@@ -113,6 +122,7 @@ type SystemPresencePayload = {
|
||||
host?: string;
|
||||
ip?: string;
|
||||
version?: string;
|
||||
platform?: string;
|
||||
lastInputSeconds?: number;
|
||||
mode?: string;
|
||||
reason?: string;
|
||||
@@ -136,6 +146,7 @@ export function updateSystemPresence(payload: SystemPresencePayload) {
|
||||
host: payload.host ?? parsed.host ?? existing.host,
|
||||
ip: payload.ip ?? parsed.ip ?? existing.ip,
|
||||
version: payload.version ?? parsed.version ?? existing.version,
|
||||
platform: payload.platform ?? existing.platform,
|
||||
mode: payload.mode ?? parsed.mode ?? existing.mode,
|
||||
lastInputSeconds:
|
||||
payload.lastInputSeconds ??
|
||||
|
||||
Reference in New Issue
Block a user