17 Commits

Author SHA1 Message Date
8e8bfaee45 Merge pull request 'Fixed Json Parser' (#37) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 1m5s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m59s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #37
2025-10-30 08:34:09 +01:00
42a40e8b2e Fixed Json Parser 2025-10-30 08:33:35 +01:00
b8b0520eaa Merge pull request 'Merge Errors fixed' (#36) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 50s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 7m46s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #36
2025-10-29 20:58:18 +01:00
df1c4ca4b4 Merge Errors fixed 2025-10-29 20:57:57 +01:00
33acb5ea3a Merge pull request 'feature/service-detection' (#35) from feature/service-detection into staging
Some checks failed
Gitea CI/CD / dotnet-build-and-test (push) Failing after 41s
Gitea CI/CD / Set Tag Name (push) Has been skipped
Gitea CI/CD / docker-build-and-push (push) Has been skipped
Gitea CI/CD / Create Tag (push) Has been skipped
Reviewed-on: #35
2025-10-29 20:55:35 +01:00
a6d992c874 Merge branch 'feature/service-detection' of https://git.triggermeelmo.com/Watcher/watcher into feature/service-detection 2025-10-29 20:54:43 +01:00
8475083897 ServiceMetrics angefangen 2025-10-29 20:52:16 +01:00
f1287314f4 Parse funktioniert mit List<String> als Eingabe 2025-10-29 20:51:04 +01:00
e1684d4484 Frontend Changes + Model angepasst 2025-10-29 12:11:03 +01:00
f113147972 renamed variables 2025-10-29 08:50:16 +01:00
6354075a5c Merge pull request 'feature/service-detection' (#33) from feature/service-detection into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 57s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 7m19s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #33
2025-10-29 07:57:44 +01:00
7c85e338fa Json Parsing 2025-10-29 07:57:13 +01:00
c01401eb07 Logik um ServiceDiscovery Payload zu verarbeiten soweit gemacht, JSON Input wird aber nocht nicht geparsed 2025-10-28 15:25:08 +01:00
7c06f7ee06 Merge pull request 'enhancement/Dokumentation' (#31) from enhancement/Dokumentation into staging
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 54s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 6m27s
Gitea CI/CD / Create Tag (push) Successful in 5s
Reviewed-on: #31
2025-10-27 11:51:36 +01:00
6eed3c764e swagger working /api/v1/swagger 2025-10-21 19:15:16 +02:00
f207440ae6 added swagger for automatic API Documentation 2025-10-10 18:00:41 +02:00
ef187f8750 Basic Structure of new Background Service SystemManagement 2025-10-09 20:27:37 +02:00
16 changed files with 1043 additions and 52 deletions

View File

@@ -3,10 +3,39 @@ import urllib.request
url = "http://localhost:5000/monitoring/service-discovery"
payload = {
"ServerId": 1,
"ContainerId": "aaaaaaaaaaaa",
"Name": "test-Name",
"Image": "test-Image"
"Server_id": 2,
"Containers": [
{
"id": "3e74abf5ce30",
"image": "hello-world:latest",
"name": "serene_nightingale"
},
{
"id": "83cd9d461690",
"image": "postgres:latest",
"name": "distracted_feistel"
},
{
"id": "b296c2ed1213",
"image": "postgres:latest",
"name": "mystifying_jackson"
},
{
"id": "69568181d576",
"image": "hello-world:latest",
"name": "romantic_driscoll"
},
{
"id": "67c37a2b1791",
"image": "hello-world:latest",
"name": "asdf"
},
{
"id": "8f39bae1e316",
"image": "hello-world:latest",
"name": "distracted_mirzakhani"
}
]
}
data = json.dumps(payload).encode("utf-8")

View File

@@ -1,8 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@@ -78,12 +82,15 @@ public class MetricDto
public double NET_Out { get; set; } // Bytes/s
}
public class ServiceDto
public class DockerServiceDto
{
public int ServerId { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
public String? ContainerId { get; set; }
public String? Image { get; set; }
public String? Name { get; set; }
public required int Server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
public required JsonElement Containers { get; set; }
}
public class DockerServiceMetricDto
{
}
[ApiController]
@@ -224,7 +231,7 @@ public class MonitoringController : Controller
// Endpoint, an dem Agents Ihre laufenden Services registrieren
[HttpPost("service-discovery")]
public async Task<IActionResult> ServiceDetection([FromBody] ServiceDto dto)
public async Task<IActionResult> ServiceDetection([FromBody] DockerServiceDto dto)
{
// Gültigkeit des Payloads prüfen
if (!ModelState.IsValid)
@@ -234,33 +241,113 @@ public class MonitoringController : Controller
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("Ungültiger ServiceDetection-Payload.");
return BadRequest(new { error = "Ungültiger Payload", details = errors });
_logger.LogError("Invalid ServiceDetection-Payload.");
return BadRequest(new { error = "Invalid Payload", details = errors });
}
// Metrics für Container, die zu ServerId gehören, löschen
List<Container> newContainers =
JsonSerializer.Deserialize<List<Container>>(dto.Containers.GetRawText())
?? new List<Container>();
// Container, die zu ServerId gehören löschen
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 neu in Datenbank einlesen
}
// Liste aller Container, die bereits der übergebenen ServerId zugewiesen sind
List<Container> 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
{
Container container = new Container
{
ServerId = dto.ServerId,
ContainerId = dto.ContainerId,
Image = dto.Name,
Name = dto.Name
};
_logger.LogInformation(container.Name + " added for Host " + container.ServerId);
_context.Containers.Add(container);
_context.Containers.Add(c);
await _context.SaveChangesAsync();
_logger.LogInformation(c.Name + " added for Host " + c.ServerId);
}
catch (SqliteException e)
{
_logger.LogError("ServiceDiscovery failed: " + e.Message);
_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();
}
// Endpoint, an den der Agent die Metrics der registrierten Container schickt
public async Task<IActionResult> ServiceMetrics([FromBody] DockerServiceMetricDto 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 });
}
// Liste an Metrics aus der dto erstellen
List<ContainerMetric> metrics = new List<ContainerMetric>();
// Metrics in die Datenbank eintragen
try
{
foreach (ContainerMetric m in metrics)
{
_context.ContainerMetrics.Add(m);
await _context.SaveChangesAsync();
// _logger.LogInformation(m. + " added for Host " + c.ServerId);
return Ok();
}
}
catch (SqliteException e)
{
_logger.LogError(e.Message);
return StatusCode(500);
}
return Ok();
@@ -268,7 +355,6 @@ public class MonitoringController : Controller
}
// Durchschnittliche Werte Berechnen
[HttpGet("median")]
public async Task<IActionResult> CalculateMedian(string Metric, int HoursToMonitor, int ServerId)
{
// Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll
@@ -368,4 +454,13 @@ public class MonitoringController : Controller
return metric_input;
}
private List<Container> ParseServiceDiscoveryInput(int server_id, List<Container> containers)
{
List<Container> containerList = new List<Container>();
// JSON-Objekt auslesen und Container-Objekte erstellen
return containerList;
}
}

View File

@@ -26,6 +26,8 @@ public class AppDbContext : DbContext
public DbSet<Tag> Tags { get; set; }
public DbSet<ContainerMetric> ContainerMetrics { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

View File

@@ -0,0 +1,355 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Watcher.Data;
#nullable disable
namespace Watcher.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20251029105954_container-changed")]
partial class containerchanged
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ContainerId")
.HasColumnType("TEXT");
b.Property<string>("Image")
.HasColumnType("TEXT");
b.Property<int?>("ImageId")
.HasColumnType("INTEGER");
b.Property<bool>("IsRunning")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("ServerId")
.HasColumnType("INTEGER");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("TagId");
b.ToTable("Containers");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Tag")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER");
b.Property<string>("Level")
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ContainerId");
b.HasIndex("ServerId");
b.ToTable("LogEvents");
});
modelBuilder.Entity("Watcher.Models.Metric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Size")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Usage")
.HasColumnType("REAL");
b.Property<double>("GPU_Load")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Size")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Usage")
.HasColumnType("REAL");
b.Property<double>("NET_In")
.HasColumnType("REAL");
b.Property<double>("NET_Out")
.HasColumnType("REAL");
b.Property<double>("RAM_Load")
.HasColumnType("REAL");
b.Property<double>("RAM_Size")
.HasColumnType("REAL");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Metrics");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<int>("CpuCores")
.HasColumnType("INTEGER");
b.Property<string>("CpuType")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<double>("DISK_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("DiskSpace")
.HasColumnType("TEXT");
b.Property<double>("Disk_Usage_Critical")
.HasColumnType("REAL");
b.Property<double>("Disk_Usage_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("GpuType")
.HasColumnType("TEXT");
b.Property<string>("IPAddress")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<bool>("IsVerified")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastSeen")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("RAM_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("RAM_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("RamSize")
.HasColumnType("REAL");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("TagId");
b.ToTable("Servers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Tags");
});
modelBuilder.Entity("Watcher.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<string>("IdentityProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("LastLogin")
.HasColumnType("TEXT");
b.Property<string>("OIDC_Id")
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.HasOne("Watcher.Models.Image", null)
.WithMany("Containers")
.HasForeignKey("ImageId");
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Containers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.HasOne("Watcher.Models.Container", "Container")
.WithMany()
.HasForeignKey("ContainerId");
b.HasOne("Watcher.Models.Server", "Server")
.WithMany()
.HasForeignKey("ServerId");
b.Navigation("Container");
b.Navigation("Server");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Servers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Navigation("Containers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Navigation("Containers");
b.Navigation("Servers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Watcher.Migrations
{
/// <inheritdoc />
public partial class containerchanged : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -0,0 +1,355 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Watcher.Data;
#nullable disable
namespace Watcher.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20251029125404_ContainerMetrics-Added")]
partial class ContainerMetricsAdded
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ContainerId")
.HasColumnType("TEXT");
b.Property<string>("Image")
.HasColumnType("TEXT");
b.Property<int?>("ImageId")
.HasColumnType("INTEGER");
b.Property<bool>("IsRunning")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("ServerId")
.HasColumnType("INTEGER");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("TagId");
b.ToTable("Containers");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Tag")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int?>("ContainerId")
.HasColumnType("INTEGER");
b.Property<string>("Level")
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ContainerId");
b.HasIndex("ServerId");
b.ToTable("LogEvents");
});
modelBuilder.Entity("Watcher.Models.Metric", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Size")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp")
.HasColumnType("REAL");
b.Property<double>("DISK_Usage")
.HasColumnType("REAL");
b.Property<double>("GPU_Load")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Size")
.HasColumnType("REAL");
b.Property<double>("GPU_Vram_Usage")
.HasColumnType("REAL");
b.Property<double>("NET_In")
.HasColumnType("REAL");
b.Property<double>("NET_Out")
.HasColumnType("REAL");
b.Property<double>("RAM_Load")
.HasColumnType("REAL");
b.Property<double>("RAM_Size")
.HasColumnType("REAL");
b.Property<int?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Metrics");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<double>("CPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("CPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<int>("CpuCores")
.HasColumnType("INTEGER");
b.Property<string>("CpuType")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<double>("DISK_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("DISK_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("DiskSpace")
.HasColumnType("TEXT");
b.Property<double>("Disk_Usage_Critical")
.HasColumnType("REAL");
b.Property<double>("Disk_Usage_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Critical")
.HasColumnType("REAL");
b.Property<double>("GPU_Temp_Warning")
.HasColumnType("REAL");
b.Property<string>("GpuType")
.HasColumnType("TEXT");
b.Property<string>("IPAddress")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<bool>("IsVerified")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastSeen")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("RAM_Load_Critical")
.HasColumnType("REAL");
b.Property<double>("RAM_Load_Warning")
.HasColumnType("REAL");
b.Property<double>("RamSize")
.HasColumnType("REAL");
b.Property<int?>("TagId")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("TagId");
b.ToTable("Servers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Tags");
});
modelBuilder.Entity("Watcher.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT");
b.Property<string>("IdentityProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("LastLogin")
.HasColumnType("TEXT");
b.Property<string>("OIDC_Id")
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("Watcher.Models.Container", b =>
{
b.HasOne("Watcher.Models.Image", null)
.WithMany("Containers")
.HasForeignKey("ImageId");
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Containers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.LogEvent", b =>
{
b.HasOne("Watcher.Models.Container", "Container")
.WithMany()
.HasForeignKey("ContainerId");
b.HasOne("Watcher.Models.Server", "Server")
.WithMany()
.HasForeignKey("ServerId");
b.Navigation("Container");
b.Navigation("Server");
});
modelBuilder.Entity("Watcher.Models.Server", b =>
{
b.HasOne("Watcher.Models.Tag", null)
.WithMany("Servers")
.HasForeignKey("TagId");
});
modelBuilder.Entity("Watcher.Models.Image", b =>
{
b.Navigation("Containers");
});
modelBuilder.Entity("Watcher.Models.Tag", b =>
{
b.Navigation("Containers");
b.Navigation("Servers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Watcher.Migrations
{
/// <inheritdoc />
public partial class ContainerMetricsAdded : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -1,14 +1,23 @@
using System.Text.Json.Serialization;
namespace Watcher.Models;
public class Container
{
public int Id { get; set; }
[JsonPropertyName("Server_id")]
public int ServerId { get; set; }
[JsonPropertyName("id")]
public String? ContainerId { get; set; }
[JsonPropertyName("image")]
public String? Image { get; set; }
[JsonPropertyName("name")]
public String? Name { get; set; }
// keine Variable, die vom Agent übergeben wird. Ein container ist immer Running, die Variable dient nur für die Übersicht
// auf dem Dashboard.
public Boolean IsRunning { get; set; } = true;
}

View File

@@ -0,0 +1,24 @@
namespace Watcher.Models;
public class ContainerMetric
{
// Metric Metadata
public int Id { get; set; }
public DateTime Timestamp { get; set; }
// Zuordnung zu einem Container -- Foreign Key
public int? ContainerId { get; set; }
// CPU-Daten
public double CPU_Load { get; set; } = 0.0; // %
public double CPU_Temp { get; set; } = 0.0; // deg C
// RAM-Daten
public double RAM_Size { get; set; } = 0.0; // GB
public double RAM_Load { get; set; } = 0.0; // %
}

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Serilog;
@@ -36,11 +37,17 @@ builder.Services.AddHttpContextAccessor();
// Storage Singleton
builder.Services.AddSingleton<IDashboardStore, DashboardStore>();
builder.Services.AddSingleton<ISystemStore, SystemStore>();
// Background Services
builder.Services.AddHostedService<NetworkCheck>();
builder.Services.AddHostedService<DatabaseCheck>();
// Swagger API-Dokumentation
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Watcher-Server API", Version = "v1" });
});
// ---------- Konfiguration ----------
DotNetEnv.Env.Load();
@@ -204,19 +211,27 @@ using (var scope = app.Services.CreateScope())
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// 🔹 Swagger aktivieren
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Watcher-Server API v1");
options.RoutePrefix = "api/v1/swagger";
});
// 🔹 Authentifizierung & Autorisierung
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
// 🔹 MVC-Routing
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"

View File

@@ -0,0 +1,9 @@
namespace Watcher.Services;
public interface ISystemStore
{
Boolean NewVersionAvailable { get; set; }
Double DatabaseSize { get; set; }
}

View File

@@ -0,0 +1,42 @@
using System.Net.Http;
namespace Watcher.Services;
public class SystemManagement : BackgroundService
{
private readonly ILogger<NetworkCheck> _logger;
private ISystemStore _SystemStore;
public SystemManagement(ILogger<NetworkCheck> logger, ISystemStore SystemStore)
{
_logger = logger;
_SystemStore = SystemStore;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Todo: Umstellen auf einmal alle 24h
var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// Hintergrundprozess abwarten
await checkForNewDockerImageVersion();
// 5 Sekdunden Offset
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
public Task checkForNewDockerImageVersion()
{
return Task.CompletedTask;
}
public Task createDailySqliteBackup()
{
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,9 @@
namespace Watcher.Services;
public class SystemStore: ISystemStore
{
public Boolean NewVersionAvailable { get; set; }
public Double DatabaseSize { get; set; }
}

View File

@@ -12,20 +12,22 @@
<div >
<table class="ServiceList">
<tr>
<th>Service</th>
<th>HostServer</th>
<th>Status</th>
<th>Deployment</th>
<th></th>
<th>Container-ID</th>
<th>Name</th>
<th>Image</th>
<th>Host-ID</th>
<th>Aktionen</th>
</tr>
@foreach (Container container in Model.Containers)
{
<tr class="ServiceRow">
<td>@container.ContainerId</td>
<td>@container.Name</td>
<td><a class="ServiceEntry" href="/Server/Details/${containerId}">@container.ServerId</a></td>
<td>nicht automatisiert</td>
<td>Docker</td>
<td>@container.Image</td>
<!-- TODO: Link Hardcoded -->
<td><a class="ServiceEntry" href="/Server/Details/2">@container.ServerId</a></td>
<td>nicht verfügbar</td>
</tr>
}
</table>

View File

@@ -23,6 +23,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
</ItemGroup>
</Project>