CPU Load Graph geht

This commit is contained in:
2025-08-11 21:23:04 +02:00
parent 09e53a2a9d
commit de9b8daadc
7 changed files with 147 additions and 239 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Per-Use special Files and Directories # Per-Use special Files and Directories
/persistance/*.db /persistance/*.db
/persistance/*.db-shm
/persistance/*.db-wal
/logs/*.log /logs/*.log
*.env *.env
/wwwroot/downloads/sqlite/*.sql /wwwroot/downloads/sqlite/*.sql

View File

@@ -231,7 +231,7 @@ public class MonitoringController : Controller
return NotFound(); return NotFound();
} }
[HttpPost("cpu-usage")] [HttpGet("cpu-usage")]
public async Task<IActionResult> GetCpuUsageData() public async Task<IActionResult> GetCpuUsageData()
{ {
var oneDayAgo = DateTime.UtcNow.AddDays(-1); var oneDayAgo = DateTime.UtcNow.AddDays(-1);
@@ -242,8 +242,8 @@ public class MonitoringController : Controller
{ {
// Hier die Formatierung anpassen // Hier die Formatierung anpassen
// 'o' ist der Standard-Formatbezeichner für ISO 8601-Format mit Zeitzone // 'o' ist der Standard-Formatbezeichner für ISO 8601-Format mit Zeitzone
Timestamp = m.Timestamp.ToUniversalTime().ToString("o"), // Wichtig: ToUniversalTime() für Konsistenz label = m.Timestamp.ToUniversalTime().ToString("o"), // Wichtig: ToUniversalTime() für Konsistenz
CpuUsage = m.CPU_Load data = m.CPU_Load
}) })
.ToListAsync(); .ToListAsync();

View File

@@ -6,6 +6,7 @@ using Watcher.Models;
using Watcher.ViewModels; using Watcher.ViewModels;
[Authorize] [Authorize]
[Route("Server")]
public class ServerController : Controller public class ServerController : Controller
{ {
private readonly AppDbContext _context; private readonly AppDbContext _context;
@@ -18,6 +19,7 @@ public class ServerController : Controller
_logger = logger; _logger = logger;
} }
[HttpGet("Overview")]
public async Task<IActionResult> Overview() public async Task<IActionResult> Overview()
{ {
var vm = new ServerOverviewViewModel var vm = new ServerOverviewViewModel
@@ -126,7 +128,7 @@ public class ServerController : Controller
} }
// GET: Server/Details/5 // GET: Server/Details/5
[HttpGet] [HttpGet("Details")]
public async Task<IActionResult> Details(int id) public async Task<IActionResult> Details(int id)
{ {
@@ -153,6 +155,7 @@ public class ServerController : Controller
return View(vm); return View(vm);
} }
[HttpGet("ServerCardPartial")]
public async Task<IActionResult> ServerCardsPartial() public async Task<IActionResult> ServerCardsPartial()
{ {
var servers = _context.Servers.ToList(); var servers = _context.Servers.ToList();
@@ -166,17 +169,11 @@ public class ServerController : Controller
return PartialView("_ServerCard", servers); return PartialView("_ServerCard", servers);
} }
public async Task<IActionResult> ServerDetailsPartial() [HttpGet("ServerDetailPartial")]
public IActionResult ServerDetailPartial()
{ {
var servers = _context.Servers.ToList(); _logger.LogInformation("Server Detail Seite neu geladen");
return PartialView("_ServerDetails");
foreach (var server in servers)
{
server.IsOnline = (DateTime.UtcNow - server.LastSeen).TotalSeconds <= 120;
await _context.SaveChangesAsync();
}
return PartialView("_ServerDetails", servers);
} }

View File

@@ -4,31 +4,135 @@
} }
<div id="server-cards-container"> <div id="server-cards-container">
@await Html.PartialAsync("_ServerDetails") <!-- @await Html.PartialAsync("_ServerDetails") -->
<div class="container mt-4"></div>
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-hdd-network me-2 text-primary"></i>Serverdetails: @Model.Name
</h5>
<span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")">
<i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
@(Model.IsOnline ? "Online" : "Offline")
</span>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-3">ID</dt>
<dd class="col-sm-9">@Model.Id</dd>
<dt class="col-sm-3">IP-Adresse</dt>
<dd class="col-sm-9">@Model.IPAddress</dd>
<dt class="col-sm-3">Typ</dt>
<dd class="col-sm-9">@Model.Type</dd>
<dt class="col-sm-3">Erstellt am</dt>
<dd class="col-sm-9">@Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
<dt class="col-sm-3">Zuletzt gesehen</dt>
<dd class="col-sm-9">@Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
</dl>
</div>
<div class="card-footer text-end">
<a href="/Download/File/Linux/heartbeat" class="btn btn-success">
🖥️ Linux Agent
</a>
<a asp-action="EditServer" asp-route-id="@Model.Id" class="btn btn-outline-primary me-2">
<i class="bi bi-pencil"></i> Bearbeiten
</a>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post" class="d-inline"
onsubmit="return confirm('Diesen Server wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div>
</div>
<div class="mt-4">
<h6><i class="bi bi-graph-up me-1"></i>CPU Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 1000px; width: 100%">
<canvas id="cpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas>
</div>
</div>
</div>
</div> </div>
@section Scripts { @section Scripts {
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="~/js/server_uptime.js"></script>
<script> <script>
async function loadServerStats() { document.addEventListener("DOMContentLoaded", function () {
try { const ctx = document.getElementById('cpuUsageChart').getContext('2d');
const response = await fetch('/Server/ServerDetailsPartial');
if (response.ok) { const cpuChart = new Chart(ctx, {
const html = await response.text(); type: 'line',
document.getElementById('server-cards-container').innerHTML = html; data: {
} else { labels: [],
console.error('Fehler beim Nachladen der Serverdaten'); datasets: [{
label: 'CPU Last (%)',
data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true,
tension: 0.3,
pointRadius: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: 'Prozent'
} }
},
x: {
title: {
display: true,
text: 'Zeit'
}
}
}
}
});
async function loadCpuData() {
try {
const response = await fetch('/monitoring/cpu-usage');
if (!response.ok) {
throw new Error(`HTTP-Fehler: ${response.status}`);
}
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
console.warn('Keine CPU-Daten empfangen.');
return;
}
cpuChart.data.labels = data.map(d => d.label ?? '');
cpuChart.data.datasets[0].data = data.map(d => Number(d.data));
cpuChart.update();
} catch (err) { } catch (err) {
console.error('Netzwerkfehler beim Nachladen der Serverdaten:', err); console.error('Fehler beim Laden der CPU-Daten:', err);
} }
} }
// Initial laden und dann alle 30 Sekunden // Initiales Laden
loadServerStats(); loadCpuData();
setInterval(loadServerStats, 30000);
// Alle 30 Sekunden aktualisieren
setInterval(loadCpuData, 30000);
});
</script> </script>
} }

View File

@@ -1,74 +0,0 @@
<div class="container mt-4">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-hdd-network me-2 text-primary"></i>Serverdetails: @Model.Name
</h5>
<span class="badge @(Model.IsOnline ? "bg-success" : "bg-danger")">
<i class="bi @(Model.IsOnline ? "bi-check-circle" : "bi-x-circle") me-1"></i>
@(Model.IsOnline ? "Online" : "Offline")
</span>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-3">ID</dt>
<dd class="col-sm-9">@Model.Id</dd>
<dt class="col-sm-3">IP-Adresse</dt>
<dd class="col-sm-9">@Model.IPAddress</dd>
<dt class="col-sm-3">Typ</dt>
<dd class="col-sm-9">@Model.Type</dd>
<dt class="col-sm-3">Erstellt am</dt>
<dd class="col-sm-9">@Model.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
<dt class="col-sm-3">Zuletzt gesehen</dt>
<dd class="col-sm-9">@Model.LastSeen.ToLocalTime().ToString("dd.MM.yyyy HH:mm")</dd>
</dl>
</div>
<div class="card-footer text-end">
<a href="/Download/File/Linux/heartbeat" class="btn btn-success">
🖥️ Linux Agent
</a>
<a asp-action="EditServer" asp-route-id="@Model.Id" class="btn btn-outline-primary me-2">
<i class="bi bi-pencil"></i> Bearbeiten
</a>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post" class="d-inline"
onsubmit="return confirm('Diesen Server wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div>
</div>
<!-- Optional: Bereich für Logs, Diagramme oder weitere Details -->
<div class="mt-4">
<h6><i class="bi bi-graph-up me-1"></i>Uptime letzte 24h</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 250px;">
<canvas id="UptimeChart"></canvas>
</div>
</div>
<div class="mt-4">
<h6><i class="bi bi-graph-up me-1"></i>CPU Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 250px; width: 100%">
<canvas id="cpuUsageChart" style="width: 800px; height: 400px; border: 1px solid red;"></canvas>
</div>
</div>
<div class="mt-4"></div>
<h6><i class="bi bi-graph-up me-1"></i>RAM Last</h6>
<div class="bg-light border rounded p-4 text-center text-muted" style="height: 250px;">
<canvas id="RamLoadChart"></canvas>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.x/build/global/luxon.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.x/dist/chartjs-adapter-luxon.min.js"></script>
<script src="~/js/CpuChart.js"></script>

View File

@@ -17,13 +17,10 @@
</div> </div>
@section Scripts { @section Scripts {
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="~/js/server_uptime.js"></script>
<script> <script>
async function loadServerCards() { async function loadServerCards() {
try { try {
const response = await fetch('/Server/ServerCardsPartial'); const response = await fetch('/Server/ServerCardPartial');
if (response.ok) { if (response.ok) {
const html = await response.text(); const html = await response.text();
document.getElementById('server-cards-container').innerHTML = html; document.getElementById('server-cards-container').innerHTML = html;

View File

@@ -1,118 +0,0 @@
let cpuChartInstance = null; // This will hold your Chart.js instance globally
function fetchDataAndRenderChart() {
console.log('fetchDataAndRenderChart wird aufgerufen...');
fetch('/monitoring/cpu-usage', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
})
.then(response => {
if (!response.ok) {
return response.text().then(text => { throw new Error(text || `HTTP error! status: ${response.status}`); });
}
return response.json();
})
.then(data => {
console.log('Daten erfolgreich geparst:', data);
// --- Crucial check for empty/invalid data ---
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('Keine Datenpunkte vom Server erhalten oder Datenarray ist leer oder nicht im erwarteten Format. Chart wird nicht gezeichnet/aktualisiert.');
// If there's an existing chart but no data, destroy it to clear the canvas
if (cpuChartInstance) {
cpuChartInstance.destroy(); // Destroy the chart instance
cpuChartInstance = null; // Reset the reference
}
return; // Exit the function as there's nothing to plot
}
// --- Data preparation ---
const timestamps = data.map(item => {
// Ensure you're parsing the timestamp string correctly if not ISO 8601 from backend
// Example: '2025-07-30 16:19:58' -> luxon.DateTime.fromFormat(item.timestamp, 'yyyy-LL-dd HH:mm:ss')
// If backend sends ISO 8601 (e.g., '2025-07-30T16:19:58Z'), Luxon handles it automatically:
return item.timestamp; // If already ISO 8601, pass directly
});
const cpuUsages = data.map(item => parseFloat(item.cpuUsage)); // Always ensure CPU usage is a number
console.log('Extrahierte Zeitstempel:', timestamps);
console.log('Extrahierte CPU-Werte:', cpuUsages);
const ctx = document.getElementById('cpuUsageChart')?.getContext('2d');
if (!ctx) {
console.error("Canvas-Element mit ID 'cpuUsageChart' nicht gefunden!");
return;
}
// --- Chart Creation/Update Logic ---
if (cpuChartInstance) {
// Chart already exists, just update its data and refresh
console.log('Bestehender Chart wird aktualisiert...');
cpuChartInstance.data.labels = timestamps;
cpuChartInstance.data.datasets[0].data = cpuUsages;
cpuChartInstance.update(); // Re-render the chart with new data
} else {
// No chart exists yet, create a new one
console.log('Neuer Chart wird erstellt...');
cpuChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: timestamps,
datasets: [{
label: 'CPU Auslastung (%)',
data: cpuUsages,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
fill: false
}]
},
options: {
responsive: true,
scales: {
x: {
type: 'time',
time: {
unit: 'minute', // Or 'hour', 'day'
tooltipFormat: 'HH:mm:ss',
displayFormats: {
minute: 'HH:mm'
}
},
title: {
display: true,
text: 'Zeit'
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'CPU Auslastung (%)'
},
max: 100
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
return context.dataset.label + ': ' + context.parsed.y + '%';
}
}
}
}
}
});
}
})
.catch(error => {
console.error('Catch-Block ausgelöst:', error);
});
}
// Initial call when the DOM is ready
document.addEventListener('DOMContentLoaded', fetchDataAndRenderChart);
// Subsequent calls for real-time updates
setInterval(fetchDataAndRenderChart, 20000); // Update every 20 seconds