diff --git a/Watcher/Controllers/HomeController.cs b/Watcher/Controllers/HomeController.cs index f3d9cf7..83df394 100644 --- a/Watcher/Controllers/HomeController.cs +++ b/Watcher/Controllers/HomeController.cs @@ -1,33 +1,41 @@ -using System.Diagnostics; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Security.Claims; +using Watcher.Data; using Watcher.Models; +using Watcher.ViewModels; -namespace Watcher.Controllers; - -[Authorize] -public class HomeController : Controller +namespace Watcher.Controllers { - private readonly ILogger _logger; - - public HomeController(ILogger logger) + [Authorize] + public class HomeController : Controller { - _logger = logger; - } + private readonly AppDbContext _context; - public IActionResult Index() - { - return View(); - } + public HomeController(AppDbContext context) + { + _context = context; + } - public IActionResult Privacy() - { - return View(); - } + public async Task Index() + { + var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + var user = await _context.Users + .Where(u => u.PocketId == userId) + .FirstOrDefaultAsync(); + + var viewModel = new DashboardViewModel + { + ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline), + OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline), + RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning), + FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning), + LastLogin = user?.LastLogin ?? DateTime.MinValue + }; + + return View(viewModel); + } } } diff --git a/Watcher/Migrations/20250614183243_Server-Container-IsRunning-Value.Designer.cs b/Watcher/Migrations/20250614183243_Server-Container-IsRunning-Value.Designer.cs new file mode 100644 index 0000000..60f4cfb --- /dev/null +++ b/Watcher/Migrations/20250614183243_Server-Container-IsRunning-Value.Designer.cs @@ -0,0 +1,284 @@ +// +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("20250614183243_Server-Container-IsRunning-Value")] + partial class ServerContainerIsRunningValue + { + /// + 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("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", 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.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/20250614183243_Server-Container-IsRunning-Value.cs b/Watcher/Migrations/20250614183243_Server-Container-IsRunning-Value.cs new file mode 100644 index 0000000..e6c9cab --- /dev/null +++ b/Watcher/Migrations/20250614183243_Server-Container-IsRunning-Value.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class ServerContainerIsRunningValue : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsOnline", + table: "Servers", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsRunning", + table: "Containers", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsOnline", + table: "Servers"); + + migrationBuilder.DropColumn( + name: "IsRunning", + table: "Containers"); + } + } +} diff --git a/Watcher/Migrations/AppDbContextModelSnapshot.cs b/Watcher/Migrations/AppDbContextModelSnapshot.cs index 888f967..2ba3088 100644 --- a/Watcher/Migrations/AppDbContextModelSnapshot.cs +++ b/Watcher/Migrations/AppDbContextModelSnapshot.cs @@ -35,6 +35,9 @@ namespace Watcher.Migrations b.Property("ImageId") .HasColumnType("int"); + b.Property("IsRunning") + .HasColumnType("tinyint(1)"); + b.Property("Name") .IsRequired() .HasColumnType("longtext"); @@ -149,6 +152,9 @@ namespace Watcher.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("IsOnline") + .HasColumnType("tinyint(1)"); + b.Property("Name") .IsRequired() .HasColumnType("longtext"); diff --git a/Watcher/Models/Container.cs b/Watcher/Models/Container.cs index ac3e8e7..77c7f6c 100644 --- a/Watcher/Models/Container.cs +++ b/Watcher/Models/Container.cs @@ -13,4 +13,6 @@ public class Container public string Hostname { get; set; } = string.Empty; public string Type { get; set; } = "docker"; // z.B. "docker", "vm", "lxc", etc. + + public Boolean IsRunning { get; set; } = false; } diff --git a/Watcher/Models/Server.cs b/Watcher/Models/Server.cs index c325177..5c4700b 100644 --- a/Watcher/Models/Server.cs +++ b/Watcher/Models/Server.cs @@ -13,5 +13,7 @@ public class Server public string Hostname { get; set; } = string.Empty; // z.B. "VPS", "standalone", "VM", etc. - public string Type { get; set; } = "VPS"; + public string Type { get; set; } = "VPS"; + + public Boolean IsOnline { get; set; } = false; } diff --git a/Watcher/ViewModels/DashboardViewModel.cs b/Watcher/ViewModels/DashboardViewModel.cs new file mode 100644 index 0000000..02bf40f --- /dev/null +++ b/Watcher/ViewModels/DashboardViewModel.cs @@ -0,0 +1,11 @@ +namespace Watcher.ViewModels +{ + public class DashboardViewModel + { + public int ActiveServers { get; set; } + public int OfflineServers { get; set; } + public int RunningContainers { get; set; } + public int FailedContainers { get; set; } + public DateTime LastLogin { get; set; } + } +} diff --git a/Watcher/Views/Home/Index.cshtml b/Watcher/Views/Home/Index.cshtml index a92733d..e692458 100644 --- a/Watcher/Views/Home/Index.cshtml +++ b/Watcher/Views/Home/Index.cshtml @@ -1,6 +1,36 @@ -@{ +@model Watcher.ViewModels.DashboardViewModel +@{ ViewData["Title"] = "Dashboard"; } -

Dashboard

-

Willkommen im Watcher Monitoring Interface!

+

Dashboard

+ +
+
+

Server

+

🟢 Online: @Model.ActiveServers

+

🔴 Offline: @Model.OfflineServers

+ → Zu den Servern +
+ +
+

Container

+

🟢 Laufend: @Model.RunningContainers

+

🔴 Fehlerhaft: @Model.FailedContainers

+ → Zu den Containern +
+ +
+

Uptime letzte 24h

+
+ (Diagramm folgt hier) +
+
+ +
+

Systeminfo

+

Benutzer: @User.FindFirst("preferred_username")?.Value

+

Letzter Login: @Model.LastLogin.ToString("g")

+ → Account-Verwaltung +
+