Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
322
apps/macos/Sources/OpenClaw/InstancesStore.swift
Normal file
322
apps/macos/Sources/OpenClaw/InstancesStore.swift
Normal file
@@ -0,0 +1,322 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import Observation
|
||||
import OpenClawKit
|
||||
import OpenClawProtocol
|
||||
import OSLog
|
||||
|
||||
struct InstanceInfo: Identifiable, Codable {
|
||||
let id: String
|
||||
let host: String?
|
||||
let ip: String?
|
||||
let version: String?
|
||||
let platform: String?
|
||||
let deviceFamily: String?
|
||||
let modelIdentifier: String?
|
||||
let lastInputSeconds: Int?
|
||||
let mode: String?
|
||||
let reason: String?
|
||||
let text: String
|
||||
let ts: Double
|
||||
|
||||
var ageDescription: String {
|
||||
let date = Date(timeIntervalSince1970: ts / 1000)
|
||||
return age(from: date)
|
||||
}
|
||||
|
||||
var lastInputDescription: String {
|
||||
guard let secs = lastInputSeconds else { return "unknown" }
|
||||
return "\(secs)s ago"
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class InstancesStore {
|
||||
static let shared = InstancesStore()
|
||||
let isPreview: Bool
|
||||
|
||||
var instances: [InstanceInfo] = []
|
||||
var lastError: String?
|
||||
var statusMessage: String?
|
||||
var isLoading = false
|
||||
|
||||
private let logger = Logger(subsystem: "ai.openclaw", category: "instances")
|
||||
private var task: Task<Void, Never>?
|
||||
private let interval: TimeInterval = 30
|
||||
private var eventTask: Task<Void, Never>?
|
||||
private var startCount = 0
|
||||
private var lastPresenceById: [String: InstanceInfo] = [:]
|
||||
private var lastLoginNotifiedAtMs: [String: Double] = [:]
|
||||
|
||||
private struct PresenceEventPayload: Codable {
|
||||
let presence: [PresenceEntry]
|
||||
}
|
||||
|
||||
init(isPreview: Bool = false) {
|
||||
self.isPreview = isPreview
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard !self.isPreview else { return }
|
||||
self.startCount += 1
|
||||
guard self.startCount == 1 else { return }
|
||||
guard self.task == nil else { return }
|
||||
GatewayPushSubscription.restartTask(task: &self.eventTask) { [weak self] push in
|
||||
self?.handle(push: push)
|
||||
}
|
||||
SimpleTaskSupport.startDetachedLoop(task: &self.task, interval: self.interval) { [weak self] in
|
||||
await self?.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
guard !self.isPreview else { return }
|
||||
guard self.startCount > 0 else { return }
|
||||
self.startCount -= 1
|
||||
guard self.startCount == 0 else { return }
|
||||
self.task?.cancel()
|
||||
self.task = nil
|
||||
self.eventTask?.cancel()
|
||||
self.eventTask = nil
|
||||
}
|
||||
|
||||
private func handle(push: GatewayPush) {
|
||||
switch push {
|
||||
case let .event(evt) where evt.event == "presence":
|
||||
if let payload = evt.payload {
|
||||
self.handlePresenceEventPayload(payload)
|
||||
}
|
||||
case .seqGap:
|
||||
Task { await self.refresh() }
|
||||
case let .snapshot(hello):
|
||||
self.applyPresence(hello.snapshot.presence)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() async {
|
||||
if self.isLoading { return }
|
||||
self.statusMessage = nil
|
||||
self.isLoading = true
|
||||
defer { self.isLoading = false }
|
||||
do {
|
||||
PresenceReporter.shared.sendImmediate(reason: "instances-refresh")
|
||||
let data = try await ControlChannel.shared.request(method: "system-presence")
|
||||
self.lastPayload = data
|
||||
if data.isEmpty {
|
||||
self.logger.error("instances fetch returned empty payload")
|
||||
self.instances = [self.localFallbackInstance(reason: "no presence payload")]
|
||||
self.lastError = nil
|
||||
self.statusMessage = "No presence payload from gateway; showing local fallback + health probe."
|
||||
await self.probeHealthIfNeeded(reason: "no payload")
|
||||
return
|
||||
}
|
||||
let decoded = try JSONDecoder().decode([PresenceEntry].self, from: data)
|
||||
let withIDs = self.normalizePresence(decoded)
|
||||
if withIDs.isEmpty {
|
||||
self.instances = [self.localFallbackInstance(reason: "no presence entries")]
|
||||
self.lastError = nil
|
||||
self.statusMessage = "Presence list was empty; showing local fallback + health probe."
|
||||
await self.probeHealthIfNeeded(reason: "empty list")
|
||||
} else {
|
||||
self.instances = withIDs
|
||||
self.lastError = nil
|
||||
self.statusMessage = nil
|
||||
}
|
||||
} catch {
|
||||
self.logger.error(
|
||||
"""
|
||||
instances fetch failed: \(error.localizedDescription, privacy: .public) \
|
||||
len=\(self.lastPayload?.count ?? 0, privacy: .public) \
|
||||
utf8=\(self.snippet(self.lastPayload), privacy: .public)
|
||||
""")
|
||||
self.instances = [self.localFallbackInstance(reason: "presence decode failed")]
|
||||
self.lastError = nil
|
||||
self.statusMessage = "Presence data invalid; showing local fallback + health probe."
|
||||
await self.probeHealthIfNeeded(reason: "decode failed")
|
||||
}
|
||||
}
|
||||
|
||||
private func localFallbackInstance(reason: String) -> InstanceInfo {
|
||||
let host = Host.current().localizedName ?? "this-mac"
|
||||
let ip = SystemPresenceInfo.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(
|
||||
id: "local-\(host)",
|
||||
host: host,
|
||||
ip: ip,
|
||||
version: version,
|
||||
platform: platform,
|
||||
deviceFamily: "Mac",
|
||||
modelIdentifier: InstanceIdentity.modelIdentifier,
|
||||
lastInputSeconds: SystemPresenceInfo.lastInputSeconds(),
|
||||
mode: "local",
|
||||
reason: reason,
|
||||
text: text,
|
||||
ts: ts)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Keep the last raw payload for logging.
|
||||
private var lastPayload: Data?
|
||||
|
||||
private func snippet(_ data: Data?, limit: Int = 256) -> String {
|
||||
guard let data else { return "<none>" }
|
||||
if data.isEmpty { return "<empty>" }
|
||||
let prefix = data.prefix(limit)
|
||||
if let asString = String(data: prefix, encoding: .utf8) {
|
||||
return asString.replacingOccurrences(of: "\n", with: " ")
|
||||
}
|
||||
return "<\(data.count) bytes non-utf8>"
|
||||
}
|
||||
|
||||
private func probeHealthIfNeeded(reason: String? = nil) async {
|
||||
do {
|
||||
let data = try await ControlChannel.shared.health(timeout: 8)
|
||||
guard let snap = decodeHealthSnapshot(from: data) else { return }
|
||||
let linkId = snap.channelOrder?.first(where: {
|
||||
if let summary = snap.channels[$0] { return summary.linked != nil }
|
||||
return false
|
||||
}) ?? snap.channels.keys.first(where: {
|
||||
if let summary = snap.channels[$0] { return summary.linked != nil }
|
||||
return false
|
||||
})
|
||||
let linked = linkId.flatMap { snap.channels[$0]?.linked } ?? false
|
||||
let linkLabel =
|
||||
linkId.flatMap { snap.channelLabels?[$0] } ??
|
||||
linkId?.capitalized ??
|
||||
"channel"
|
||||
let entry = InstanceInfo(
|
||||
id: "health-\(snap.ts)",
|
||||
host: "gateway (health)",
|
||||
ip: nil,
|
||||
version: nil,
|
||||
platform: nil,
|
||||
deviceFamily: nil,
|
||||
modelIdentifier: nil,
|
||||
lastInputSeconds: nil,
|
||||
mode: "health",
|
||||
reason: "health probe",
|
||||
text: "Health ok · \(linkLabel) linked=\(linked)",
|
||||
ts: snap.ts)
|
||||
if !self.instances.contains(where: { $0.id == entry.id }) {
|
||||
self.instances.insert(entry, at: 0)
|
||||
}
|
||||
self.lastError = nil
|
||||
self.statusMessage =
|
||||
"Presence unavailable (\(reason ?? "refresh")); showing health probe + local fallback."
|
||||
} catch {
|
||||
self.logger.error("instances health probe failed: \(error.localizedDescription, privacy: .public)")
|
||||
if let reason {
|
||||
self.statusMessage =
|
||||
"Presence unavailable (\(reason)), health probe failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handlePresenceEventPayload(_ payload: OpenClawProtocol.AnyCodable) {
|
||||
do {
|
||||
let wrapper = try GatewayPayloadDecoding.decode(payload, as: PresenceEventPayload.self)
|
||||
self.applyPresence(wrapper.presence)
|
||||
} catch {
|
||||
self.logger.error("presence event decode failed: \(error.localizedDescription, privacy: .public)")
|
||||
self.lastError = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
private func normalizePresence(_ entries: [PresenceEntry]) -> [InstanceInfo] {
|
||||
entries.map { entry -> InstanceInfo in
|
||||
let key = entry.instanceid ?? entry.host ?? entry.ip ?? entry.text ?? "entry-\(entry.ts)"
|
||||
return InstanceInfo(
|
||||
id: key,
|
||||
host: entry.host,
|
||||
ip: entry.ip,
|
||||
version: entry.version,
|
||||
platform: entry.platform,
|
||||
deviceFamily: entry.devicefamily,
|
||||
modelIdentifier: entry.modelidentifier,
|
||||
lastInputSeconds: entry.lastinputseconds,
|
||||
mode: entry.mode,
|
||||
reason: entry.reason,
|
||||
text: entry.text ?? "Unnamed node",
|
||||
ts: Double(entry.ts))
|
||||
}
|
||||
}
|
||||
|
||||
private func applyPresence(_ entries: [PresenceEntry]) {
|
||||
let withIDs = self.normalizePresence(entries)
|
||||
self.notifyOnNodeLogin(withIDs)
|
||||
self.lastPresenceById = Dictionary(uniqueKeysWithValues: withIDs.map { ($0.id, $0) })
|
||||
self.instances = withIDs
|
||||
self.statusMessage = nil
|
||||
self.lastError = nil
|
||||
}
|
||||
|
||||
private func notifyOnNodeLogin(_ instances: [InstanceInfo]) {
|
||||
for inst in instances {
|
||||
guard let reason = inst.reason?.trimmingCharacters(in: .whitespacesAndNewlines) else { continue }
|
||||
guard reason == "node-connected" else { continue }
|
||||
if let mode = inst.mode?.lowercased(), mode == "local" { continue }
|
||||
|
||||
let previous = self.lastPresenceById[inst.id]
|
||||
if previous?.reason == "node-connected", previous?.ts == inst.ts { continue }
|
||||
|
||||
let lastNotified = self.lastLoginNotifiedAtMs[inst.id] ?? 0
|
||||
if inst.ts <= lastNotified { continue }
|
||||
self.lastLoginNotifiedAtMs[inst.id] = inst.ts
|
||||
|
||||
let name = inst.host?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let device = name?.isEmpty == false ? name! : inst.id
|
||||
Task { @MainActor in
|
||||
_ = await NotificationManager().send(
|
||||
title: "Node connected",
|
||||
body: device,
|
||||
sound: nil,
|
||||
priority: .active)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InstancesStore {
|
||||
static func preview(instances: [InstanceInfo] = [
|
||||
InstanceInfo(
|
||||
id: "local",
|
||||
host: "steipete-mac",
|
||||
ip: "10.0.0.12",
|
||||
version: "1.2.3",
|
||||
platform: "macos 26.2.0",
|
||||
deviceFamily: "Mac",
|
||||
modelIdentifier: "Mac16,6",
|
||||
lastInputSeconds: 12,
|
||||
mode: "local",
|
||||
reason: "preview",
|
||||
text: "Local node: steipete-mac (10.0.0.12) · app 1.2.3",
|
||||
ts: Date().timeIntervalSince1970 * 1000),
|
||||
InstanceInfo(
|
||||
id: "gateway",
|
||||
host: "gateway",
|
||||
ip: "100.64.0.2",
|
||||
version: "1.2.3",
|
||||
platform: "linux 6.6.0",
|
||||
deviceFamily: "Linux",
|
||||
modelIdentifier: "x86_64",
|
||||
lastInputSeconds: 45,
|
||||
mode: "remote",
|
||||
reason: "preview",
|
||||
text: "Gateway node · tunnel ok",
|
||||
ts: Date().timeIntervalSince1970 * 1000 - 45000),
|
||||
]) -> InstancesStore {
|
||||
let store = InstancesStore(isPreview: true)
|
||||
store.instances = instances
|
||||
store.statusMessage = "Preview data"
|
||||
return store
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user