new Dashboard

This commit is contained in:
2025-08-26 18:54:17 +02:00
parent aa35e83f6b
commit 1c0fab71bb
7 changed files with 220 additions and 53 deletions

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
// Local Namespaces
using Watcher.Data;
using Watcher.Models;
using Watcher.ViewModels;
namespace Watcher.Controllers
@@ -41,7 +42,17 @@ namespace Watcher.Controllers
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
LastLogin = user?.LastLogin ?? DateTime.MinValue,
Servers = await _context.Servers
.OrderBy(s => s.Name)
.ToListAsync(),
RecentEvents = await _context.LogEvents
.OrderByDescending(e => e.Timestamp)
.Take(20)
.ToListAsync(),
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync()
};
return View(viewModel);
@@ -49,7 +60,7 @@ namespace Watcher.Controllers
// Funktion für /Views/Home/Index.cshtml um das DashboardStats-Partial neu zu laden.
// Die Funktion wird nicht direkt aufgerufen, sondern nur der /Home/DashboardStats Endpoint angefragt.
public IActionResult DashboardStats()
public async Task<IActionResult> DashboardStats()
{
var servers = _context.Servers.ToList();
var containers = _context.Containers.ToList();
@@ -58,14 +69,20 @@ namespace Watcher.Controllers
var model = new DashboardViewModel
{
ActiveServers = servers.Count(s => (now - s.LastSeen).TotalSeconds <= 120),
OfflineServers = servers.Count(s => (now - s.LastSeen).TotalSeconds > 120),
//TODO: anwendbar, wenn Container implementiert wurden.
//RunningContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds <= 120),
//FailedContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds > 120),
LastLogin = DateTime.Now
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),
Servers = await _context.Servers
.OrderBy(s => s.Name)
.ToListAsync(),
RecentEvents = await _context.LogEvents
.OrderByDescending(e => e.Timestamp)
.Take(20)
.ToListAsync(),
Containers = await _context.Containers
.OrderBy(s => s.Name)
.ToListAsync()
};
return PartialView("_DashboardStats", model);

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Watcher.Models
{
public class HealthStatus
{
public DateTime Timestamp { get; set; }
public bool NetworkOk { get; set; }
public bool DatabaseOk { get; set; }
public List<string> Issues { get; set; } = new List<string>();
// Optional weitere Checks
public bool ApiOk { get; set; }
public bool DiskOk { get; set; }
}
}

View File

@@ -1,14 +1,12 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Sqlite;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using Watcher.Data;
using Watcher.Models;
//using Watcher.Services;
//using Watcher.Workers;
var builder = WebApplication.CreateBuilder(args);
@@ -32,7 +30,6 @@ builder.Host.UseSerilog();
// Add services to the container.
builder.Services.AddControllersWithViews();
// HttpContentAccessor
builder.Services.AddHttpContextAccessor();
@@ -112,7 +109,11 @@ builder.Services.AddAuthentication()
var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
var principal = ctx.Principal;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
var pocketId = principal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
#pragma warning restore CS8602 // Dereference of a possibly null reference.
var preferredUsername = principal.FindFirst("preferred_username")?.Value;
var email = principal.FindFirst("email")?.Value;

View File

@@ -1,3 +1,5 @@
using Watcher.Models;
namespace Watcher.ViewModels
{
public class DashboardViewModel
@@ -7,5 +9,10 @@ namespace Watcher.ViewModels
public int RunningContainers { get; set; }
public int FailedContainers { get; set; }
public DateTime LastLogin { get; set; }
public List<Server> Servers { get; set; } = new();
public List<LogEvent> RecentEvents { get; set; } = new();
public List<Container> Containers { get; set; } = new();
}
}

View File

@@ -11,25 +11,6 @@
@await Html.PartialAsync("_DashboardStats", Model)
</div>
<div class="row g-4 mt-4">
<div class="col-12">
<div class="card p-3">
<h2 class="h5">
<i class="bi bi-person-circle me-2"></i>Systeminfo
</h2>
<p>
<i class="bi bi-person-badge me-1"></i>
Benutzer: <strong>@User.FindFirst("preferred_username")?.Value</strong>
</p>
<p>
<i class="bi bi-clock me-1"></i>
Letzter Login: <strong>@Model.LastLogin.ToString("g")</strong>
</p>
</div>
</div>
</div>
@section Scripts {
<script>
async function loadDashboardStats() {

View File

@@ -1,25 +1,170 @@
@model Watcher.ViewModels.DashboardViewModel
@{
ViewData["Title"] = "Dashboard";
}
<div class="row g-4">
<div class="col-12 col-md-6">
<div class="bg-white shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-2">Server</h2>
<p>🟢 Online: <strong>@Model.ActiveServers</strong></p>
<p>🔴 Offline: <strong>@Model.OfflineServers</strong></p>
<a href="/Server/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
→ Zu den Servern
</a>
<div class="container-fluid py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<button class="btn btn-outline-secondary btn-sm" onclick="loadDashboardStats()">
<i class="bi bi-arrow-clockwise me-1"></i> Aktualisieren
</button>
</div>
<!-- KPI Cards -->
<div class="row g-4 mb-4">
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-hdd-network text-success fs-2 mb-2"></i>
<h6 class="text-muted">Server Online</h6>
<h3 class="fw-bold">@Model.ActiveServers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-hdd-network-fill text-danger fs-2 mb-2"></i>
<h6 class="text-muted">Server Offline</h6>
<h3 class="fw-bold">@Model.OfflineServers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-box-seam text-primary fs-2 mb-2"></i>
<h6 class="text-muted">Services Running</h6>
<h3 class="fw-bold">@Model.RunningContainers</h3>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-3">
<div class="card shadow-sm border-0 rounded-3 text-center h-100">
<div class="card-body">
<i class="bi bi-exclamation-triangle text-warning fs-2 mb-2"></i>
<h6 class="text-muted">Service Warnungen</h6>
<h3 class="fw-bold">@Model.FailedContainers</h3>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="bg-white shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-2">Container</h2>
<p>🟢 Laufend: <strong>@Model.RunningContainers</strong></p>
<p>🔴 Fehlerhaft: <strong>@Model.FailedContainers</strong></p>
<a href="/Container/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
→ Zu den Containern
</a>
<!-- System Overview & Recent Events -->
<div class="row g-4">
<!-- System Health -->
<div class="col-12 col-lg-6">
<div class="card shadow-sm border-0 rounded-3 h-100">
<div class="card-body">
<h5 class="fw-bold mb-3"><i class="bi bi-heart-pulse me-2 text-danger"></i>Systemstatus</h5>
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Netzwerk</span>
<span class="badge bg-success">OK</span>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-success w-100"></div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span>Datenbank</span>
<span class="badge bg-success">OK</span>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-success w-100"></div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span>API</span>
<span class="badge bg-warning">Langsam</span>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-warning w-75"></div>
</div>
</div>
</div>
</div>
<!-- Recent Events -->
<div class="col-12 col-lg-6">
<div class="card shadow-sm border-0 rounded-3 h-100">
<div class="card-body">
<h5 class="fw-bold mb-3"><i class="bi bi-activity me-2 text-primary"></i>Letzte Ereignisse</h5>
<ul class="list-group list-group-flush small">
@foreach (var log in Model.RecentEvents.Take(1))
{
<li class="list-group-item d-flex align-items-center">
<i class="bi bi-dot fs-4 text-muted me-1"></i>
<span class="text-muted me-2">@log.Timestamp.ToString("HH:mm:ss")</span>
<span>@log</span>
</li>
}
</ul>
</div>
</div>
</div>
<!-- Services -->
<div class="col-12 col-lg-6">
<div class="bg-white shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Services</h2>
<ul class="list-group list-group-flush">
@foreach (var service in Model.Containers)
{
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>@service.Name</span>
<span class="badge @(service.Status == "Running" ? "bg-success" : "bg-warning")">
@service.Status
</span>
</li>
}
</ul>
</div>
</div>
<!-- Server Liste -->
<div class="col-12 col-lg-6">
<div class="bg-white shadow rounded-3 p-4 h-100">
<h2 class="h5 fw-semibold mb-3">Server</h2>
<ul class="list-group list-group-flush">
@foreach (var server in Model.Servers)
{
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>@server.Name</span>
<span class="badge bg-info")">
CPU: 30.45%
</span>
<span class="badge bg-info")">
RAM: 65.09%
</span>
<span class="badge @(server.IsOnline ? "bg-success" : "bg-danger")">
@(server.IsOnline ? "Online" : "Offline")
</span>
</li>
}
</ul>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
async function loadDashboardStats() {
try {
const response = await fetch('/Home/DashboardStats');
if (response.ok) {
const html = await response.text();
document.getElementById('dashboard-stats').innerHTML = html;
} else {
console.error('Fehler beim Laden der Dashboard-Daten');
}
} catch (err) {
console.error('Netzwerkfehler:', err);
}
}
</script>
}

View File

@@ -71,10 +71,10 @@
<a class="nav-link" href="/">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/Server/Overview">Servers</a>
<a class="nav-link" href="#">Netzwerk</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/Container/Overview">Container</a>
<a class="nav-link" href="/Container/Overview">Services</a>
</li>
<!-- Noch nicht implementiert
<li class="nav-item"></li>