closes feature/ui-rework #10
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
// Local Namespaces
|
// Local Namespaces
|
||||||
using Watcher.Data;
|
using Watcher.Data;
|
||||||
|
using Watcher.Models;
|
||||||
using Watcher.ViewModels;
|
using Watcher.ViewModels;
|
||||||
|
|
||||||
namespace Watcher.Controllers
|
namespace Watcher.Controllers
|
||||||
@@ -41,7 +42,17 @@ namespace Watcher.Controllers
|
|||||||
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
|
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
|
||||||
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
|
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
|
||||||
FailedContainers = 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);
|
return View(viewModel);
|
||||||
@@ -49,7 +60,7 @@ namespace Watcher.Controllers
|
|||||||
|
|
||||||
// Funktion für /Views/Home/Index.cshtml um das DashboardStats-Partial neu zu laden.
|
// 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.
|
// 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 servers = _context.Servers.ToList();
|
||||||
var containers = _context.Containers.ToList();
|
var containers = _context.Containers.ToList();
|
||||||
@@ -58,14 +69,20 @@ namespace Watcher.Controllers
|
|||||||
|
|
||||||
var model = new DashboardViewModel
|
var model = new DashboardViewModel
|
||||||
{
|
{
|
||||||
ActiveServers = servers.Count(s => (now - s.LastSeen).TotalSeconds <= 120),
|
ActiveServers = await _context.Servers.CountAsync(s => s.IsOnline),
|
||||||
OfflineServers = servers.Count(s => (now - s.LastSeen).TotalSeconds > 120),
|
OfflineServers = await _context.Servers.CountAsync(s => !s.IsOnline),
|
||||||
|
RunningContainers = await _context.Containers.CountAsync(c => c.IsRunning),
|
||||||
//TODO: anwendbar, wenn Container implementiert wurden.
|
FailedContainers = await _context.Containers.CountAsync(c => !c.IsRunning),
|
||||||
//RunningContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds <= 120),
|
Servers = await _context.Servers
|
||||||
//FailedContainers = containers.Count(c => (now - c.LastSeen).TotalSeconds > 120),
|
.OrderBy(s => s.Name)
|
||||||
|
.ToListAsync(),
|
||||||
LastLogin = DateTime.Now
|
RecentEvents = await _context.LogEvents
|
||||||
|
.OrderByDescending(e => e.Timestamp)
|
||||||
|
.Take(20)
|
||||||
|
.ToListAsync(),
|
||||||
|
Containers = await _context.Containers
|
||||||
|
.OrderBy(s => s.Name)
|
||||||
|
.ToListAsync()
|
||||||
};
|
};
|
||||||
|
|
||||||
return PartialView("_DashboardStats", model);
|
return PartialView("_DashboardStats", model);
|
||||||
|
16
Watcher/Models/HealthStatus.cs
Normal file
16
Watcher/Models/HealthStatus.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
||||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Sqlite;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
using Watcher.Data;
|
using Watcher.Data;
|
||||||
using Watcher.Models;
|
using Watcher.Models;
|
||||||
|
//using Watcher.Services;
|
||||||
|
//using Watcher.Workers;
|
||||||
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -32,7 +30,6 @@ builder.Host.UseSerilog();
|
|||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
|
|
||||||
// HttpContentAccessor
|
// HttpContentAccessor
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
@@ -112,7 +109,11 @@ builder.Services.AddAuthentication()
|
|||||||
var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
|
var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
var principal = ctx.Principal;
|
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;
|
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 preferredUsername = principal.FindFirst("preferred_username")?.Value;
|
||||||
var email = principal.FindFirst("email")?.Value;
|
var email = principal.FindFirst("email")?.Value;
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Watcher.Models;
|
||||||
|
|
||||||
namespace Watcher.ViewModels
|
namespace Watcher.ViewModels
|
||||||
{
|
{
|
||||||
public class DashboardViewModel
|
public class DashboardViewModel
|
||||||
@@ -7,5 +9,10 @@ namespace Watcher.ViewModels
|
|||||||
public int RunningContainers { get; set; }
|
public int RunningContainers { get; set; }
|
||||||
public int FailedContainers { get; set; }
|
public int FailedContainers { get; set; }
|
||||||
public DateTime LastLogin { 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();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,25 +11,6 @@
|
|||||||
@await Html.PartialAsync("_DashboardStats", Model)
|
@await Html.PartialAsync("_DashboardStats", Model)
|
||||||
</div>
|
</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 {
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
async function loadDashboardStats() {
|
async function loadDashboardStats() {
|
||||||
|
@@ -1,25 +1,170 @@
|
|||||||
@model Watcher.ViewModels.DashboardViewModel
|
@model Watcher.ViewModels.DashboardViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Dashboard";
|
||||||
|
}
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="container-fluid py-4">
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<div class="bg-white shadow rounded-3 p-4 h-100">
|
<!-- Header -->
|
||||||
<h2 class="h5 fw-semibold mb-2">Server</h2>
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<p>🟢 Online: <strong>@Model.ActiveServers</strong></p>
|
<button class="btn btn-outline-secondary btn-sm" onclick="loadDashboardStats()">
|
||||||
<p>🔴 Offline: <strong>@Model.OfflineServers</strong></p>
|
<i class="bi bi-arrow-clockwise me-1"></i> Aktualisieren
|
||||||
<a href="/Server/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
|
</button>
|
||||||
→ Zu den Servern
|
</div>
|
||||||
</a>
|
|
||||||
|
<!-- 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>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-6">
|
<!-- System Overview & Recent Events -->
|
||||||
<div class="bg-white shadow rounded-3 p-4 h-100">
|
<div class="row g-4">
|
||||||
<h2 class="h5 fw-semibold mb-2">Container</h2>
|
<!-- System Health -->
|
||||||
<p>🟢 Laufend: <strong>@Model.RunningContainers</strong></p>
|
<div class="col-12 col-lg-6">
|
||||||
<p>🔴 Fehlerhaft: <strong>@Model.FailedContainers</strong></p>
|
<div class="card shadow-sm border-0 rounded-3 h-100">
|
||||||
<a href="/Container/Overview" class="text-primary text-decoration-none mt-2 d-inline-block">
|
<div class="card-body">
|
||||||
→ Zu den Containern
|
<h5 class="fw-bold mb-3"><i class="bi bi-heart-pulse me-2 text-danger"></i>Systemstatus</h5>
|
||||||
</a>
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span>Netzwerk</span>
|
||||||
|
<span class="badge bg-success">OK</span>
|
||||||
</div>
|
</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>
|
||||||
</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>
|
||||||
|
}
|
||||||
|
@@ -71,10 +71,10 @@
|
|||||||
<a class="nav-link" href="/">Dashboard</a>
|
<a class="nav-link" href="/">Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/Server/Overview">Servers</a>
|
<a class="nav-link" href="#">Netzwerk</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/Container/Overview">Container</a>
|
<a class="nav-link" href="/Container/Overview">Services</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- Noch nicht implementiert
|
<!-- Noch nicht implementiert
|
||||||
<li class="nav-item"></li>
|
<li class="nav-item"></li>
|
||||||
|
Reference in New Issue
Block a user