diff --git a/Dockerfile b/Dockerfile index 100b962..fad3ec9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,25 @@ -# Build-Stage +# 1. Build-Phase: SDK-Image FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /app # Projektdateien kopieren und Abhängigkeiten wiederherstellen -COPY *.csproj ./ +COPY *.sln . +COPY Watcher/*.csproj ./Watcher/ RUN dotnet restore -# Restlichen Code kopieren und veröffentlichen -COPY . ./ -RUN dotnet publish -c Release -o out +# Restliche Dateien kopieren und Build ausführen +COPY Watcher/. ./Watcher/ +WORKDIR /app/Watcher +RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -# Runtime-Stage -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +# 2. Laufzeit-Phase: ASP.NET Core Runtime +FROM mcr.microsoft.com/dotnet/aspnet:9.0 WORKDIR /app +COPY --from=build /app/publish . -COPY --from=build /app/out ./ - -EXPOSE 5000 -EXPOSE 5001 +# Exponiere Port 80 und 443 (HTTP + HTTPS) +EXPOSE 80 +EXPOSE 443 +# Anwendung starten ENTRYPOINT ["dotnet", "Watcher.dll"] 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/HeartbeatController.cs b/Watcher/Controllers/HeartbeatController.cs new file mode 100644 index 0000000..0a362e3 --- /dev/null +++ b/Watcher/Controllers/HeartbeatController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.CodeAnalysis; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using Watcher.Data; +using Watcher.Models; +using Watcher.ViewModels; + +[ApiController] +[Route("[controller]")] +public class HeartbeatController : Controller +{ + + private readonly AppDbContext _context; + + public HeartbeatController(AppDbContext context) + { + _context = context; + } + + [HttpPost("receive")] + public async Task Receive([FromForm] int serverId) + { + var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == serverId); + + // Je nachdem, ob dier Datenbankeintrag für einen neuen Server vorher oder nacher passiert, ist das hier überflüssig + if (server != null) + { + server.LastSeen = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + return Ok(); + + } + else + { + return BadRequest(); + } + + } +} 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/Controllers/ServerController.cs b/Watcher/Controllers/ServerController.cs new file mode 100644 index 0000000..e4d89a3 --- /dev/null +++ b/Watcher/Controllers/ServerController.cs @@ -0,0 +1,106 @@ +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, + Type = vm.Type, + IsOnline = vm.IsOnline, + }; + + _context.Servers.Add(server); + await _context.SaveChangesAsync(); + + return RedirectToAction("Overview"); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(int id) + { + var server = await _context.Servers.FindAsync(id); + if (server == null) + { + return NotFound(); + } + + _context.Servers.Remove(server); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Overview)); + } + + // GET: Server/Edit/5 + public async Task EditServer(int id) + { + var server = await _context.Servers.FindAsync(id); + if (server == null) return NotFound(); + + var vm = new EditServerViewModel + { + Name = server.Name, + IPAddress = server.IPAddress, + Type = server.Type + }; + + return View(vm); + } + + // POST: Server/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EditServer(int id, EditServerViewModel vm) + { + if (ModelState.IsValid) + { + var server = await _context.Servers.FindAsync(id); + if (server == null) return NotFound(); + + server.Name = vm.Name; + server.IPAddress = vm.IPAddress; + server.Type = vm.Type; + + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Overview)); + } + + return View(vm); + } + +} 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/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/20250615102649_ServerAnpassung.Designer.cs b/Watcher/Migrations/20250615102649_ServerAnpassung.Designer.cs new file mode 100644 index 0000000..acd59d2 --- /dev/null +++ b/Watcher/Migrations/20250615102649_ServerAnpassung.Designer.cs @@ -0,0 +1,293 @@ +// +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("20250615102649_ServerAnpassung")] + partial class ServerAnpassung + { + /// + 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("LastSeen") + .HasColumnType("datetime(6)"); + + 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/20250615102649_ServerAnpassung.cs b/Watcher/Migrations/20250615102649_ServerAnpassung.cs new file mode 100644 index 0000000..c8e9931 --- /dev/null +++ b/Watcher/Migrations/20250615102649_ServerAnpassung.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class ServerAnpassung : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastSeen", + table: "Servers", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastSeen", + table: "Servers"); + } + } +} diff --git a/Watcher/Migrations/20250615114821_ServerCleanUp.Designer.cs b/Watcher/Migrations/20250615114821_ServerCleanUp.Designer.cs new file mode 100644 index 0000000..98add17 --- /dev/null +++ b/Watcher/Migrations/20250615114821_ServerCleanUp.Designer.cs @@ -0,0 +1,288 @@ +// +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("20250615114821_ServerCleanUp")] + partial class ServerCleanUp + { + /// + 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("Description") + .HasColumnType("longtext"); + + b.Property("IPAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsOnline") + .HasColumnType("tinyint(1)"); + + b.Property("LastSeen") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .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/20250615114821_ServerCleanUp.cs b/Watcher/Migrations/20250615114821_ServerCleanUp.cs new file mode 100644 index 0000000..3d03083 --- /dev/null +++ b/Watcher/Migrations/20250615114821_ServerCleanUp.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Watcher.Migrations +{ + /// + public partial class ServerCleanUp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Hostname", + table: "Servers"); + + migrationBuilder.DropColumn( + name: "Status", + table: "Servers"); + + migrationBuilder.AddColumn( + name: "Description", + table: "Servers", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Description", + table: "Servers"); + + migrationBuilder.AddColumn( + name: "Hostname", + table: "Servers", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "Status", + table: "Servers", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/Watcher/Migrations/AppDbContextModelSnapshot.cs b/Watcher/Migrations/AppDbContextModelSnapshot.cs index 888f967..1117443 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"); @@ -145,18 +148,23 @@ namespace Watcher.Migrations b.Property("CreatedAt") .HasColumnType("datetime(6)"); - b.Property("Hostname") + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IPAddress") .IsRequired() .HasColumnType("longtext"); + b.Property("IsOnline") + .HasColumnType("tinyint(1)"); + + b.Property("LastSeen") + .HasColumnType("datetime(6)"); + b.Property("Name") .IsRequired() .HasColumnType("longtext"); - b.Property("Status") - .IsRequired() - .HasColumnType("longtext"); - b.Property("TagId") .HasColumnType("int"); @@ -212,13 +220,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 ac3e8e7..2d5ab6a 100644 --- a/Watcher/Models/Container.cs +++ b/Watcher/Models/Container.cs @@ -8,9 +8,13 @@ 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; 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..84a2f9d 100644 --- a/Watcher/Models/Server.cs +++ b/Watcher/Models/Server.cs @@ -6,12 +6,18 @@ public class Server public string Name { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; + public string IPAddress { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - 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; + + public DateTime LastSeen { get; set; } + + public string? Description { get; set; } + + } 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/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/ViewModels/EditServerViewModel.cs b/Watcher/ViewModels/EditServerViewModel.cs new file mode 100644 index 0000000..f94df04 --- /dev/null +++ b/Watcher/ViewModels/EditServerViewModel.cs @@ -0,0 +1,10 @@ +namespace Watcher.ViewModels; + +public class EditServerViewModel +{ + public string Name { get; set; } = string.Empty; + + public string IPAddress { get; set; } = string.Empty; + + public string Type { get; set; } = "VPS"; +} 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 a92733d..627e7d3 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 +
+
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..97471fb --- /dev/null +++ b/Watcher/Views/Server/AddServer.cshtml @@ -0,0 +1,42 @@ +@model Watcher.ViewModels.AddServerViewModel +@{ + ViewData["Title"] = "Neuen Server hinzufügen"; +} + +
+

Neuen Server hinzufügen

+ +
+
+ + + +
+ +
+ + + +
+ +
+ + +
+ +
+ Abbrechen + +
+
+
+ +@section Scripts { + +} diff --git a/Watcher/Views/Server/EditServer.cshtml b/Watcher/Views/Server/EditServer.cshtml new file mode 100644 index 0000000..d3f3fe5 --- /dev/null +++ b/Watcher/Views/Server/EditServer.cshtml @@ -0,0 +1,31 @@ +@model Watcher.ViewModels.EditServerViewModel; + +@{ + ViewData["Title"] = "Server bearbeiten"; +} + +

Server bearbeiten

+ +
+ @Html.AntiForgeryToken() + +
+ + +
+ +
+ + +
+ + + +
+ + +
+ + + Abbrechen +
diff --git a/Watcher/Views/Server/overview.cshtml b/Watcher/Views/Server/overview.cshtml new file mode 100644 index 0000000..1b537f2 --- /dev/null +++ b/Watcher/Views/Server/overview.cshtml @@ -0,0 +1,48 @@ +@model Watcher.ViewModels.ServerOverviewViewModel +@{ + ViewData["Title"] = "Serverübersicht"; +} + +
+

Serverübersicht

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

(#@s.Id) @s.Name

+
+ + @(s.IsOnline ? "Online" : "Offline") + +
+ +
+
IP: @s.IPAddress
+
Typ: @s.Type
+
Status: @((s.IsOnline) ? "Online" : "Offline")
+
Erstellt: @s.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")
+
Last-Seen: @(s.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm") ?? "Never")
+
+ +
+ Bearbeiten + +
+ +
+
+
+ } +
\ No newline at end of file diff --git a/Watcher/Views/Shared/_Layout.cshtml b/Watcher/Views/Shared/_Layout.cshtml index 1911a3f..9104883 100644 --- a/Watcher/Views/Shared/_Layout.cshtml +++ b/Watcher/Views/Shared/_Layout.cshtml @@ -68,10 +68,10 @@ Uptime @@ -103,6 +103,9 @@ + + @RenderSection("Scripts", required: false) + diff --git a/Watcher/appsettings.json b/Watcher/appsettings.json index c59c356..81748ed 100644 --- a/Watcher/appsettings.json +++ b/Watcher/appsettings.json @@ -7,7 +7,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "server=192.168.178.68;port=3306;database=watcher;user=monitoringuser;password=ssp123;" + "DefaultConnection": "server=100.64.0.5;port=3306;database=watcher;user=monitoringuser;password=ssp123;" }, "Authentication": { "PocketID": {