Initial commit

This commit is contained in:
Janus C. H. Knudsen 2026-02-03 00:17:08 +01:00
commit 77d35ff965
51 changed files with 5591 additions and 0 deletions

235
Pages/Azure/Index.cshtml Normal file
View file

@ -0,0 +1,235 @@
@page
@model PlanTempusAdmin.Pages.Azure.IndexModel
@{
ViewData["Title"] = "Azure Storage";
}
<div class="page-header">
<h1 class="page-title">Azure Blob Storage</h1>
<p class="page-subtitle">@Model.Dashboard.AccountName</p>
</div>
@if (!Model.IsConnected)
{
<div class="card">
<div class="card-body">
<p class="text-danger">Kan ikke forbinde til Azure Storage</p>
<p class="text-muted">Tjek at <code>ConnectionStrings:AzureStorage</code> er konfigureret i appsettings.json</p>
</div>
</div>
}
else
{
var d = Model.Dashboard;
<!-- Hero Stats -->
<div class="status-grid">
<div class="status-item">
<div class="status-label">Status</div>
<div class="status-value success">ONLINE</div>
<div class="status-detail">@d.AccountName</div>
</div>
<div class="status-item">
<div class="status-label">Containers</div>
<div class="status-value">@d.TotalContainers</div>
<div class="status-detail">@d.TotalBlobs blobs total</div>
</div>
<div class="status-item">
<div class="status-label">Total Størrelse</div>
<div class="status-value">@FormatBytes(d.TotalSize)</div>
<div class="status-detail">Backup: @FormatBytes(d.BackupTotalSize)</div>
</div>
<div class="status-item">
<div class="status-label">Sidste Upload</div>
<div class="status-value @(d.LastBackupUpload.HasValue && (DateTimeOffset.Now - d.LastBackupUpload.Value).TotalHours < 24 ? "success" : "warning")">
@FormatTimeAgo(d.LastBackupUpload)
</div>
<div class="status-detail">@d.BackupFileCount backup filer</div>
</div>
</div>
<div class="dashboard-grid mt-2">
<!-- Containers -->
<div class="card">
<div class="card-header">Containers</div>
<table class="table">
<thead>
<tr>
<th>Navn</th>
<th>Blobs</th>
<th>Størrelse</th>
<th>Ændret</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var container in d.Containers)
{
<tr>
<td>
@if (container.Name.Contains("backup", StringComparison.OrdinalIgnoreCase))
{
<span class="container-icon">💾</span>
}
else
{
<span class="container-icon">📦</span>
}
<code>@container.Name</code>
</td>
<td>@container.BlobCount</td>
<td>@FormatBytes(container.TotalSize)</td>
<td>@FormatTimeAgo(container.LastModified)</td>
<td>
<a href="/Azure/Container?name=@container.Name" class="btn btn-sm">Åbn</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Recent Uploads -->
<div class="card">
<div class="card-header">Seneste Uploads</div>
<div class="card-body compact-list">
@if (d.RecentBlobs.Count == 0)
{
<p class="text-muted">Ingen filer endnu</p>
}
@foreach (var blob in d.RecentBlobs)
{
<div class="list-item">
<div class="item-main">
<span class="file-icon">@GetFileIcon(blob.Name)</span>
<code class="blob-name" title="@blob.Name">@blob.FileName</code>
@if (!string.IsNullOrEmpty(blob.AccessTier))
{
<span class="badge badge-tier">@blob.AccessTier</span>
}
</div>
<div class="item-meta">
@FormatBytes(blob.Size) · @FormatTimeAgo(blob.LastModified)
@if (!string.IsNullOrEmpty(blob.Directory))
{
<text>· @blob.Directory</text>
}
</div>
</div>
}
</div>
</div>
</div>
<!-- Last Updated -->
<div class="last-updated mt-2">
Opdateret: @DateTime.Now.ToString("HH:mm:ss")
</div>
}
<style>
.status-detail {
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
}
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.container-icon, .file-icon {
margin-right: 8px;
}
.compact-list {
padding: 8px 16px !important;
}
.list-item {
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
}
.list-item:last-child {
border-bottom: none;
}
.item-main {
display: flex;
align-items: center;
gap: 8px;
}
.item-meta {
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
padding-left: 28px;
}
.blob-name {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.badge-tier {
background: var(--border-color);
color: var(--muted-color);
font-size: 9px;
padding: 2px 6px;
}
.last-updated {
text-align: center;
font-size: 10px;
color: var(--muted-color);
padding: 16px;
}
.success { color: var(--success-color); }
.warning { color: var(--warning-color); }
@@media (max-width: 1000px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
</style>
@functions {
string FormatBytes(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]}";
}
string FormatTimeAgo(DateTimeOffset? time)
{
if (!time.HasValue) return "-";
var diff = DateTimeOffset.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");
}
string GetFileIcon(string name)
{
if (name.EndsWith(".tar.gz")) return "📦";
if (name.EndsWith(".gz") || name.EndsWith(".zip") || name.EndsWith(".7z")) return "📦";
if (name.EndsWith(".sql")) return "🐘";
if (name.EndsWith(".bak")) return "💾";
if (name.EndsWith(".log")) return "📜";
if (name.EndsWith(".json")) return "📋";
if (name.EndsWith(".xml")) return "📄";
return "📄";
}
}