diff --git a/Watcher/Controllers/ContainerController.cs b/Watcher/Controllers/ContainerController.cs new file mode 100644 index 0000000..624a1a8 --- /dev/null +++ b/Watcher/Controllers/ContainerController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Watcher.Data; +using Watcher.Models; +using Watcher.ViewModels; + +namespace Watcher.Controllers; + + +public class ContainerController : Controller +{ + private readonly AppDbContext _context; + + public ContainerController(AppDbContext context) + { + _context = context; + } + + public async Task Overview() +{ + var containers = await _context.Containers.ToListAsync(); + + var viewModel = new ContainerOverviewViewModel + { + Containers = containers + }; + + return View(viewModel); +} + + + [HttpGet] + public IActionResult AddContainer() + { + return View(); + } + + [HttpPost] + public async Task AddContainer(AddContainerViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + + var container = new Container + { + Name = vm.Name, + Image = vm.Image, + Status = vm.Status, + Hostname = vm.Hostname, + IsRunning = vm.IsRunning, + CreatedAt = DateTime.UtcNow + }; + + _context.Containers.Add(container); + await _context.SaveChangesAsync(); + + return RedirectToAction("Index"); + } +} diff --git a/Watcher/Controllers/ServerController.cs b/Watcher/Controllers/ServerController.cs new file mode 100644 index 0000000..1d7535c --- /dev/null +++ b/Watcher/Controllers/ServerController.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Watcher.Data; +using Watcher.Models; +using Watcher.ViewModels; + +[Authorize] +public class ServerController : Controller +{ + private readonly AppDbContext _context; + + public ServerController(AppDbContext context) + { + _context = context; + } + + public async Task Overview() + { + var vm = new ServerOverviewViewModel + { + Servers = await _context.Servers.OrderBy(s => s.Id).ToListAsync() + }; + + return View(vm); + } + + [HttpGet] + public IActionResult AddServer() + { + return View(); + } + + [HttpPost] + public async Task AddServer(AddServerViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + + var server = new Server + { + Name = vm.Name, + IPAddress = vm.IPAddress, + Hostname = vm.Hostname, + Status = vm.Status, + Type = vm.Type, + IsOnline = vm.IsOnline, + CreatedAt = DateTime.UtcNow + }; + + _context.Servers.Add(server); + await _context.SaveChangesAsync(); + + return RedirectToAction("Overview"); + } +} diff --git a/Watcher/Migrations/20250614224113_Container-Server-Update.Designer.cs b/Watcher/Migrations/20250614224113_Container-Server-Update.Designer.cs new file mode 100644 index 0000000..0ac9bbe --- /dev/null +++ b/Watcher/Migrations/20250614224113_Container-Server-Update.Designer.cs @@ -0,0 +1,290 @@ +// +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("20250614224113_Container-Server-Update")] + partial class ContainerServerUpdate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Watcher.Models.Container", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Hostname") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("IsRunning") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagId") + .HasColumnType("int"); + + b.Property("Type") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.HasIndex("TagId"); + + b.ToTable("Containers"); + }); + + modelBuilder.Entity("Watcher.Models.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Tag") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Watcher.Models.LogEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ContainerId") + .HasColumnType("int"); + + b.Property("Level") + .HasColumnType("longtext"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ContainerId"); + + b.HasIndex("ServerId"); + + b.ToTable("LogEvents"); + }); + + modelBuilder.Entity("Watcher.Models.Metric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ContainerId") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("longtext"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.HasIndex("ContainerId"); + + b.HasIndex("ServerId"); + + b.ToTable("Metrics"); + }); + + modelBuilder.Entity("Watcher.Models.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Hostname") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IPAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsOnline") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagId") + .HasColumnType("int"); + + b.Property("Type") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("TagId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Watcher.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Watcher.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("LastLogin") + .HasColumnType("datetime(6)"); + + b.Property("PocketId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PreferredUsername") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Watcher.Models.Container", b => + { + b.HasOne("Watcher.Models.Image", "Image") + .WithMany("Containers") + .HasForeignKey("ImageId"); + + b.HasOne("Watcher.Models.Tag", null) + .WithMany("Containers") + .HasForeignKey("TagId"); + + b.Navigation("Image"); + }); + + 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.Metric", 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 + } + } +} diff --git a/Watcher/Migrations/20250614224113_Container-Server-Update.cs b/Watcher/Migrations/20250614224113_Container-Server-Update.cs new file mode 100644 index 0000000..b485dfd --- /dev/null +++ b/Watcher/Migrations/20250614224113_Container-Server-Update.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class ContainerServerUpdate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IPAddress", + table: "Servers", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IPAddress", + table: "Servers"); + } + } +} diff --git a/Watcher/Migrations/AppDbContextModelSnapshot.cs b/Watcher/Migrations/AppDbContextModelSnapshot.cs index 2ba3088..f444135 100644 --- a/Watcher/Migrations/AppDbContextModelSnapshot.cs +++ b/Watcher/Migrations/AppDbContextModelSnapshot.cs @@ -152,6 +152,10 @@ namespace Watcher.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("IPAddress") + .IsRequired() + .HasColumnType("longtext"); + b.Property("IsOnline") .HasColumnType("tinyint(1)"); @@ -218,13 +222,15 @@ namespace Watcher.Migrations modelBuilder.Entity("Watcher.Models.Container", b => { - b.HasOne("Watcher.Models.Image", null) + b.HasOne("Watcher.Models.Image", "Image") .WithMany("Containers") .HasForeignKey("ImageId"); b.HasOne("Watcher.Models.Tag", null) .WithMany("Containers") .HasForeignKey("TagId"); + + b.Navigation("Image"); }); modelBuilder.Entity("Watcher.Models.LogEvent", b => diff --git a/Watcher/Models/Container.cs b/Watcher/Models/Container.cs index 77c7f6c..2d5ab6a 100644 --- a/Watcher/Models/Container.cs +++ b/Watcher/Models/Container.cs @@ -8,6 +8,8 @@ public class Container public string Status { get; set; } = string.Empty; + public Image? Image { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public string Hostname { get; set; } = string.Empty; diff --git a/Watcher/Models/Server.cs b/Watcher/Models/Server.cs index 5c4700b..ac10a8d 100644 --- a/Watcher/Models/Server.cs +++ b/Watcher/Models/Server.cs @@ -6,6 +6,8 @@ public class Server public string Name { get; set; } = string.Empty; + public string IPAddress { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; diff --git a/Watcher/ViewModels/AddContainerViewModel.cs b/Watcher/ViewModels/AddContainerViewModel.cs new file mode 100644 index 0000000..38c2017 --- /dev/null +++ b/Watcher/ViewModels/AddContainerViewModel.cs @@ -0,0 +1,13 @@ +using Watcher.Models; + +namespace Watcher.ViewModels; +public class AddContainerViewModel +{ + public string Name { get; set; } = string.Empty; + public Image? Image { get; set; } + public string Status { get; set; } = string.Empty; + public string Hostname { get; set; } = string.Empty; + public string IPAddress { get; set; } = string.Empty; + public string ServerName { get; set; } = string.Empty; // oder ID, je nach Relation + public bool IsRunning { get; set; } = false; +} diff --git a/Watcher/ViewModels/AddServerViewModel.cs b/Watcher/ViewModels/AddServerViewModel.cs new file mode 100644 index 0000000..d582028 --- /dev/null +++ b/Watcher/ViewModels/AddServerViewModel.cs @@ -0,0 +1,12 @@ + +namespace Watcher.ViewModels; + +public class AddServerViewModel +{ + public string Name { get; set; } = string.Empty; + public string IPAddress { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public string Hostname { get; set; } = string.Empty; + public string Type { get; set; } = "VPS"; + public bool IsOnline { get; set; } = false; +} diff --git a/Watcher/ViewModels/ContainerOverviewViewModel.cs b/Watcher/ViewModels/ContainerOverviewViewModel.cs new file mode 100644 index 0000000..e075100 --- /dev/null +++ b/Watcher/ViewModels/ContainerOverviewViewModel.cs @@ -0,0 +1,10 @@ +using Watcher.Models; + +namespace Watcher.ViewModels +{ + public class ContainerOverviewViewModel +{ + public List Containers { get; set; } = new(); +} +} + diff --git a/Watcher/ViewModels/ServerOverviewViewModel.cs b/Watcher/ViewModels/ServerOverviewViewModel.cs new file mode 100644 index 0000000..72be054 --- /dev/null +++ b/Watcher/ViewModels/ServerOverviewViewModel.cs @@ -0,0 +1,10 @@ +using Watcher.Models; + +namespace Watcher.ViewModels + +{ + public class ServerOverviewViewModel + { + public List Servers { get; set; } = new(); + } +} diff --git a/Watcher/Views/Container/AddContainer.cshtml b/Watcher/Views/Container/AddContainer.cshtml new file mode 100644 index 0000000..29ab3c2 --- /dev/null +++ b/Watcher/Views/Container/AddContainer.cshtml @@ -0,0 +1,22 @@ +@model Watcher.ViewModels.AddContainerViewModel +@{ + ViewData["Title"] = "Neuen Container hinzufügen"; +} + +

Neuen Container hinzufügen

+ +
+
+ + +
+
+ + +
+
+ + +
+ +
diff --git a/Watcher/Views/Container/Overview.cshtml b/Watcher/Views/Container/Overview.cshtml new file mode 100644 index 0000000..d9b9cec --- /dev/null +++ b/Watcher/Views/Container/Overview.cshtml @@ -0,0 +1,36 @@ +@model Watcher.ViewModels.ContainerOverviewViewModel +@{ + ViewData["Title"] = "Containerübersicht"; +} + +
+

Containerübersicht

+ + + Container hinzufügen + +
+ +
+ @foreach (var container in Model.Containers) + { +
+

@container.Name

+

Image: @container.Image

+

Hostname: @container.Hostname

+

Status: @container.Status

+

+ Läuft: + + @(container.IsRunning ? "Ja" : "Nein") + +

+ +
+ } +
+ diff --git a/Watcher/Views/Home/Index.cshtml b/Watcher/Views/Home/Index.cshtml index e692458..627e7d3 100644 --- a/Watcher/Views/Home/Index.cshtml +++ b/Watcher/Views/Home/Index.cshtml @@ -10,14 +10,14 @@

Server

🟢 Online: @Model.ActiveServers

🔴 Offline: @Model.OfflineServers

- → Zu den Servern + → Zu den Servern

Container

🟢 Laufend: @Model.RunningContainers

🔴 Fehlerhaft: @Model.FailedContainers

- → Zu den Containern + → Zu den Containern
diff --git a/Watcher/Views/Home/Servers.cshtml b/Watcher/Views/Home/Servers.cshtml deleted file mode 100644 index e69de29..0000000 diff --git a/Watcher/Views/Server/AddServer.cshtml b/Watcher/Views/Server/AddServer.cshtml new file mode 100644 index 0000000..3945bab --- /dev/null +++ b/Watcher/Views/Server/AddServer.cshtml @@ -0,0 +1,42 @@ +@using Watcher.ViewModels + +@model AddServerViewModel +@{ + ViewData["Title"] = "Neuen Server hinzufügen"; +} + +

Neuen Server hinzufügen

+ +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
diff --git a/Watcher/Views/Server/overview.cshtml b/Watcher/Views/Server/overview.cshtml new file mode 100644 index 0000000..5b42554 --- /dev/null +++ b/Watcher/Views/Server/overview.cshtml @@ -0,0 +1,43 @@ +@model Watcher.ViewModels.ServerOverviewViewModel +@{ + ViewData["Title"] = "Serverübersicht"; +} + +
+

Serverübersicht

+ + + Server hinzufügen + +
+ + + +
+@foreach (var s in Model.Servers) +{ +
+
+
+

@s.Name

+

@s.Hostname

+
+ + @(s.IsOnline ? "Online" : "Offline") + +
+ +
+
IP: Ip
+
Typ: @s.Type
+
Status: @(string.IsNullOrEmpty(s.Status) ? "-" : s.Status)
+
Erstellt: @s.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")
+
+ + +
+} +
\ No newline at end of file