montana/macOS/MontanaPresence/NTSAnchorView.swift

444 lines
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
//
// NTS Anchor View 36 Global Atomic Time Servers
// Montana Protocol v3.17.0
//
// Визуализация NTS-якорей: каждое τ окно привязано к глобальному
// атомному времени через TLS 1.3 handshake с 36 серверами (RFC 8915).
//
struct NTSAnchorView: View {
@EnvironmentObject var engine: PresenceEngine
@Environment(\.dismiss) private var dismiss
@State private var showSidebar = false
// Data
@State private var ntsStatus: [String: Any] = [:]
@State private var serverList: [[String: Any]] = []
@State private var regions: [String: Any] = [:]
@State private var latestAnchor: [String: Any]? = nil
@State private var attestations: [[String: Any]] = []
@State private var isLoading = true
@State private var errorText = ""
@State private var selectedRegion: String? = nil
// Colors
private let cyan = Color(red: 0, green: 0.83, blue: 1)
private let gold = Color(red: 0.85, green: 0.68, blue: 0.25)
private let cardBg = Color(red: 0.09, green: 0.09, blue: 0.12)
private let green = Color(red: 0.2, green: 0.85, blue: 0.4)
// Region display order
private let regionOrder = ["GLOBAL", "EU", "RU", "US", "SA", "ASIA", "OCEANIA", "POLES"]
private let regionEmoji: [String: String] = [
"GLOBAL": "\u{1F310}", "EU": "\u{1F1EA}\u{1F1FA}", "RU": "\u{1F1F7}\u{1F1FA}",
"US": "\u{1F1FA}\u{1F1F8}", "SA": "\u{1F30E}", "ASIA": "\u{1F30F}",
"OCEANIA": "\u{1F30F}", "POLES": "\u{2744}\u{FE0F}"
]
private let regionNames: [String: String] = [
"GLOBAL": "Global", "EU": "Europe", "RU": "Russia",
"US": "USA", "SA": "South America", "ASIA": "Asia",
"OCEANIA": "Oceania", "POLES": "Poles"
]
var body: some View {
ZStack(alignment: .leading) {
VStack(spacing: 0) {
// Burger menu
HStack {
Button(action: { withAnimation(.easeInOut(duration: 0.3)) { showSidebar = true } }) {
Image(systemName: "line.3.horizontal")
.font(.system(size: 18, weight: .medium))
.foregroundColor(gold)
.padding(8)
}
.buttonStyle(.plain)
Spacer()
}
.padding(.horizontal, 20)
.padding(.top, 24)
// Header
HStack(spacing: 6) {
Image(systemName: "shield.checkered")
.foregroundColor(cyan)
.font(.system(size: 16))
Text("NTS Anchor")
.font(.system(size: 14, weight: .bold))
Text("36 Atomic Servers")
.font(.system(size: 10))
.foregroundColor(.secondary)
Spacer()
Button(action: { loadData() }) {
Image(systemName: "arrow.clockwise")
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
Button(action: { dismiss() }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
}
.padding(.horizontal, 20)
.padding(.top, 4)
.padding(.bottom, 8)
Divider()
if isLoading {
Spacer()
ProgressView().controlSize(.small)
Text("Loading NTS status...")
.font(.caption).foregroundColor(.secondary)
Spacer()
} else if !errorText.isEmpty {
Spacer()
VStack(spacing: 8) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 24)).foregroundColor(.orange)
Text(errorText)
.font(.caption).foregroundColor(.red)
.multilineTextAlignment(.center)
}
Spacer()
} else {
ScrollView {
VStack(spacing: 12) {
// Status card
statusCard
// Region grid
regionGrid
// Latest anchor details
if let anchor = latestAnchor {
anchorDetailCard(anchor)
}
// Server list
serverListSection
}
.padding(.horizontal, 20)
.padding(.top, 12)
.padding(.bottom, 20)
}
}
}
.background(Color.black)
// Sidebar
if showSidebar {
SharedSidebar(isVisible: $showSidebar)
.environmentObject(engine)
}
}
.onAppear { loadData() }
}
// Status Card
private var statusCard: some View {
VStack(spacing: 8) {
HStack {
let enabled = ntsStatus["nts_enabled"] as? Bool ?? false
Circle()
.fill(enabled ? green : .red)
.frame(width: 8, height: 8)
Text(enabled ? "NTS Active" : "NTS Disabled")
.font(.system(size: 11, weight: .bold))
.foregroundColor(enabled ? green : .red)
Spacer()
let coverage = ntsStatus["coverage"] as? String ?? "0/0"
Text(coverage)
.font(.system(size: 11, weight: .bold, design: .monospaced))
.foregroundColor(gold)
Text("anchored")
.font(.system(size: 9))
.foregroundColor(.secondary)
}
HStack(spacing: 16) {
statPill(
label: "Servers",
value: "\(serverList.count)",
icon: "server.rack"
)
statPill(
label: "Regions",
value: "\(regions.count)",
icon: "globe"
)
let anchors = ntsStatus["total_anchors"] as? Int ?? 0
statPill(
label: "Anchors",
value: "\(anchors)",
icon: "lock.shield"
)
}
}
.padding(12)
.background(cardBg)
.cornerRadius(10)
}
private func statPill(label: String, value: String, icon: String) -> some View {
HStack(spacing: 4) {
Image(systemName: icon)
.font(.system(size: 9))
.foregroundColor(cyan)
Text(value)
.font(.system(size: 12, weight: .bold, design: .monospaced))
Text(label)
.font(.system(size: 9))
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
}
// Region Grid
private var regionGrid: some View {
VStack(alignment: .leading, spacing: 6) {
Text("REGIONS")
.font(.system(size: 9, weight: .bold))
.foregroundColor(.secondary)
LazyVGrid(columns: [
GridItem(.flexible()), GridItem(.flexible()),
GridItem(.flexible()), GridItem(.flexible())
], spacing: 6) {
ForEach(regionOrder, id: \.self) { region in
let count = serverCountForRegion(region)
if count > 0 {
regionCell(region: region, count: count)
}
}
}
}
}
private func regionCell(region: String, count: Int) -> some View {
let isSelected = selectedRegion == region
return VStack(spacing: 2) {
Text(regionEmoji[region] ?? "")
.font(.system(size: 16))
Text("\(count)")
.font(.system(size: 12, weight: .bold, design: .monospaced))
Text(regionNames[region] ?? region)
.font(.system(size: 8))
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 6)
.background(isSelected ? cyan.opacity(0.15) : cardBg)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? cyan : Color.clear, lineWidth: 1)
)
.onTapGesture {
withAnimation(.easeInOut(duration: 0.2)) {
selectedRegion = selectedRegion == region ? nil : region
}
}
}
// Anchor Detail Card
private func anchorDetailCard(_ anchor: [String: Any]) -> some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "lock.shield.fill")
.foregroundColor(gold)
Text("Latest NTS Anchor")
.font(.system(size: 11, weight: .bold))
Spacer()
}
if let contentHash = anchor["content_hash"] as? String {
hashRow(label: "Content Hash", hash: contentHash)
}
if let anchorHash = anchor["anchor_hash"] as? String {
hashRow(label: "Anchor Hash", hash: anchorHash)
}
HStack(spacing: 12) {
if let servers = anchor["server_count"] as? Int {
detailPill(icon: "server.rack", value: "\(servers)", label: "servers")
}
if let regionCount = anchor["region_count"] as? Int {
detailPill(icon: "globe", value: "\(regionCount)", label: "regions")
}
if let spreadNs = anchor["timestamp_spread_ns"] as? Int {
let spreadMs = Double(spreadNs) / 1_000_000.0
detailPill(icon: "clock", value: String(format: "%.1fms", spreadMs), label: "spread")
}
}
// Attestation list
if let atts = anchor["attestations"] as? [[String: Any]], !atts.isEmpty {
VStack(alignment: .leading, spacing: 2) {
Text("ATTESTATIONS (\(atts.count))")
.font(.system(size: 8, weight: .bold))
.foregroundColor(.secondary)
.padding(.top, 4)
ForEach(Array(atts.prefix(12).enumerated()), id: \.offset) { _, att in
attestationRow(att)
}
if atts.count > 12 {
Text("+ \(atts.count - 12) more...")
.font(.system(size: 9))
.foregroundColor(.secondary)
}
}
}
}
.padding(12)
.background(cardBg)
.cornerRadius(10)
}
private func attestationRow(_ att: [String: Any]) -> some View {
let host = att["server_host"] as? String ?? "?"
let region = att["server_region"] as? String ?? ""
let tls = att["tls_version"] as? String ?? ""
let cert = att["cert_fingerprint"] as? String ?? ""
return HStack(spacing: 4) {
Circle().fill(green).frame(width: 4, height: 4)
Text(regionEmoji[region] ?? "")
.font(.system(size: 8))
Text(host)
.font(.system(size: 9, design: .monospaced))
.foregroundColor(.white)
.lineLimit(1)
Spacer()
Text(tls)
.font(.system(size: 8))
.foregroundColor(tls.contains("1.3") ? green : .orange)
Text(String(cert.prefix(8)))
.font(.system(size: 8, design: .monospaced))
.foregroundColor(.secondary)
}
.padding(.vertical, 1)
}
private func hashRow(label: String, hash: String) -> some View {
HStack {
Text(label)
.font(.system(size: 9))
.foregroundColor(.secondary)
Spacer()
Text(String(hash.prefix(16)) + "...")
.font(.system(size: 9, design: .monospaced))
.foregroundColor(cyan)
}
}
private func detailPill(icon: String, value: String, label: String) -> some View {
HStack(spacing: 3) {
Image(systemName: icon)
.font(.system(size: 8))
.foregroundColor(gold)
Text(value)
.font(.system(size: 10, weight: .bold, design: .monospaced))
Text(label)
.font(.system(size: 8))
.foregroundColor(.secondary)
}
}
// Server List
private var serverListSection: some View {
VStack(alignment: .leading, spacing: 6) {
Text("ALL SERVERS (\(filteredServers.count))")
.font(.system(size: 9, weight: .bold))
.foregroundColor(.secondary)
ForEach(Array(filteredServers.enumerated()), id: \.offset) { idx, server in
serverRow(server, index: idx + 1)
}
}
}
private var filteredServers: [[String: Any]] {
if let region = selectedRegion {
return serverList.filter { ($0["region"] as? String) == region }
}
return serverList
}
private func serverRow(_ server: [String: Any], index: Int) -> some View {
let host = server["host"] as? String ?? "?"
let region = server["region"] as? String ?? ""
let org = server["org"] as? String ?? ""
let country = server["country"] as? String ?? ""
let port = server["port"] as? Int ?? 4460
return HStack(spacing: 6) {
Text("\(index)")
.font(.system(size: 8, design: .monospaced))
.foregroundColor(.secondary)
.frame(width: 18, alignment: .trailing)
Text(country)
.font(.system(size: 10))
VStack(alignment: .leading, spacing: 1) {
Text(host)
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundColor(.white)
.lineLimit(1)
Text("\(org) : \(port)")
.font(.system(size: 8))
.foregroundColor(.secondary)
.lineLimit(1)
}
Spacer()
Text(regionNames[region] ?? region)
.font(.system(size: 8))
.foregroundColor(cyan)
.padding(.horizontal, 4)
.padding(.vertical, 1)
.background(cyan.opacity(0.1))
.cornerRadius(3)
}
.padding(.vertical, 3)
.padding(.horizontal, 8)
.background(cardBg)
.cornerRadius(6)
}
// Data Loading
private func loadData() {
isLoading = true
errorText = ""
Task { @MainActor in
do {
async let fetchServers = engine.api.fetchNTSServers()
async let fetchStatus = engine.api.fetchNTSStatus()
let serversResult = try await fetchServers
let statusResult = try await fetchStatus
serverList = serversResult["servers"] as? [[String: Any]] ?? []
regions = serversResult["regions"] as? [String: Any] ?? [:]
ntsStatus = statusResult
latestAnchor = statusResult["latest_anchor"] as? [String: Any]
isLoading = false
} catch {
errorText = "NTS data unavailable"
isLoading = false
}
}
}
private func serverCountForRegion(_ region: String) -> Int {
return serverList.filter { ($0["region"] as? String) == region }.count
}
}