-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
115 changed files
with
9,684 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import SwiftUI | ||
|
||
struct ContentView: View { | ||
@EnvironmentObject private var authViewModel: AuthViewModel | ||
|
||
var body: some View { | ||
if authViewModel.isAuthenticated { | ||
DashboardView() | ||
} else { | ||
LoginView() | ||
} | ||
} | ||
} | ||
|
||
struct ContentView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
ContentView() | ||
.environmentObject(AuthViewModel()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Foundation | ||
|
||
struct Camera: Identifiable { | ||
let id: String | ||
let clientId: String | ||
var isMinimized: Bool = false | ||
var position: CGPoint = CGPoint(x: 100, y: 100) | ||
var size: CGSize = CGSize(width: 400, height: 300) | ||
|
||
init(id: String = UUID().uuidString, clientId: String) { | ||
self.id = id | ||
self.clientId = clientId | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import Foundation | ||
|
||
struct Client: Identifiable, Codable { | ||
var id: String { mid } | ||
let mid: String | ||
let name: String? | ||
var properties: [String: String]? | ||
var lastSeen: Date | ||
var hasCamera: Bool | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case mid, name, properties | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
mid = try container.decode(String.self, forKey: .mid) | ||
name = try container.decodeIfPresent(String.self, forKey: .name) | ||
properties = try container.decodeIfPresent([String: String].self, forKey: .properties) | ||
lastSeen = Date() | ||
|
||
// Determine if client has camera based on properties | ||
hasCamera = properties?["has_camera"] == "true" | ||
} | ||
|
||
init(mid: String, name: String? = nil, properties: [String: String]? = nil) { | ||
self.mid = mid | ||
self.name = name | ||
self.properties = properties | ||
self.lastSeen = Date() | ||
self.hasCamera = properties?["has_camera"] == "true" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
struct Terminal: Identifiable { | ||
let id: String | ||
let clientId: String | ||
var title: String | ||
var output: String = "" | ||
var isMinimized: Bool = false | ||
var position: CGPoint = CGPoint(x: 100, y: 100) | ||
var size: CGSize = CGSize(width: 600, height: 400) | ||
|
||
init(id: String = UUID().uuidString, clientId: String, title: String) { | ||
self.id = id | ||
self.clientId = clientId | ||
self.title = title | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import Foundation | ||
|
||
struct UploadProgress: Identifiable { | ||
let id: String | ||
let filename: String | ||
let clientId: String | ||
var progress: Double | ||
var status: UploadStatus | ||
var startTime: Date | ||
|
||
enum UploadStatus: String { | ||
case uploading | ||
case completed | ||
case failed | ||
} | ||
|
||
init(id: String = UUID().uuidString, filename: String, clientId: String) { | ||
self.id = id | ||
self.filename = filename | ||
self.clientId = clientId | ||
self.progress = 0.0 | ||
self.status = .uploading | ||
self.startTime = Date() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import SwiftUI | ||
|
||
@main | ||
struct OverlordApp: App { | ||
@StateObject private var authViewModel = AuthViewModel() | ||
|
||
var body: some Scene { | ||
WindowGroup { | ||
ContentView() | ||
.environmentObject(authViewModel) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Foundation | ||
import Combine | ||
|
||
class APIService { | ||
static let baseURL = "http://your-server-address/api" // Replace with your actual server address | ||
|
||
private let session: URLSession | ||
private var cancellables = Set<AnyCancellable>() | ||
|
||
init(session: URLSession = .shared) { | ||
self.session = session | ||
} | ||
|
||
func getClients(token: String) -> AnyPublisher<[Client], Error> { | ||
let url = URL(string: "\(APIService.baseURL)/agents/list")! | ||
var request = URLRequest(url: url) | ||
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") | ||
|
||
return session.dataTaskPublisher(for: request) | ||
.map { $0.data } | ||
.decode(type: [Client].self, decoder: JSONDecoder()) | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
func getClientProperties(mid: String, token: String) -> AnyPublisher<[String: String], Error> { | ||
let url = URL(string: "\(APIService.baseURL)/agent/properties/\(mid)")! | ||
var request = URLRequest(url: url) | ||
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") | ||
|
||
return session.dataTaskPublisher(for: request) | ||
.map { $0.data } | ||
.decode(type: [String: String].self, decoder: JSONDecoder()) | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
func downloadFile(sid: String, token: String) { | ||
let url = URL(string: "\(APIService.baseURL)/file/download/\(sid)?token=\(token)")! | ||
UIApplication.shared.open(url) | ||
} | ||
} |
144 changes: 144 additions & 0 deletions
144
OverlordApp/OverlordApp/Services/WebSocketService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import Foundation | ||
import Combine | ||
|
||
class WebSocketService: ObservableObject { | ||
private var webSocket: URLSessionWebSocketTask? | ||
private var session: URLSession | ||
private var isStarted = false | ||
private var reconnectTimer: Timer? | ||
private var reconnectAttempts = 0 | ||
private let maxReconnectAttempts = 5 | ||
|
||
@Published var isConnected = false | ||
|
||
private var eventHandlers: [String: [(String) -> Void]] = [:] | ||
|
||
init(session: URLSession = .shared) { | ||
self.session = session | ||
} | ||
|
||
func start(token: String) { | ||
guard !isStarted else { return } | ||
|
||
isStarted = true | ||
reconnectAttempts = 0 | ||
connect(token: token) | ||
} | ||
|
||
func stop() { | ||
isStarted = false | ||
|
||
reconnectTimer?.invalidate() | ||
reconnectTimer = nil | ||
|
||
webSocket?.cancel(with: .normalClosure, reason: nil) | ||
webSocket = nil | ||
|
||
isConnected = false | ||
} | ||
|
||
private func connect(token: String) { | ||
guard isStarted, webSocket == nil else { return } | ||
|
||
let urlString = "\(APIService.baseURL.replacingOccurrences(of: "http", with: "ws"))/monitor?token=\(token)" | ||
guard let url = URL(string: urlString) else { | ||
print("Invalid WebSocket URL") | ||
return | ||
} | ||
|
||
webSocket = session.webSocketTask(with: url) | ||
webSocket?.resume() | ||
|
||
receiveMessage() | ||
|
||
isConnected = true | ||
} | ||
|
||
private func receiveMessage() { | ||
webSocket?.receive { [weak self] result in | ||
guard let self = self else { return } | ||
|
||
switch result { | ||
case .success(let message): | ||
switch message { | ||
case .string(let text): | ||
self.handleMessage(text) | ||
case .data(let data): | ||
if let text = String(data: data, encoding: .utf8) { | ||
self.handleMessage(text) | ||
} | ||
@unknown default: | ||
break | ||
} | ||
|
||
// Continue receiving messages | ||
self.receiveMessage() | ||
|
||
case .failure(let error): | ||
print("WebSocket receive error: \(error)") | ||
self.handleDisconnect() | ||
} | ||
} | ||
} | ||
|
||
private func handleMessage(_ text: String) { | ||
guard let data = text.data(using: .utf8), | ||
let message = try? JSONDecoder().decode(WebSocketMessage.self, from: data) else { | ||
return | ||
} | ||
|
||
let handlers = eventHandlers[message.event] ?? [] | ||
let messageData = message.data.first ?? "" | ||
|
||
DispatchQueue.main.async { | ||
handlers.forEach { handler in | ||
handler(messageData) | ||
} | ||
} | ||
} | ||
|
||
private func handleDisconnect() { | ||
isConnected = false | ||
webSocket = nil | ||
|
||
guard isStarted else { return } | ||
|
||
reconnectAttempts += 1 | ||
|
||
if reconnectAttempts >= maxReconnectAttempts { | ||
// Too many failed attempts, stop trying | ||
stop() | ||
|
||
// Notify that authentication might have failed | ||
NotificationCenter.default.post(name: .webSocketAuthenticationFailed, object: nil) | ||
return | ||
} | ||
|
||
// Try to reconnect after a delay | ||
reconnectTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in | ||
guard let self = self, let token = UserDefaults.standard.string(forKey: "authToken") else { return } | ||
self.connect(token: token) | ||
} | ||
} | ||
|
||
func on(event: String, handler: @escaping (String) -> Void) { | ||
if eventHandlers[event] == nil { | ||
eventHandlers[event] = [] | ||
} | ||
|
||
eventHandlers[event]?.append(handler) | ||
} | ||
|
||
func off(event: String) { | ||
eventHandlers[event] = nil | ||
} | ||
} | ||
|
||
struct WebSocketMessage: Codable { | ||
let event: String | ||
let data: [String] | ||
} | ||
|
||
extension Notification.Name { | ||
static let webSocketAuthenticationFailed = Notification.Name("webSocketAuthenticationFailed") | ||
} |
Oops, something went wrong.