using System.ComponentModel.DataAnnotations; using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Watcher.Data; using Watcher.Models; using Watcher.ViewModels; namespace Watcher.Controllers; public class HardwareDto { // Server Identity [Required] public int Id { get; set; } [Required] public string? IpAddress { get; set; } // Hardware Info [Required] public string? CpuType { get; set; } [Required] public int CpuCores { get; set; } [Required] public string? GpuType { get; set; } [Required] public double RamSize { get; set; } } public class MetricDto { // Server Identity [Required] public int ServerId { get; set; } [Required] public string? IpAddress { get; set; } // Hardware Metrics // CPU public double CPU_Load { get; set; } // % public double CPU_Temp { get; set; } // deg C // GPU public double GPU_Load { get; set; } // % public double GPU_Temp { get; set; } // deg C public double GPU_Vram_Size { get; set; } // Bytes public double GPU_Vram_Load { get; set; } // % // RAM public double RAM_Size { get; set; } // Bytes public double RAM_Load { get; set; } // % // Disks public double DISK_Size { get; set; } // Bytes public double DISK_Usage { get; set; } // Bytes public double DISK_Temp { get; set; } // deg C (if available) // Network public double NET_In { get; set; } // Bytes/s public double NET_Out { get; set; } // Bytes/s } public class ServiceDto { public required int Server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts public required JsonContent Containers { get; set; } } [ApiController] [Route("[controller]")] public class MonitoringController : Controller { private readonly AppDbContext _context; private readonly ILogger _logger; public MonitoringController(AppDbContext context, ILogger logger) { _context = context; _logger = logger; } // Endpoint, an den der Agent seine Hardwareinformationen schickt [HttpPost("hardware-info")] public async Task Register([FromBody] HardwareDto dto) { // Gültigkeit des Payloads prüfen if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogError("Fehlerhafter Registrierungs-Payload."); return BadRequest(new { error = "Ungültiger Payload", details = errors }); } // Server in Datenbank finden var server = await _context.Servers .FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress); if (server != null) { // Serverdaten in Datenbank eintragen server.CpuType = dto.CpuType; server.CpuCores = dto.CpuCores; server.GpuType = dto.GpuType; server.RamSize = dto.RamSize; // Änderungen in Datenbank speichern await _context.SaveChangesAsync(); // Success _logger.LogInformation("Agent für '{server}' erfolgreich registriert.", server.Name); return Ok(); } _logger.LogError("Kein Server für Registrierung gefunden"); return NotFound("No Matching Server found."); } // Endpoint, an dem sich ein Agent initial registriert [HttpGet("register")] public async Task GetServerIdByIp([FromQuery] string IpAddress) { var server = await _context.Servers .FirstOrDefaultAsync(s => s.IPAddress == IpAddress); if (server == null) return NotFound(); return Ok(new { id = server.Id, IpAddress = server.IPAddress }); } // Enpoint, an den Agents Ihre gesammelten Daten senden [HttpPost("metric")] public async Task Receive([FromBody] MetricDto dto) { // Gültigkeit des Payloads prüfen if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogError("Ungültiger Monitoring-Payload."); return BadRequest(new { error = "Ungültiger Payload", details = errors }); } // Server in Datenbank finden var server = await _context.Servers .FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress); if (server != null) { // neues Metric-Objekt erstellen var NewMetric = new Metric { Timestamp = DateTime.UtcNow, ServerId = dto.ServerId, CPU_Load = sanitizeInput(dto.CPU_Load), CPU_Temp = sanitizeInput(dto.CPU_Temp), GPU_Load = sanitizeInput(dto.GPU_Load), GPU_Temp = sanitizeInput(dto.GPU_Temp), GPU_Vram_Size = calculateGigabyte(dto.GPU_Vram_Size), GPU_Vram_Usage = sanitizeInput(dto.GPU_Vram_Load), RAM_Load = sanitizeInput(dto.RAM_Load), RAM_Size = calculateGigabyte(dto.RAM_Size), DISK_Size = calculateGigabyte(dto.DISK_Size), DISK_Usage = calculateGigabyte(dto.DISK_Usage), DISK_Temp = sanitizeInput(dto.DISK_Temp), NET_In = calculateMegabit(dto.NET_In), NET_Out = calculateMegabit(dto.NET_Out) }; try { // Metric Objekt in Datenbank einfügen _context.Metrics.Add(NewMetric); await _context.SaveChangesAsync(); _logger.LogInformation("Monitoring-Daten für '{server}' empfangen", server.Name); return Ok(); } catch { // Alert triggern _logger.LogError("Metric für {server} konnte nicht in Datenbank geschrieben werden.", server.Name); return BadRequest(); } } _logger.LogError("Kein Server für eingegangenen Monitoring-Payload gefunden"); return NotFound("No Matching Server found."); } // Endpoint, an dem Agents Ihre laufenden Services registrieren [HttpPost("service-discovery")] public async Task ServiceDetection([FromBody] ServiceDto dto) { // Gültigkeit des Payloads prüfen if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogError("Invalid ServiceDetection-Payload."); return BadRequest(new { error = "Invalid Payload", details = errors }); } // Json zu was brauchbarem machen string containersJson = await dto.Containers.ReadAsStringAsync(); List newContainers = JsonSerializer.Deserialize>(containersJson)?? new List();; foreach (Container c in newContainers) { c.ServerId = dto.Server_id; // Debug Logs // TODO entfernen wenn fertig getestet Console.WriteLine("---------"); Console.WriteLine("ServerId: " + c.ServerId); Console.WriteLine("ContainerId: " + c.ContainerId); Console.WriteLine("Name: " + c.Name); Console.WriteLine("Image: " + c.Image); Console.WriteLine("---------"); } // Container Objekte erstellen //List newContainers = ParseServiceDiscoveryInput(dto.Server_id, containers); // Liste aller Container, die bereits der übergebenen ServerId zugewiesen sind List existingContainers = _context.Containers .Where(c => c.ServerId == dto.Server_id) .ToList(); // Logik, um Container, die mit dem Payload kamen zu verarbeiten foreach (Container c in newContainers) { // Überprüfen, ob ein übergebener Container bereits für den Host registriert ist if (existingContainers.Contains(c)) { _logger.LogInformation("Container with id " + c.ContainerId + " already exists."); } // Container auf einen Host/Server registrieren else { // Container in Datenbank einlesen try { _context.Containers.Add(c); await _context.SaveChangesAsync(); _logger.LogInformation(c.Name + " added for Host " + c.ServerId); } catch (SqliteException e) { _logger.LogError("Error writing new Containers to Database: " + e.Message); } } } // Logik um abgeschaltene Container aus der Datenbank zu entfernen foreach (Container c in existingContainers) { // Abfrage, ob bereits vorhandener Container im Payload vorhanden war if (!newContainers.Contains(c)) { // Container entfernen _context.Containers.Remove(c); await _context.SaveChangesAsync(); // Metrics für den Container entfernen //Todo _logger.LogInformation("Container " + c.Name + " (" + c.Id + ") on Host-Id " + c.ServerId + " was successfully removed from the database."); } } return Ok(); } // Durchschnittliche Werte Berechnen public async Task CalculateMedian(string Metric, int HoursToMonitor, int ServerId) { // Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll DateTime TimeToMonitor = DateTime.Now.AddHours(-HoursToMonitor); // Alle Metrics von Server "ServerId" finden, die in der festgelegten Zeitspanne liegen var MetricsInTimeFrame = await _context.Metrics .Where(e => e.Timestamp >= TimeToMonitor && e.ServerId == ServerId) .ToListAsync(); // return Action (MetricsInTimeFrame) return NotFound(); } [HttpGet("cpu-usage")] public async Task GetCpuUsageData(int serverId) { var oneDayAgo = DateTime.UtcNow.AddDays(-1); var data = await _context.Metrics .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) .OrderBy(m => m.Timestamp) .Select(m => new { label = m.Timestamp.ToUniversalTime().ToString("o"), data = m.CPU_Load }) .ToListAsync(); return Ok(data); } [HttpGet("ram-usage")] public async Task GetRamUsageData(int serverId) { var oneDayAgo = DateTime.UtcNow.AddDays(-1); var data = await _context.Metrics .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) .OrderBy(m => m.Timestamp) .Select(m => new { label = m.Timestamp.ToUniversalTime().ToString("o"), data = m.RAM_Load }) .ToListAsync(); return Ok(data); } [HttpGet("gpu-usage")] public async Task GetGpuUsageData(int serverId) { var oneDayAgo = DateTime.UtcNow.AddDays(-1); var data = await _context.Metrics .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId) .OrderBy(m => m.Timestamp) .Select(m => new { label = m.Timestamp.ToUniversalTime().ToString("o"), data = m.GPU_Load }) .ToListAsync(); return Ok(data); } // Metric Input Byte zu Gigabyte umwandeln public static double calculateGigabyte(double metric_input) { // *10^-9 um auf Gigabyte zu kommen double calculatedValue = metric_input * Math.Pow(10, -9); // Auf 2 Nachkommastellen runden double calculatedValue_s = sanitizeInput(calculatedValue); return calculatedValue_s; } // Metric Input Byte/s zu Megabit/s umrechnen //TODO public static double calculateMegabit(double metric_input) { // *10^-9 um auf Gigabyte zu kommen double calculatedValue = metric_input * Math.Pow(10, -9); // Auf 2 Nachkommastellen runden double calculatedValue_s = sanitizeInput(calculatedValue); return calculatedValue_s; } // Degree Input auf zwei Nachkommastellen runden public static double sanitizeInput(double metric_input) { Math.Round(metric_input, 2); return metric_input; } private List ParseServiceDiscoveryInput(int server_id, List containers) { List containerList = new List(); // JSON-Objekt auslesen und Container-Objekte erstellen return containerList; } }