montana/macOS/MontanaPresence/NTSAnchorView.swift

444 lines
17 KiB
Swift
Raw Permalink Normal View History

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
}
}