diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..0b4c733 Binary files /dev/null and b/.DS_Store differ diff --git a/MySwiftApp/.gitignore b/MySwiftApp/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/MySwiftApp/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/MySwiftApp/Package.swift b/MySwiftApp/Package.swift new file mode 100644 index 0000000..db03d07 --- /dev/null +++ b/MySwiftApp/Package.swift @@ -0,0 +1,19 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "MySwiftApp", + platforms: [ + .macOS(.v13) + ], + products: [ + .executable(name: "MySwiftApp", targets: ["MySwiftApp"]) + ], + targets: [ + .executableTarget( + name: "MySwiftApp", + dependencies: [], + path: "Sources" + ) + ] +) diff --git a/MySwiftApp/Sources/main.swift b/MySwiftApp/Sources/main.swift new file mode 100644 index 0000000..cc6bdde --- /dev/null +++ b/MySwiftApp/Sources/main.swift @@ -0,0 +1,235 @@ +import SwiftUI +import Combine + +class ResourceMonitorViewModel: ObservableObject { + @Published var cpuUsage: Double = 0 + @Published var memoryUsage: Double = 0 + @Published var diskUsage: Double = 0 + + @Published var alertsEnabled: Bool = true + + @Published var cpuThreshold: String = "" + @Published var memoryThreshold: String = "" + @Published var diskThreshold: String = "" + + private var timer: AnyCancellable? + + init() { + startFetching() + } + + func startFetching() { + timer = Timer.publish(every: 1, on: .main, in: .common) + .autoconnect() + .sink { _ in self.fetchUsage() } + } + + func fetchUsage() { + guard let url = URL(string: "http://127.0.0.1:8080/resource-usage") else { return } + URLSession.shared.dataTask(with: url) { data, _, error in + if let error = error { + print("Error fetching usage:", error.localizedDescription) + return + } + guard let data = data else { return } + do { + let usage = try JSONDecoder().decode(ResourceUsage.self, from: data) + DispatchQueue.main.async { + self.cpuUsage = usage.cpu_usage + self.memoryUsage = usage.memory_usage + self.diskUsage = usage.disk_usage + } + } catch { + print("Decoding error:", error.localizedDescription) + } + }.resume() + } + + func toggleAlerts() { + guard let url = URL(string: "http://127.0.0.1:8080/toggle-alerts") else { return } + let body = ["enable_alerts": alertsEnabled] + sendPostRequest(url: url, body: body) + } + + func changeLimits() { + guard let url = URL(string: "http://127.0.0.1:8080/limit-changer"), + let cpu = Double(cpuThreshold), + let mem = Double(memoryThreshold), + let disk = Double(diskThreshold) + else { + print("Invalid threshold input") + return + } + let body: [String: Any] = [ + "cpu_threshold": cpu, + "memory_threshold": mem, + "disk_threshold": disk + ] + sendPostRequest(url: url, body: body) + } + + func shutdownServer(completion: @escaping () -> Void) { + guard let url = URL(string: "http://127.0.0.1:8080/shutdown") else { return } + var request = URLRequest(url: url) + request.httpMethod = "POST" + URLSession.shared.dataTask(with: request) { _, _, error in + if let error = error { + print("Error shutting down:", error.localizedDescription) + } + DispatchQueue.main.async { + completion() + } + }.resume() + } + + private func sendPostRequest(url: URL, body: [String: Any]) { + var request = URLRequest(url: url) + request.httpMethod = "POST" + do { + request.httpBody = try JSONSerialization.data(withJSONObject: body) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + } catch { + print("Error serializing JSON:", error.localizedDescription) + return + } + URLSession.shared.dataTask(with: request) { _, _, error in + if let error = error { + print("POST request error:", error.localizedDescription) + } + }.resume() + } +} + +struct ResourceUsage: Codable { + let cpu_usage: Double + let memory_usage: Double + let disk_usage: Double +} + +struct ContentView: View { + @StateObject private var vm = ResourceMonitorViewModel() + @State private var isShuttingDown = false + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + Group { + UsageBar(title: "CPU Usage", value: vm.cpuUsage, color: .red) + UsageBar(title: "Memory Usage", value: vm.memoryUsage, color: .blue) + UsageBar(title: "Disk Usage", value: vm.diskUsage, color: .purple) + } + + Divider() + .padding(.vertical, 10) + + Toggle("Enable Alerts", isOn: $vm.alertsEnabled) + .onChange(of: vm.alertsEnabled) { _ in vm.toggleAlerts() } + .toggleStyle(SwitchToggleStyle(tint: .pink)) + .padding(.bottom, 10) + + Text("Change Resource Limits") + .font(.title3).bold() + .foregroundColor(.primary) + + HStack(spacing: 12) { + LimitInputField(text: $vm.cpuThreshold, placeholder: "CPU %") + LimitInputField(text: $vm.memoryThreshold, placeholder: "Memory %") + LimitInputField(text: $vm.diskThreshold, placeholder: "Disk %") + + Button(action: vm.changeLimits) { + Text("Apply") + .bold() + .padding(.vertical, 8) + .padding(.horizontal, 16) + .background(Color.accentColor) + .foregroundColor(.white) + .cornerRadius(8) + .shadow(radius: 2) + } + .buttonStyle(PlainButtonStyle()) + .keyboardShortcut(.defaultAction) + } + + Divider() + .padding(.vertical, 10) + + Button(action: { + isShuttingDown = true + vm.shutdownServer { + isShuttingDown = false + NSApplication.shared.terminate(nil) + } + }) { + Text(isShuttingDown ? "Shutting down..." : "Shutdown Server") + .bold() + .frame(maxWidth: .infinity) + .padding() + .background(isShuttingDown ? Color.gray : Color.red) + .foregroundColor(.white) + .cornerRadius(12) + .shadow(radius: 3) + } + .disabled(isShuttingDown) + + Spacer() + } + .padding(24) + .frame(width: 400) // Wider app content width + .background( + RoundedRectangle(cornerRadius: 20) + .fill(Color(.windowBackgroundColor)) + .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 6) + ) + .padding() + } +} + +struct UsageBar: View { + let title: String + let value: Double + let color: Color + + var body: some View { + VStack(alignment: .leading, spacing: 6) { + Text(title) + .font(.headline) + .foregroundColor(color) + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 6) + .frame(height: 14) + .foregroundColor(Color.gray.opacity(0.3)) + RoundedRectangle(cornerRadius: 6) + .frame(width: CGFloat(value / 100) * 360, height: 14) // adapt bar width + .foregroundColor(color) + .animation(.easeInOut(duration: 0.5), value: value) + } + Text(String(format: "%.1f%%", value)) + .font(.caption) + .foregroundColor(.secondary) + .padding(.leading, 4) + } + } +} + +struct LimitInputField: View { + @Binding var text: String + let placeholder: String + + var body: some View { + TextField(placeholder, text: $text) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .frame(width: 80) + .multilineTextAlignment(.center) + .font(.system(size: 14, weight: .semibold)) + } +} + +@main +struct SysGuardApp: App { + var body: some Scene { + WindowGroup("SysGuard") { + ContentView() +} + + .windowResizability(.contentSize) // window resizes to content size tightly + } +} diff --git a/backend.log b/backend.log new file mode 100644 index 0000000..ac27c0b --- /dev/null +++ b/backend.log @@ -0,0 +1,8 @@ +2025/07/13 19:32:49 Default limits: {90 90 90} +2025/07/13 19:32:49 Starting backend server on port 8080... +2025/07/13 19:33:01 Alerts enabled: false +2025/07/13 19:33:02 Alerts enabled: true +2025/07/13 19:33:03 Alerts enabled: false +2025/07/13 19:33:15 Received shutdown request +2025/07/13 19:33:15 Shutting down the server... +2025/07/13 19:33:15 Server stopped. diff --git a/backend/.DS_Store b/backend/.DS_Store new file mode 100644 index 0000000..d188abd Binary files /dev/null and b/backend/.DS_Store differ diff --git a/backend/sysguard b/backend/sysguard new file mode 100755 index 0000000..dad12fb Binary files /dev/null and b/backend/sysguard differ diff --git a/frontend/image b/frontend/image deleted file mode 100755 index b34c581..0000000 Binary files a/frontend/image and /dev/null differ diff --git a/frontend/widget.go b/frontend/widget.go deleted file mode 100644 index afc076b..0000000 --- a/frontend/widget.go +++ /dev/null @@ -1,213 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "strconv" - "time" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" -) - -// Structure to hold resource usage data -type ResourceUsage struct { - CPUUsage float64 `json:"cpu_usage"` - MemoryUsage float64 `json:"memory_usage"` - DiskUsage float64 `json:"disk_usage"` -} - -// Function to fetch resource usage data from the backend API -func fetchResourceUsage() (*ResourceUsage, error) { - resp, err := http.Get("http://localhost:8080/resource-usage") - if err != nil { - return nil, fmt.Errorf("error fetching resource usage: %v", err) - } - defer resp.Body.Close() - - var usage ResourceUsage - if err := json.NewDecoder(resp.Body).Decode(&usage); err != nil { - return nil, fmt.Errorf("error decoding response: %v", err) - } - - return &usage, nil -} - -// Function to toggle the alert status on the backend -func toggleAlerts(enabled bool) error { - data := struct { - EnableAlerts bool `json:"enable_alerts"` - }{enabled} - - body, err := json.Marshal(data) - if err != nil { - return fmt.Errorf("error marshalling request body: %v", err) - } - - resp, err := http.Post("http://localhost:8080/toggle-alerts", "application/json", bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("error sending alert toggle request: %v", err) - } - defer resp.Body.Close() - - return nil -} - -// Function to change the resource usage limits on the backend -func changeLimits(cpuThreshold, memoryThreshold, diskThreshold float64) error { - data := struct { - CPUThreshold float64 `json:"cpu_threshold"` - MemoryThreshold float64 `json:"memory_threshold"` - DiskThreshold float64 `json:"disk_threshold"` - }{ - CPUThreshold: cpuThreshold, - MemoryThreshold: memoryThreshold, - DiskThreshold: diskThreshold, - } - - body, err := json.Marshal(data) - if err != nil { - return fmt.Errorf("error marshalling request body: %v", err) - } - - resp, err := http.Post("http://localhost:8080/limit-changer", "application/json", bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("error sending limit change request: %v", err) - } - defer resp.Body.Close() - - return nil -} - -// Function to shut down the backend server -func shutdownServer() error { - resp, err := http.Post("http://localhost:8080/shutdown", "application/json", nil) - if err != nil { - return fmt.Errorf("error sending shutdown request: %v", err) - } - defer resp.Body.Close() - - return nil -} - -func createGraphWindow(a fyne.App) fyne.Window { - w := a.NewWindow("SysGuard") - - cpuGraph := widget.NewProgressBar() - cpuGraph.Min = 0 - cpuGraph.Max = 100 - - memGraph := widget.NewProgressBar() - memGraph.Min = 0 - memGraph.Max = 100 - - diskGraph := widget.NewProgressBar() - diskGraph.Min = 0 - diskGraph.Max = 100 - - alertCheckbox := widget.NewCheck("Enable Alerts", func(enabled bool) { - if err := toggleAlerts(enabled); err != nil { - log.Println("Error toggling alerts:", err) - } - }) - alertCheckbox.SetChecked(true) // Set the checkbox to be initially checked - - // Add input fields for changing the limits - cpuEntry := widget.NewEntry() - cpuEntry.SetPlaceHolder("Enter CPU Threshold") - - memEntry := widget.NewEntry() - memEntry.SetPlaceHolder("Enter Memory Threshold") - - diskEntry := widget.NewEntry() - diskEntry.SetPlaceHolder("Enter Disk Threshold") - - // Button to submit new limits - changeLimitsButton := widget.NewButton("Change Limits", func() { - cpuThreshold, err := strconv.ParseFloat(cpuEntry.Text, 64) - if err != nil { - log.Println("Invalid CPU threshold") - return - } - - memThreshold, err := strconv.ParseFloat(memEntry.Text, 64) - if err != nil { - log.Println("Invalid Memory threshold") - return - } - - diskThreshold, err := strconv.ParseFloat(diskEntry.Text, 64) - if err != nil { - log.Println("Invalid Disk threshold") - return - } - - // Send the new limits to the backend - if err := changeLimits(cpuThreshold, memThreshold, diskThreshold); err != nil { - log.Println("Error changing limits:", err) - } else { - log.Println("Limits updated successfully!") - } - }) - - // Button to close the app - closeAppButton := widget.NewButton("Close App", func() { - if err := shutdownServer(); err != nil { - log.Println("Error shutting down server:", err) - } else { - log.Println("Server shut down successfully. Exiting application...") - a.Quit() - os.Exit(0) // Exit the frontend application - } - }) - - go func() { - for { - usage, err := fetchResourceUsage() - if err != nil { - log.Println("Error fetching resource usage:", err) - continue - } - - // Update the graphs with the fetched resource data - cpuGraph.SetValue(usage.CPUUsage) - memGraph.SetValue(usage.MemoryUsage) - diskGraph.SetValue(usage.DiskUsage) - - time.Sleep(time.Second) - } - }() - - content := container.NewVBox( - widget.NewLabel("CPU Usage"), - cpuGraph, - widget.NewLabel("Memory Usage"), - memGraph, - widget.NewLabel("Disk Usage"), - diskGraph, - alertCheckbox, // Add the checkbox for enabling/disabling alerts - widget.NewLabel("Change Resource Limits"), - cpuEntry, - memEntry, - diskEntry, - changeLimitsButton, - closeAppButton, // Add the "Close App" button - ) - - w.SetContent(content) - w.Resize(fyne.NewSize(400, 300)) - - return w -} - -func main() { - a := app.New() - w := createGraphWindow(a) - w.ShowAndRun() -} diff --git a/main.sh b/main.sh index c9f2f10..599e066 100755 --- a/main.sh +++ b/main.sh @@ -3,33 +3,29 @@ # Get the script's directory SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) -# Start backend monitor echo "Starting backend monitor..." -if [ -f "$SCRIPT_DIR/backend/monitor" ]; then - echo "Found 'monitor' executable in $SCRIPT_DIR/backend." - "$SCRIPT_DIR/backend/monitor" & +if [ -f "$SCRIPT_DIR/backend/sysguard" ]; then + echo "Found 'sysguard' backend executable in $SCRIPT_DIR/backend." + "$SCRIPT_DIR/backend/sysguard" & backend_pid=$! echo "Backend monitor started with PID: $backend_pid" else - echo "Error: 'monitor' file not found in $SCRIPT_DIR/backend." + echo "Error: 'sysguard' backend executable not found in $SCRIPT_DIR/backend." exit 1 fi -# Start frontend SysGuard echo "Starting frontend SysGuard..." -if [ -f "$SCRIPT_DIR/frontend/monitor" ]; then - echo "Found 'sysguard' executable in $SCRIPT_DIR/frontend." - "$SCRIPT_DIR/frontend/monitor" & - frontend_pid=$! - echo "Frontend SysGuard started with PID: $frontend_pid" +if [ -d "$SCRIPT_DIR/frontend/build/Release/SysGuard.app" ]; then + echo "Found 'SysGuard.app' in $SCRIPT_DIR/frontend/build/Release." + open "$SCRIPT_DIR/frontend/build/Release/SysGuard.app" + echo "Frontend SysGuard launched (runs in foreground by macOS)." else - echo "Error: 'sysguard' file not found in $SCRIPT_DIR/frontend." + echo "Error: 'SysGuard.app' not found in $SCRIPT_DIR/frontend/build/Release." echo "Stopping backend monitor..." kill $backend_pid exit 1 fi -# Wait for both processes to complete -echo "Both backend and frontend are running. Press Ctrl+C to stop." -trap "echo 'Stopping processes...'; kill $backend_pid $frontend_pid" EXIT -wait +echo "Backend monitor is running with PID $backend_pid. Press Ctrl+C to stop backend." +trap "echo 'Stopping backend...'; kill $backend_pid" EXIT +wait $backend_pid diff --git a/makefile b/makefile index ca0c090..4461c29 100644 --- a/makefile +++ b/makefile @@ -1,66 +1,75 @@ -BINARY_NAME = image +# Binary name for backend +BINARY_NAME = sysguard GO = go GOFMT = gofmt BACKEND_DIR = backend -FRONTEND_DIR = frontend +FRONTEND_DIR = MySwiftApp BACKEND_LOG = backend.log BACKEND_PORT = 8080 +# Install Go dependencies deps: @echo "Installing Go dependencies..." - @go mod tidy + $(GO) mod tidy +# Build backend build-backend: @echo "Building the backend application..." $(GO) build -o $(BACKEND_DIR)/$(BINARY_NAME) $(BACKEND_DIR)/main.go +# Build frontend (Swift app) build-frontend: - @echo "Building the frontend application..." - $(GO) build -o $(FRONTEND_DIR)/$(BINARY_NAME) $(FRONTEND_DIR)/widget.go + @echo "Building the frontend Swift application..." + cd MySwiftApp && swift build -c release +# Build both build: build-backend build-frontend +# Run backend run-backend: @echo "Running the backend server..." - @$(BACKEND_DIR)/$(BINARY_NAME) > $(BACKEND_LOG) 2>&1 & # Run in background + @$(BACKEND_DIR)/$(BINARY_NAME) > $(BACKEND_LOG) 2>&1 & @echo "Backend started in the background." +# Wait for backend wait-backend: @echo "Waiting for backend to start..." @while ! nc -z localhost $(BACKEND_PORT); do sleep 1; done @echo "Backend is running!" +# Run frontend (Swift app) run-frontend: @echo "Running the frontend application..." - @$(FRONTEND_DIR)/$(BINARY_NAME) + cd MySwiftApp && .build/release/MySwiftApp +# Run everything run-all: deps build run-backend wait-backend run-frontend +# Run using pre-built run-built: run-backend wait-backend run-frontend -docker-run: - @echo "Building binaries..." - $(MAKE) build - @echo "Building Docker image..." - docker-compose build - @echo "Starting container..." - docker-compose up -d - +# Clean clean: @echo "Cleaning up..." - rm -f $(BACKEND_DIR)/$(BINARY_NAME) $(FRONTEND_DIR)/$(BINARY_NAME) $(BACKEND_LOG) + rm -f $(BACKEND_DIR)/$(BINARY_NAME) $(BACKEND_LOG) + @echo "Cleaning frontend build..." + cd $(FRONTEND_DIR) && xcodebuild clean +# Help help: - @echo "Makefile for Go application" + @echo "Makefile for SysGuard project" @echo "" @echo "Available targets:" + @echo " deps - Install Go dependencies" @echo " build - Build both backend and frontend applications" - @echo " run-backend - Build and run the backend server" - @echo " wait-backend - Wait until the backend is ready using a real check" - @echo " run-frontend - Build and run the frontend application" - @echo " run-all - Install deps, build everything, then run backend & frontend" - @echo " clean - Remove generated binaries and logs" - @echo " deps - Install Go dependencies and tidy up" + @echo " build-backend- Build backend Go server" + @echo " build-frontend - Build Swift frontend" + @echo " run-backend - Run backend server" + @echo " wait-backend - Wait for backend to become ready" + @echo " run-frontend - Run frontend application" + @echo " run-all - Install deps, build, run backend and frontend" + @echo " run-built - Run backend and frontend using existing build" + @echo " clean - Clean build artifacts and logs" @echo " help - Show this help message" diff --git a/start.sh b/start.sh index c3b50df..3b5bb26 100644 --- a/start.sh +++ b/start.sh @@ -1,6 +1,15 @@ #!/bin/sh -./backend-monitor & +# Start backend +echo "Starting backend..." +./backend/sysguard & +backend_pid=$! sleep 1 -./frontend-monitor + +echo "Starting frontend..." +open ./frontend/build/Release/SysGuard.app + +echo "Backend running with PID $backend_pid. Press Ctrl+C to stop backend." +trap "echo 'Stopping backend...'; kill $backend_pid" EXIT +wait $backend_pid