mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
- Fix session count display to show on single line in menu bar - Add conditional compilation to disable automatic updates in DEBUG mode - Add "Open Dashboard" menu item that opens internal server URL - Convert Help menu from popover to native macOS submenu style - Enable automatic update downloads in Sparkle configuration - Increase Advanced Settings tab height from 400 to 500 pixels - Add Tailscale recommendation with clickable markdown link - Fix Sendable protocol conformance issues throughout codebase - Add ApplicationMover utility for app installation location management These changes improve the overall user experience by making the UI more intuitive and ensuring automatic updates work correctly in production while being disabled during development.
139 lines
4.2 KiB
Swift
139 lines
4.2 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
/// Monitors tty-fwd sessions and provides real-time session count
|
|
@MainActor
|
|
@Observable
|
|
class SessionMonitor {
|
|
static let shared = SessionMonitor()
|
|
|
|
var sessionCount: Int = 0
|
|
var sessions: [String: SessionInfo] = [:]
|
|
var lastError: String?
|
|
|
|
private var monitoringTask: Task<Void, Never>?
|
|
private let refreshInterval: TimeInterval = 5.0 // Check every 5 seconds
|
|
private var serverPort: Int
|
|
|
|
/// Information about a terminal session
|
|
struct SessionInfo: Codable {
|
|
let cmdline: [String]
|
|
let cwd: String
|
|
let exitCode: Int?
|
|
let name: String
|
|
let pid: Int
|
|
let startedAt: String
|
|
let status: String
|
|
let stdin: String
|
|
let streamOut: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case cmdline
|
|
case cwd
|
|
case name
|
|
case pid
|
|
case status
|
|
case stdin
|
|
case exitCode = "exit_code"
|
|
case startedAt = "started_at"
|
|
case streamOut = "stream-out"
|
|
}
|
|
|
|
var isRunning: Bool {
|
|
status == "running"
|
|
}
|
|
}
|
|
|
|
private init() {
|
|
let port = UserDefaults.standard.integer(forKey: "serverPort")
|
|
self.serverPort = port > 0 ? port : 4_020
|
|
}
|
|
|
|
func startMonitoring() {
|
|
stopMonitoring()
|
|
|
|
// Update port from UserDefaults in case it changed
|
|
let port = UserDefaults.standard.integer(forKey: "serverPort")
|
|
self.serverPort = port > 0 ? port : 4_020
|
|
|
|
// Start monitoring task
|
|
monitoringTask = Task {
|
|
// Initial fetch
|
|
await fetchSessions()
|
|
|
|
// Set up periodic fetching
|
|
while !Task.isCancelled {
|
|
try? await Task.sleep(for: .seconds(refreshInterval))
|
|
if !Task.isCancelled {
|
|
await fetchSessions()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func stopMonitoring() {
|
|
monitoringTask?.cancel()
|
|
monitoringTask = nil
|
|
}
|
|
|
|
@MainActor
|
|
private func fetchSessions() async {
|
|
do {
|
|
// First check if server is running
|
|
let healthURL = URL(string: "http://127.0.0.1:\(serverPort)/health")!
|
|
let healthRequest = URLRequest(url: healthURL, timeoutInterval: 2.0)
|
|
|
|
do {
|
|
let (_, healthResponse) = try await URLSession.shared.data(for: healthRequest)
|
|
guard let httpResponse = healthResponse as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200
|
|
else {
|
|
// Server not running
|
|
self.sessions = [:]
|
|
self.sessionCount = 0
|
|
self.lastError = nil
|
|
return
|
|
}
|
|
} catch {
|
|
// Server not reachable
|
|
self.sessions = [:]
|
|
self.sessionCount = 0
|
|
self.lastError = nil
|
|
return
|
|
}
|
|
|
|
// Server is running, fetch sessions
|
|
let url = URL(string: "http://127.0.0.1:\(serverPort)/sessions")!
|
|
let request = URLRequest(url: url, timeoutInterval: 5.0)
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200
|
|
else {
|
|
self.lastError = "Failed to fetch sessions"
|
|
return
|
|
}
|
|
|
|
// Parse JSON response
|
|
let sessionsData = try JSONDecoder().decode([String: SessionInfo].self, from: data)
|
|
self.sessions = sessionsData
|
|
|
|
// Count only running sessions
|
|
self.sessionCount = sessionsData.values.count(where: { $0.isRunning })
|
|
self.lastError = nil
|
|
|
|
} catch {
|
|
// Don't set error for connection issues when server is likely not running
|
|
if !(error is URLError) {
|
|
self.lastError = "Error fetching sessions: \(error.localizedDescription)"
|
|
}
|
|
// Clear sessions on error
|
|
self.sessions = [:]
|
|
self.sessionCount = 0
|
|
}
|
|
}
|
|
|
|
func refreshNow() async {
|
|
await fetchSessions()
|
|
}
|
|
}
|