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
This commit is contained in:
Janus C. H. Knudsen 2026-02-03 15:55:44 +01:00
parent 77d35ff965
commit 08f8150064
12 changed files with 577 additions and 555 deletions

View file

@ -4,6 +4,10 @@
ViewData["Title"] = $"Container: {Model.Name}";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/azure.css" asp-append-version="true" />
}
<div class="page-header">
<div class="breadcrumb">
<a href="/Azure">Azure Storage</a>
@ -128,122 +132,6 @@ else
</div>
}
<style>
.breadcrumb {
font-size: 12px;
color: var(--muted-color);
margin-bottom: 8px;
}
.breadcrumb a {
color: var(--primary-color);
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
.breadcrumb .separator {
margin: 0 6px;
color: var(--muted-color);
}
.folder-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
padding: 16px;
}
.folder-item {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: var(--bg-secondary);
border-radius: 4px;
text-decoration: none;
color: inherit;
transition: background 0.2s;
}
.folder-item:hover {
background: var(--border-color);
}
.folder-icon {
font-size: 20px;
}
.folder-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-icon {
margin-right: 8px;
}
.blob-name {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
.header-meta {
float: right;
font-weight: normal;
font-size: 11px;
color: var(--muted-color);
}
.badge-tier {
font-size: 9px;
padding: 2px 6px;
}
.badge-tier.hot {
background: rgba(240, 165, 0, 0.2);
color: var(--warning-color);
}
.badge-tier.cool {
background: rgba(0, 123, 255, 0.2);
color: #007bff;
}
.badge-tier.archive {
background: rgba(108, 117, 125, 0.2);
color: #6c757d;
}
.actions {
white-space: nowrap;
}
.actions .btn {
padding: 4px 8px;
font-size: 12px;
}
.btn-danger {
background: transparent;
border-color: var(--danger-color);
color: var(--danger-color);
}
.btn-danger:hover {
background: var(--danger-color);
color: white;
}
</style>
@functions {
string FormatBytes(long bytes)
{

View file

@ -4,6 +4,10 @@
ViewData["Title"] = "Azure Storage";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/azure.css" asp-append-version="true" />
}
<div class="page-header">
<h1 class="page-title">Azure Blob Storage</h1>
<p class="page-subtitle">@Model.Dashboard.AccountName</p>
@ -127,80 +131,6 @@ else
</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)
{

View file

@ -4,6 +4,10 @@
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>
@ -201,74 +205,6 @@ else
</div>
}
<style>
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.dashboard-col {
display: flex;
flex-direction: column;
gap: 16px;
}
.pulse {
animation: pulse 1.5s ease-in-out infinite;
color: var(--warning-color);
}
@@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.running-card {
border-color: var(--warning-color);
}
.running-card .card-header {
background: linear-gradient(90deg, rgba(240, 165, 0, 0.1), transparent);
}
.rate-bar {
display: inline-block;
width: 40px;
height: 6px;
background: var(--border-color);
border-radius: 3px;
overflow: hidden;
vertical-align: middle;
}
.rate-fill {
height: 100%;
}
.rate-fill.good { background: var(--success-color); }
.rate-fill.warn { background: var(--warning-color); }
.rate-fill.bad { background: var(--danger-color); }
.rate-text {
font-size: 10px;
margin-left: 4px;
}
.failed-row {
background: rgba(220, 53, 69, 0.05);
}
.success { color: var(--success-color); }
.warning { color: var(--warning-color); }
@@media (max-width: 1400px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
</style>
@functions {
string FormatDuration(TimeSpan? duration)
{

View file

@ -4,6 +4,10 @@
ViewData["Title"] = "Forgejo Oversigt";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/forgejo.css" asp-append-version="true" />
}
<div class="page-header">
<h1 class="page-title">Forgejo Oversigt</h1>
<p class="page-subtitle">Git repositories og CI/CD status</p>
@ -285,163 +289,6 @@ else
</div>
}
<style>
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.dashboard-col {
display: flex;
flex-direction: column;
gap: 16px;
}
.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; }
}
.running-card {
border-color: var(--warning-color);
}
.running-card .card-header {
background: linear-gradient(90deg, rgba(240, 165, 0, 0.1), transparent);
}
.error-card {
border-color: var(--danger-color);
}
.error-card .card-header {
background: linear-gradient(90deg, rgba(220, 53, 69, 0.1), transparent);
}
.stat-bars {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-bar-item {
display: flex;
align-items: center;
gap: 8px;
}
.stat-bar-label {
width: 70px;
font-size: 11px;
}
.stat-bar {
flex: 1;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
}
.stat-bar-fill {
height: 100%;
border-radius: 4px;
}
.stat-bar-value {
width: 30px;
text-align: right;
font-size: 11px;
font-weight: bold;
}
.ci-stats {
display: flex;
justify-content: space-around;
text-align: center;
}
.ci-stat-value {
font-size: 24px;
font-weight: bold;
}
.ci-stat-value.success { color: var(--success-color); }
.ci-stat-value.danger { color: var(--danger-color); }
.ci-stat-label {
font-size: 10px;
color: var(--muted-color);
text-transform: uppercase;
}
.ci-rate-bar {
height: 8px;
background: var(--danger-color);
border-radius: 4px;
overflow: hidden;
}
.ci-rate-success {
height: 100%;
background: var(--success-color);
}
.ci-rate-label {
text-align: center;
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
}
.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: 45px;
}
.success { color: var(--success-color); }
.danger { color: var(--danger-color); }
@@media (max-width: 1200px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
</style>
@functions {
string FormatSize(long bytes)
{

View file

@ -4,6 +4,10 @@
ViewData["Title"] = "Forgejo Repositories";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/forgejo.css" asp-append-version="true" />
}
<div class="page-header">
<h1 class="page-title">Repositories</h1>
<p class="page-subtitle">Alle repositories med backup status</p>
@ -144,74 +148,6 @@ else
}
}
<style>
.filter-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-tab {
padding: 6px 12px;
border: 1px solid var(--border-color);
background: transparent;
font-family: inherit;
font-size: 11px;
cursor: pointer;
border-radius: var(--radius-sm);
transition: all 0.15s;
}
.filter-tab:hover {
background: var(--hover-bg);
}
.filter-tab.active {
background: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
.repo-desc {
font-size: 10px;
color: var(--muted-color);
margin-top: 2px;
}
.backup-detail {
font-size: 10px;
color: var(--muted-color);
margin-top: 2px;
}
.warning-card {
border-color: var(--warning-color);
}
.warning-card .card-header {
background: linear-gradient(90deg, rgba(240, 165, 0, 0.1), transparent);
}
.missing-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.missing-repo {
background: rgba(240, 165, 0, 0.1);
border: 1px solid var(--warning-color);
padding: 4px 8px;
border-radius: var(--radius-sm);
}
.repo-row.hidden {
display: none;
}
.success { color: var(--success-color); }
</style>
<script>
function filterRepos(filter) {
// Update active tab

View file

@ -4,6 +4,10 @@
ViewData["Title"] = "Dashboard";
}
@section Styles {
<link rel="stylesheet" href="~/css/pages/dashboard.css" asp-append-version="true" />
}
<div class="page-header">
<h1 class="page-title">Dashboard</h1>
<p class="page-subtitle">PlanTempus SaaS Infrastructure Status</p>
@ -250,78 +254,6 @@
</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)
{

View file

@ -8,6 +8,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/css/components.css" asp-append-version="true" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body>
<div class="app-container">

135
wwwroot/css/components.css Normal file
View file

@ -0,0 +1,135 @@
/* Shared Components */
/* Dashboard Grid */
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.dashboard-col {
display: flex;
flex-direction: column;
gap: 16px;
}
@media (max-width: 1200px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
/* Status Detail */
.status-detail {
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
}
/* Pulse Animation */
.pulse {
animation: pulse 1.5s ease-in-out infinite;
color: var(--warning-color);
}
.pulse.mini {
font-size: 8px;
margin-left: 4px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Running Card */
.running-card {
border-color: var(--warning-color);
}
.running-card .card-header {
background: linear-gradient(90deg, rgba(240, 165, 0, 0.1), transparent);
}
/* Error Card */
.error-card {
border-color: var(--danger-color);
}
.error-card .card-header {
background: linear-gradient(90deg, rgba(220, 53, 69, 0.1), transparent);
}
/* Warning Card */
.warning-card {
border-color: var(--warning-color);
}
.warning-card .card-header {
background: linear-gradient(90deg, rgba(240, 165, 0, 0.1), transparent);
}
/* Rate Bar */
.rate-bar {
display: inline-block;
width: 50px;
height: 6px;
background: var(--border-color);
border-radius: 3px;
overflow: hidden;
vertical-align: middle;
}
.rate-fill {
height: 100%;
transition: width 0.3s;
}
.rate-fill.good { background: var(--success-color); }
.rate-fill.warn { background: var(--warning-color); }
.rate-fill.bad { background: var(--danger-color); }
.rate-text {
font-size: 10px;
margin-left: 6px;
}
/* Compact List */
.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: 40px;
}
/* Last Updated */
.last-updated {
text-align: center;
font-size: 10px;
color: var(--muted-color);
padding: 16px;
}
/* Color Utilities */
.success { color: var(--success-color); }
.warning { color: var(--warning-color); }
.danger { color: var(--danger-color); }

120
wwwroot/css/pages/azure.css Normal file
View file

@ -0,0 +1,120 @@
/* Azure Pages Styles */
/* Breadcrumb */
.breadcrumb {
font-size: 12px;
color: var(--muted-color);
margin-bottom: 8px;
}
.breadcrumb a {
color: var(--accent-color);
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
.breadcrumb .separator {
margin: 0 6px;
color: var(--muted-color);
}
/* Folder Grid */
.folder-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
padding: 16px;
}
.folder-item {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: var(--menu-bg);
border-radius: 4px;
text-decoration: none;
color: inherit;
transition: background 0.2s;
}
.folder-item:hover {
background: var(--border-color);
}
.folder-icon {
font-size: 20px;
}
.folder-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Container & File Icons */
.container-icon, .file-icon {
margin-right: 8px;
}
/* Blob Name */
.blob-name {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
/* Header Meta */
.header-meta {
float: right;
font-weight: normal;
font-size: 11px;
color: var(--muted-color);
}
/* Tier Badges */
.badge-tier {
font-size: 9px;
padding: 2px 6px;
background: var(--border-color);
color: var(--muted-color);
}
.badge-tier.hot {
background: rgba(240, 165, 0, 0.2);
color: var(--warning-color);
}
.badge-tier.cool {
background: rgba(0, 123, 255, 0.2);
color: #007bff;
}
.badge-tier.archive {
background: rgba(108, 117, 125, 0.2);
color: #6c757d;
}
/* Actions */
.actions {
white-space: nowrap;
}
.actions .btn {
padding: 4px 8px;
font-size: 12px;
}
/* Responsive */
@media (max-width: 1000px) {
.azure-page .dashboard-grid {
grid-template-columns: 1fr;
}
}

View file

@ -0,0 +1,89 @@
/* Backup Page Styles */
.type-icon, .dest-icon {
margin-right: 6px;
}
/* Daily Chart */
.daily-chart {
display: flex;
gap: 4px;
height: 80px;
align-items: flex-end;
padding: 8px 0;
}
.day-bar {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.bar-container {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
position: relative;
}
.bar-success {
background: var(--success-color);
border-radius: 2px 2px 0 0;
min-height: 2px;
}
.bar-failed {
background: var(--danger-color);
position: absolute;
bottom: 0;
left: 0;
right: 0;
border-radius: 2px 2px 0 0;
}
.day-label {
font-size: 9px;
color: var(--muted-color);
margin-top: 4px;
}
/* Chart Legend */
.chart-legend {
display: flex;
gap: 16px;
justify-content: center;
font-size: 10px;
color: var(--muted-color);
}
.legend-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 4px;
}
.legend-dot.success { background: var(--success-color); }
.legend-dot.danger { background: var(--danger-color); }
/* Item Error */
.item-error {
font-size: 10px;
color: var(--danger-color);
margin-top: 4px;
padding-left: 40px;
font-style: italic;
}
/* Error Code */
.error-code {
background: rgba(220, 53, 69, 0.1);
color: var(--danger-color);
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
}

View file

@ -0,0 +1,55 @@
/* Dashboard Page Styles */
.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;
}
}

View file

@ -0,0 +1,152 @@
/* Forgejo Pages Styles */
/* Stat Bars (Repository Types) */
.stat-bars {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-bar-item {
display: flex;
align-items: center;
gap: 8px;
}
.stat-bar-label {
width: 70px;
font-size: 11px;
}
.stat-bar {
flex: 1;
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
}
.stat-bar-fill {
height: 100%;
border-radius: 4px;
}
.stat-bar-value {
width: 30px;
text-align: right;
font-size: 11px;
font-weight: bold;
}
/* CI Stats */
.ci-stats {
display: flex;
justify-content: space-around;
text-align: center;
}
.ci-stat-value {
font-size: 24px;
font-weight: bold;
}
.ci-stat-value.success { color: var(--success-color); }
.ci-stat-value.danger { color: var(--danger-color); }
.ci-stat-label {
font-size: 10px;
color: var(--muted-color);
text-transform: uppercase;
}
.ci-rate-bar {
height: 8px;
background: var(--danger-color);
border-radius: 4px;
overflow: hidden;
}
.ci-rate-success {
height: 100%;
background: var(--success-color);
}
.ci-rate-label {
text-align: center;
font-size: 10px;
color: var(--muted-color);
margin-top: 4px;
}
/* Failed Row */
.failed-row {
background: rgba(220, 53, 69, 0.05);
}
/* Filter Tabs */
.filter-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-tab {
padding: 6px 12px;
border: 1px solid var(--border-color);
background: transparent;
font-family: inherit;
font-size: 11px;
cursor: pointer;
border-radius: var(--radius-sm);
transition: all 0.15s;
}
.filter-tab:hover {
background: var(--hover-bg);
}
.filter-tab.active {
background: var(--text-color);
color: var(--bg-color);
border-color: var(--text-color);
}
/* Repo Description */
.repo-desc {
font-size: 10px;
color: var(--muted-color);
margin-top: 2px;
}
/* Backup Detail */
.backup-detail {
font-size: 10px;
color: var(--muted-color);
margin-top: 2px;
}
/* Missing List */
.missing-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.missing-repo {
background: rgba(240, 165, 0, 0.1);
border: 1px solid var(--warning-color);
padding: 4px 8px;
border-radius: var(--radius-sm);
}
/* Repo Row */
.repo-row.hidden {
display: none;
}
/* Actions page - smaller rate bar */
@media (max-width: 1400px) {
.forgejo-actions .dashboard-grid {
grid-template-columns: 1fr;
}
}