import SwiftUI struct OrionMenuView: View { @EnvironmentObject var vm: OrionVMController @Environment(\.openSettings) private var openSettings private var server: ServerDefinition { vm.servers.first ?? .orion } private var status: OrionVMController.VMStatus { vm.status(for: server) } var body: some View { VStack(alignment: .leading, spacing: 0) { statusRow separator menuRow("terminal", "Open SSH in Terminal") { vm.openSSH(server) } .disabled(status != .running) menuRow("envelope.fill", "Open Mailpit", tint: .blue) { vm.openMailpit(server) } .disabled(status != .running || server.mailpitPort == nil) separator if status == .stopped { menuRow("play.fill", "Start", tint: .green) { vm.start(server) } } else { menuRow("stop.fill", "Stop", tint: .red) { vm.stop(server) } .disabled(vm.isBusy(server)) menuRow("arrow.clockwise", "Restart", tint: .orange) { vm.restart(server) } .disabled(vm.isBusy(server) || status != .running) menuRow("exclamationmark.triangle.fill", "Force Stop", tint: .orange) { vm.forceStop(server) } .disabled(vm.isBusy(server)) } separator menuRow("macwindow", "Open in Parallels Desktop", tint: .red) { vm.openParallels() } separator menuRow("gearshape.fill", "Settings…", tint: .secondary) { openSettings() NSApplication.shared.activate(ignoringOtherApps: true) DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { NSApplication.shared.activate(ignoringOtherApps: true) NSApplication.shared.windows .filter { $0.isVisible } .forEach { $0.makeKeyAndOrderFront(nil) } } } separator menuRow("xmark", "Quit OrionToolbar") { NSApplication.shared.terminate(nil) } } .padding(.horizontal, 16) .padding(.vertical, 10) .frame(width: 365) .task { await vm.refreshStatus(server) } } private var statusRow: some View { Button { Task { await vm.refreshStatus(server) } } label: { HStack(spacing: 12) { Circle() .fill(statusDotColor) .frame(width: 12, height: 12) VStack(alignment: .leading, spacing: 3) { Text(server.name) .font(.system(size: 17, weight: .medium)) .foregroundStyle(.primary) HStack(spacing: 5) { Text(status.title) .foregroundStyle(.secondary) Text("·") .foregroundStyle(.secondary) Text(reachabilityText) .foregroundStyle(reachabilityColor) } .font(.system(size: 14)) } Spacer() if vm.isBusy(server) { ProgressView().controlSize(.small) } else { Image(systemName: "chevron.right") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(.secondary) } } .contentShape(Rectangle()) .frame(height: 62) } .buttonStyle(.plain) } private var separator: some View { Divider().padding(.vertical, 4) } private func menuRow(_ icon: String, _ title: String, tint: Color = .primary, action: @escaping () -> Void) -> some View { Button(action: action) { HStack(spacing: 14) { Image(systemName: icon) .font(.system(size: 17, weight: .medium)) .foregroundStyle(tint) .frame(width: 24, alignment: .center) Text(title) .font(.system(size: 16)) .foregroundStyle(.primary) Spacer() } .contentShape(Rectangle()) .frame(height: 43) } .buttonStyle(.plain) } private var statusDotColor: Color { switch status { case .running: .green case .stopped, .unavailable: .red case .suspended, .transitioning: .orange case .checking: .gray } } private var reachabilityText: String { switch status { case .running: "Reachable" case .checking: "Checking…" case .transitioning: "Working…" default: "Unavailable" } } private var reachabilityColor: Color { status == .running ? .green : .secondary } }