PlanTempusAdmin/Pages/Index.cshtml

334 lines
12 KiB
Text
Raw Normal View History

2026-02-03 00:17:08 +01:00
@page
@model PlanTempusAdmin.Pages.IndexModel
@{
ViewData["Title"] = "Dashboard";
}
<div class="page-header">
<h1 class="page-title">Dashboard</h1>
<p class="page-subtitle">PlanTempus SaaS Infrastructure Status</p>
</div>
<!-- Top Status Grid -->
<div class="status-grid">
<div class="status-item">
<div class="status-label">Caddy Server</div>
<div class="status-value @(Model.CaddyRunning ? "success" : "danger")">
@(Model.CaddyRunning ? "ONLINE" : "OFFLINE")
</div>
<div class="status-detail">@Model.HostCount hosts</div>
</div>
<div class="status-item">
<div class="status-label">Forgejo</div>
<div class="status-value @(Model.ForgejoConnected ? "success" : "danger")">
@if (Model.ForgejoConnected)
{
@if (Model.ForgejoDashboard.RunningNow > 0)
{
<span class="pulse">●</span> @Model.ForgejoDashboard.RunningNow <text> CI</text>
}
else
{
<text>ONLINE</text>
}
}
else
{
<text>OFFLINE</text>
}
</div>
<div class="status-detail">@Model.ForgejoDashboard.TotalRepos repos</div>
</div>
<div class="status-item">
<div class="status-label">Backup</div>
<div class="status-value @(Model.BackupDbConnected ? (Model.LastBackupOk ? "success" : "warning") : "danger")">
@if (Model.BackupDbConnected)
{
@(Model.LastBackupAge ?? "INGEN")
}
else
{
<text>OFFLINE</text>
}
</div>
<div class="status-detail">@Model.BackupSummary.SuccessfulBackups OK / @Model.BackupSummary.FailedBackups fejl</div>
</div>
<div class="status-item">
<div class="status-label">Azure Storage</div>
<div class="status-value @(Model.AzureConnected ? "success" : "danger")">
@(Model.AzureConnected ? "ONLINE" : "OFFLINE")
</div>
<div class="status-detail">@FormatSize(Model.AzureDashboard.TotalSize)</div>
</div>
</div>
<!-- Cards Grid -->
<div class="dashboard-cards mt-2">
<!-- Forgejo Card -->
<div class="card">
<div class="card-header">Forgejo Git</div>
<div class="card-body">
@if (Model.ForgejoConnected)
{
<div class="mini-stats">
<div class="mini-stat">
<div class="mini-stat-value">@Model.ForgejoDashboard.TotalRepos</div>
<div class="mini-stat-label">Repos</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@Model.ForgejoDashboard.TotalOpenIssues</div>
<div class="mini-stat-label">Issues</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@Model.ForgejoDashboard.TotalOpenPRs</div>
<div class="mini-stat-label">PRs</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@FormatSize(Model.ForgejoDashboard.TotalSize * 1024)</div>
<div class="mini-stat-label">Størrelse</div>
</div>
</div>
@if (Model.ForgejoDashboard.RunningRuns.Count > 0)
{
<div class="running-list mt-1">
@foreach (var run in Model.ForgejoDashboard.RunningRuns.Take(3))
{
<div class="running-item">
<span class="pulse">●</span>
<code>@run.FullRepoName</code>
<span class="text-muted">@run.WorkflowId</span>
</div>
}
</div>
}
<a href="/Forgejo" class="btn btn-primary mt-1">Se detaljer</a>
}
else
{
<p class="text-danger">Forgejo database ikke tilgængelig</p>
}
</div>
</div>
<!-- Caddy Card -->
<div class="card">
<div class="card-header">Caddy Reverse Proxy</div>
<div class="card-body">
@if (Model.CaddyRunning)
{
<p>Server kører og håndterer <strong>@Model.HostCount</strong> host(s).</p>
<a href="/Caddy" class="btn btn-primary mt-1">Se detaljer</a>
}
else
{
<p class="text-danger">Caddy server er ikke tilgængelig.</p>
}
</div>
</div>
<!-- Backup Card -->
<div class="card">
<div class="card-header">Backup Status</div>
<div class="card-body">
@if (Model.BackupDbConnected)
{
<div class="mini-stats">
<div class="mini-stat">
<div class="mini-stat-value success">@Model.BackupSummary.SuccessfulBackups</div>
<div class="mini-stat-label">Success</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value @(Model.BackupSummary.FailedBackups > 0 ? "danger" : "")">@Model.BackupSummary.FailedBackups</div>
<div class="mini-stat-label">Fejlet</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@FormatSize(Model.BackupSummary.TotalSizeBytes)</div>
<div class="mini-stat-label">Total</div>
</div>
</div>
<a href="/Backup" class="btn btn-primary mt-1">Se detaljer</a>
}
else
{
<p class="text-danger">Backup database er ikke tilgængelig.</p>
}
</div>
</div>
<!-- CI/CD Card -->
<div class="card">
<div class="card-header">CI/CD Actions</div>
<div class="card-body">
@if (Model.ForgejoConnected)
{
<div class="mini-stats">
<div class="mini-stat">
<div class="mini-stat-value success">@Model.ForgejoDashboard.SuccessfulRuns</div>
<div class="mini-stat-label">Success</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value @(Model.ForgejoDashboard.FailedRunsCount > 0 ? "danger" : "")">@Model.ForgejoDashboard.FailedRunsCount</div>
<div class="mini-stat-label">Fejlet</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@Model.ForgejoDashboard.RunsThisWeek</div>
<div class="mini-stat-label">Denne uge</div>
</div>
</div>
@if (Model.ForgejoDashboard.FailedRuns.Count > 0)
{
<div class="failed-list mt-1">
<div class="failed-header">Seneste fejl:</div>
@foreach (var run in Model.ForgejoDashboard.FailedRuns.Take(2))
{
<div class="failed-item">
<span class="badge badge-danger">FEJL</span>
<code>@run.FullRepoName</code>
</div>
}
</div>
}
<a href="/Forgejo/Actions" class="btn btn-primary mt-1">Se detaljer</a>
}
else
{
<p class="text-muted">Ikke tilgængelig</p>
}
</div>
</div>
<!-- Azure Storage Card -->
<div class="card">
<div class="card-header">Azure Blob Storage</div>
<div class="card-body">
@if (Model.AzureConnected)
{
<div class="mini-stats">
<div class="mini-stat">
<div class="mini-stat-value">@Model.AzureDashboard.TotalContainers</div>
<div class="mini-stat-label">Containers</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@Model.AzureDashboard.TotalBlobs</div>
<div class="mini-stat-label">Blobs</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@FormatSize(Model.AzureDashboard.TotalSize)</div>
<div class="mini-stat-label">Størrelse</div>
</div>
<div class="mini-stat">
<div class="mini-stat-value">@Model.AzureDashboard.BackupFileCount</div>
<div class="mini-stat-label">Backups</div>
</div>
</div>
@if (Model.AzureDashboard.RecentBlobs.Count > 0)
{
<div class="running-list mt-1">
@foreach (var blob in Model.AzureDashboard.RecentBlobs.Take(2))
{
<div class="running-item">
<span>📄</span>
<code>@blob.FileName</code>
<span class="text-muted">@FormatSize(blob.Size)</span>
</div>
}
</div>
}
<a href="/Azure" class="btn btn-primary mt-1">Se detaljer</a>
}
else
{
<p class="text-danger">Azure Storage ikke tilgængelig</p>
}
</div>
</div>
</div>
<style>
.status-detail {
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
}
.pulse {
animation: pulse 1.5s ease-in-out infinite;
color: var(--warning-color);
}
@@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.dashboard-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.mini-stats {
display: flex;
gap: 16px;
}
.mini-stat {
text-align: center;
}
.mini-stat-value {
font-size: 18px;
font-weight: bold;
}
.mini-stat-value.success { color: var(--success-color); }
.mini-stat-value.danger { color: var(--danger-color); }
.mini-stat-label {
font-size: 10px;
color: var(--muted-color);
text-transform: uppercase;
}
.running-list, .failed-list {
border-top: 1px solid var(--border-color);
padding-top: 8px;
}
.running-item, .failed-item {
font-size: 11px;
padding: 4px 0;
display: flex;
align-items: center;
gap: 6px;
}
.failed-header {
font-size: 10px;
color: var(--muted-color);
margin-bottom: 4px;
}
@@media (max-width: 900px) {
.dashboard-cards {
grid-template-columns: 1fr;
}
}
</style>
@functions {
string FormatSize(long bytes)
{
if (bytes == 0) return "0 B";
var sizes = new[] { "B", "KB", "MB", "GB", "TB" };
var i = (int)Math.Floor(Math.Log(bytes) / Math.Log(1024));
return $"{Math.Round(bytes / Math.Pow(1024, i), 1)} {sizes[i]}";
}
}