PlanTempusAdmin/Pages/Forgejo/Actions.cshtml
Janus C. H. Knudsen 08f8150064 Refactors page styles into separate CSS files
Extracts inline styles from Razor pages into modular CSS files
Adds new CSS files for components and specific page styles
Improves code organization and maintainability by separating styling concerns

Updates layout to include new CSS files and optional style sections
2026-02-03 15:55:44 +01:00

228 lines
9.3 KiB
Text

@page
@model PlanTempusAdmin.Pages.Forgejo.ActionsModel
@{
ViewData["Title"] = "Forgejo Actions";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/forgejo.css" asp-append-version="true" />
}
<div class="page-header">
<h1 class="page-title">CI/CD Actions</h1>
<p class="page-subtitle">Workflow runs og statistik</p>
</div>
@if (!Model.IsConnected)
{
<div class="card">
<div class="card-body">
<p class="text-danger">Kan ikke forbinde til Forgejo database</p>
</div>
</div>
}
else
{
var running = Model.Runs.Count(r => r.Status == 2);
var successful = Model.Runs.Count(r => r.Status == 3);
var failed = Model.Runs.Count(r => r.Status == 4);
<!-- Stats -->
<div class="status-grid">
<div class="status-item">
<div class="status-label">Status</div>
<div class="status-value @(running > 0 ? "warning" : "success")">
@if (running > 0)
{
<span class="pulse">●</span> @running <text> KØRER</text>
}
else
{
<text>IDLE</text>
}
</div>
</div>
<div class="status-item">
<div class="status-label">Workflows</div>
<div class="status-value">@Model.Stats.Count</div>
</div>
<div class="status-item">
<div class="status-label">Success Rate</div>
<div class="status-value @(Model.Stats.Count > 0 ? (Model.Stats.Average(s => s.SuccessRate) >= 90 ? "success" : "warning") : "")">
@(Model.Stats.Count > 0 ? Model.Stats.Average(s => s.SuccessRate).ToString("0") : "0")%
</div>
</div>
<div class="status-item">
<div class="status-label">Viste Runs</div>
<div class="status-value">@Model.Runs.Count</div>
</div>
</div>
<!-- Running Now -->
@if (running > 0)
{
<div class="card mt-2 running-card">
<div class="card-header">
<span class="pulse">●</span> Kørende Workflows
</div>
<table class="table">
<thead>
<tr>
<th>Repository</th>
<th>Workflow</th>
<th>Branch</th>
<th>Trigger</th>
<th>Startet</th>
<th>Varighed</th>
</tr>
</thead>
<tbody>
@foreach (var run in Model.Runs.Where(r => r.Status == 2))
{
<tr>
<td><code>@run.FullRepoName</code></td>
<td>@run.WorkflowId</td>
<td><code>@run.Ref.Replace("refs/heads/", "").Replace("refs/tags/", "tag:")</code></td>
<td>
<span class="badge">@run.Event</span>
@if (!string.IsNullOrEmpty(run.TriggerUser))
{
<span class="text-muted">by @run.TriggerUser</span>
}
</td>
<td>@run.Started?.ToString("HH:mm:ss")</td>
<td class="warning">@FormatDuration(run.Duration)</td>
</tr>
}
</tbody>
</table>
</div>
}
<div class="dashboard-grid mt-2">
<!-- Left Column: Workflow Stats -->
<div class="dashboard-col">
<div class="card">
<div class="card-header">Workflow Statistik</div>
<table class="table">
<thead>
<tr>
<th>Workflow</th>
<th>Repo</th>
<th>Runs</th>
<th>Rate</th>
<th>Avg. Tid</th>
<th>Sidst</th>
</tr>
</thead>
<tbody>
@foreach (var stat in Model.Stats.OrderByDescending(s => s.TotalRuns))
{
<tr>
<td><code>@stat.WorkflowId</code></td>
<td><code>@stat.RepoName</code></td>
<td>
<span class="success">@stat.Successful</span>
@if (stat.Failed > 0)
{
<span class="text-danger">/ @stat.Failed</span>
}
</td>
<td>
<div class="rate-bar">
<div class="rate-fill @(stat.SuccessRate >= 90 ? "good" : stat.SuccessRate >= 70 ? "warn" : "bad")"
style="width: @stat.SuccessRate.ToString("0")%"></div>
</div>
<span class="rate-text">@stat.SuccessRate.ToString("0")%</span>
</td>
<td>@(stat.AvgDurationSeconds.HasValue ? FormatDuration(TimeSpan.FromSeconds(stat.AvgDurationSeconds.Value)) : "-")</td>
<td>@FormatTimeAgo(stat.LastRun)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<!-- Right Column: Recent Runs -->
<div class="dashboard-col">
<div class="card">
<div class="card-header">Seneste Workflow Runs</div>
<table class="table">
<thead>
<tr>
<th>Status</th>
<th>Repository</th>
<th>Workflow</th>
<th>Event</th>
<th>Tid</th>
<th>Varighed</th>
</tr>
</thead>
<tbody>
@foreach (var run in Model.Runs.Take(50))
{
<tr class="@(run.Status == 4 ? "failed-row" : "")">
<td>
@switch (run.Status)
{
case 1:
<span class="badge">VENTER</span>
break;
case 2:
<span class="badge badge-warning">KØRER</span>
break;
case 3:
<span class="badge badge-success">OK</span>
break;
case 4:
<span class="badge badge-danger">FEJL</span>
break;
case 5:
<span class="badge">ANNULLERET</span>
break;
case 6:
<span class="badge">SKIPPED</span>
break;
default:
<span class="badge">@run.StatusText</span>
break;
}
</td>
<td><code>@run.FullRepoName</code></td>
<td>@run.WorkflowId</td>
<td>
<span class="badge">@run.Event</span>
</td>
<td>@FormatTimeAgo(run.Created)</td>
<td>@FormatDuration(run.Duration)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
@functions {
string FormatDuration(TimeSpan? duration)
{
if (!duration.HasValue) return "-";
var d = duration.Value;
if (d.TotalHours >= 1) return $"{(int)d.TotalHours}t {d.Minutes}m";
if (d.TotalMinutes >= 1) return $"{(int)d.TotalMinutes}m {d.Seconds}s";
return $"{d.Seconds}s";
}
string FormatTimeAgo(DateTime? time)
{
if (!time.HasValue) return "-";
var diff = DateTime.Now - time.Value;
if (diff.TotalMinutes < 1) return "lige nu";
if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes}m siden";
if (diff.TotalHours < 24) return $"{(int)diff.TotalHours}t siden";
if (diff.TotalDays < 7) return $"{(int)diff.TotalDays}d siden";
return time.Value.ToString("dd/MM HH:mm");
}
}