2026-02-03 00:17:08 +01:00
|
|
|
@page
|
|
|
|
|
@model PlanTempusAdmin.Pages.Azure.IndexModel
|
|
|
|
|
@{
|
|
|
|
|
ViewData["Title"] = "Azure Storage";
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 15:55:44 +01:00
|
|
|
@section Styles {
|
|
|
|
|
<link rel="stylesheet" href="~/css/pages/azure.css" asp-append-version="true" />
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 00:17:08 +01:00
|
|
|
<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>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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 "📄";
|
|
|
|
|
}
|
|
|
|
|
}
|