Added Authentication with user-auth and apikey-auth
All checks were successful
Gitea CI/CD / dotnet-build-and-test (push) Successful in 10m5s
Gitea CI/CD / Set Tag Name (push) Successful in 5s
Gitea CI/CD / docker-build-and-push (push) Successful in 11m28s
Gitea CI/CD / Create Tag (push) Successful in 5s

This commit is contained in:
2026-01-09 10:18:06 +01:00
parent 05e5a209da
commit d8b164e3eb
25 changed files with 1809 additions and 5 deletions

View File

@@ -0,0 +1,18 @@
@{
ViewData["Title"] = "Zugriff verweigert";
}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-danger">
<div class="card-body text-center">
<h1 class="display-1 text-danger">🚫</h1>
<h2 class="card-title">Zugriff verweigert</h2>
<p class="card-text">Sie haben keine Berechtigung, auf diese Seite zuzugreifen.</p>
<a asp-controller="Auth" asp-action="Login" class="btn btn-primary">Zurück zum Login</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,108 @@
@model watcher_monitoring.Models.LoginViewModel
@{
ViewData["Title"] = "Login";
Layout = null;
}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Watcher Monitoring</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
max-width: 400px;
width: 100%;
}
.login-card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 40px;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h2 {
color: #333;
font-weight: 600;
}
.login-header p {
color: #666;
margin-top: 10px;
}
.btn-login {
width: 100%;
padding: 12px;
font-size: 16px;
font-weight: 600;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
}
.btn-login:hover {
background: linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%);
}
.form-control:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-card">
<div class="login-header">
<h2>🔒 Watcher Monitoring</h2>
<p>Bitte melden Sie sich an</p>
</div>
@if (TempData["Error"] != null)
{
<div class="alert alert-danger" role="alert">
@TempData["Error"]
</div>
}
<form asp-action="Login" asp-controller="Auth" method="post">
<div class="form-group mb-3">
<label asp-for="Username" class="form-label">Benutzername</label>
<input asp-for="Username" class="form-control" placeholder="Benutzername eingeben" autofocus />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<div class="form-group mb-4">
<label asp-for="Password" class="form-label">Passwort</label>
<input asp-for="Password" type="password" class="form-control" placeholder="Passwort eingeben" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-check mb-3">
<input asp-for="RememberMe" type="checkbox" class="form-check-input" />
<label asp-for="RememberMe" class="form-check-label">
Angemeldet bleiben
</label>
</div>
<button type="submit" class="btn btn-primary btn-login">Anmelden</button>
</form>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -8,11 +9,13 @@
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/watcher_monitoring.styles.css" asp-append-version="true" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid px-4">
<a class="navbar-brand fw-bold" asp-area="" asp-controller="Home" asp-action="Index">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="me-2" style="display: inline-block; vertical-align: middle;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
class="me-2" style="display: inline-block; vertical-align: middle;">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 2"></path>
</svg>
@@ -26,9 +29,47 @@
<li class="nav-item">
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Settings</a>
</li>
@if (User.Identity?.IsAuthenticated == true)
{
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" style="display: inline-block; vertical-align: middle;">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
@User.Identity.Name
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li>
<form asp-controller="Auth" asp-action="Logout" method="post" style="display:inline;">
@Html.AntiForgeryToken()
<button type="submit" class="dropdown-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
style="display: inline-block; vertical-align: middle; margin-right: 8px;">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
Abmelden
</button>
</form>
</li>
<li class="dropdown-item">
<a style="display: inline-block; vertical-align: middle; margin-right: 8px;">
asp-area="" asp-controller="User" asp-action="Index"><svg width="16" height="16"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
style="display: inline-block; vertical-align: middle; margin-right: 8px;">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>Settings</a>
</li>
</ul>
</li>
}
</ul>
</div>
</div>
@@ -49,4 +90,5 @@
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
</html>

View File

@@ -0,0 +1,47 @@
@model watcher_monitoring.Models.User
@{
ViewData["Title"] = "Neuer Benutzer";
}
<div class="container-fluid px-4">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1 class="section-title mb-4">Neuen Benutzer erstellen</h1>
<div class="card">
<form asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="alert" style="background-color: rgba(248, 81, 73, 0.15); border: 1px solid var(--danger); color: var(--danger); border-radius: 6px; padding: 1rem; margin-bottom: 1rem;"></div>
<div class="mb-3">
<label asp-for="Username" class="form-label" style="color: var(--text-primary); font-weight: 500; margin-bottom: 0.5rem;">Benutzername</label>
<input asp-for="Username" class="form-control" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required />
<span asp-validation-for="Username" style="color: var(--danger); font-size: 0.875rem;"></span>
</div>
<div class="mb-3">
<label asp-for="Email" class="form-label" style="color: var(--text-primary); font-weight: 500; margin-bottom: 0.5rem;">E-Mail-Adresse</label>
<input asp-for="Email" type="email" class="form-control" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required />
<span asp-validation-for="Email" style="color: var(--danger); font-size: 0.875rem;"></span>
</div>
<div class="mb-3">
<label asp-for="Password" class="form-label" style="color: var(--text-primary); font-weight: 500; margin-bottom: 0.5rem;">Passwort</label>
<input asp-for="Password" type="password" class="form-control" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required />
<span asp-validation-for="Password" style="color: var(--danger); font-size: 0.875rem;"></span>
<div style="color: var(--text-muted); font-size: 0.875rem; margin-top: 0.25rem;">Mindestens 8 Zeichen empfohlen</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Erstellen</button>
<a asp-action="Index" class="btn" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.5rem 1rem; border-radius: 6px; text-decoration: none;">Abbrechen</a>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@@ -0,0 +1,217 @@
@model watcher_monitoring.Models.User
@{
ViewData["Title"] = "Benutzerdetails";
}
<div class="container-fluid px-4">
<div class="row">
<div class="col-lg-10">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="section-title mb-0">Benutzerdetails</h1>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn" style="background-color: var(--warning); color: #fff; margin-right: 0.5rem;">Bearbeiten</a>
<a asp-action="Index" class="btn" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary);">Zurück</a>
</div>
</div>
@if (TempData["Success"] != null)
{
<div class="alert" style="background-color: rgba(63, 185, 80, 0.15); border: 1px solid var(--success); color: var(--success); border-radius: 6px; padding: 1rem; margin-bottom: 1rem; position: relative;">
@TempData["Success"]
@if (TempData["NewApiKey"] != null)
{
<hr style="border-color: var(--border-color); margin: 1rem 0;">
<strong>API-Key (wird nur einmal angezeigt!):</strong>
<div class="input-group mt-2" style="display: flex; gap: 0.5rem;">
<input type="text" class="form-control font-monospace" value="@TempData["NewApiKey"]" id="newApiKey" readonly style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px; flex: 1;">
<button class="btn" type="button" onclick="copyApiKey()" style="background-color: var(--accent-primary); color: #fff; border: none; padding: 0.75rem 1rem; border-radius: 6px;">
Kopieren
</button>
</div>
}
<button type="button" class="btn-close" data-bs-dismiss="alert" style="position: absolute; top: 1rem; right: 1rem; color: var(--success);"></button>
</div>
}
@if (TempData["Error"] != null)
{
<div class="alert" style="background-color: rgba(248, 81, 73, 0.15); border: 1px solid var(--danger); color: var(--danger); border-radius: 6px; padding: 1rem; margin-bottom: 1rem; position: relative;">
@TempData["Error"]
<button type="button" class="btn-close" data-bs-dismiss="alert" style="position: absolute; top: 1rem; right: 1rem; color: var(--danger);"></button>
</div>
}
<div class="card mb-4">
<h2 class="card-title">Benutzerinformationen</h2>
<div style="color: var(--text-secondary);">
<div class="row mb-3" style="padding-bottom: 1rem; border-bottom: 1px solid var(--border-color);">
<div class="col-sm-4"><strong style="color: var(--text-primary);">Benutzername:</strong></div>
<div class="col-sm-8" style="color: var(--text-secondary);">@Model.Username</div>
</div>
<div class="row mb-3" style="padding-bottom: 1rem; border-bottom: 1px solid var(--border-color);">
<div class="col-sm-4"><strong style="color: var(--text-primary);">E-Mail:</strong></div>
<div class="col-sm-8" style="color: var(--text-secondary);">@Model.Email</div>
</div>
<div class="row mb-3" style="padding-bottom: 1rem; border-bottom: 1px solid var(--border-color);">
<div class="col-sm-4"><strong style="color: var(--text-primary);">Status:</strong></div>
<div class="col-sm-8">
@if (Model.IsActive)
{
<span class="status-badge status-online">Aktiv</span>
}
else
{
<span class="status-badge status-offline">Inaktiv</span>
}
</div>
</div>
<div class="row mb-3" style="padding-bottom: 1rem; border-bottom: 1px solid var(--border-color);">
<div class="col-sm-4"><strong style="color: var(--text-primary);">Erstellt am:</strong></div>
<div class="col-sm-8" style="color: var(--text-secondary);">@Model.CreatedAt.ToString("dd.MM.yyyy HH:mm:ss")</div>
</div>
<div class="row mb-3" style="padding-bottom: 1rem; border-bottom: 1px solid var(--border-color);">
<div class="col-sm-4"><strong style="color: var(--text-primary);">Letzter Login:</strong></div>
<div class="col-sm-8" style="color: var(--text-secondary);">@Model.LastLogin.ToString("dd.MM.yyyy HH:mm:ss")</div>
</div>
<div class="row">
<div class="col-sm-12">
<form asp-action="ToggleActive" asp-route-id="@Model.Id" method="post" class="d-inline">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm" style="background-color: @(Model.IsActive ? "var(--warning)" : "var(--success)"); color: #fff; margin-right: 0.5rem;">
@(Model.IsActive ? "Deaktivieren" : "Aktivieren")
</button>
</form>
<form asp-action="Delete" asp-route-id="@Model.Id" method="post" class="d-inline" onsubmit="return confirm('Möchten Sie diesen Benutzer wirklich löschen? Alle API-Keys werden ebenfalls gelöscht.');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm" style="background-color: var(--danger); color: #fff;">Löschen</button>
</form>
</div>
</div>
</div>
</div>
<div class="card">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="card-title mb-0">API-Keys (@Model.ApiKeys.Count)</h2>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#createApiKeyModal">
Neuer API-Key
</button>
</div>
@if (Model.ApiKeys.Any())
{
<div class="table-responsive">
<table class="table table-hover" style="color: var(--text-primary); margin-bottom: 0;">
<thead style="border-bottom: 1px solid var(--border-color);">
<tr>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Name</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Beschreibung</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Status</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Erstellt</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Läuft ab</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Zuletzt verwendet</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Aktionen</th>
</tr>
</thead>
<tbody>
@foreach (var key in Model.ApiKeys.OrderByDescending(k => k.CreatedAt))
{
<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 1rem;"><strong style="color: var(--text-primary);">@key.Name</strong></td>
<td style="padding: 1rem; color: var(--text-secondary);">@(key.Description ?? "-")</td>
<td style="padding: 1rem;">
@if (key.IsExpired)
{
<span class="status-badge status-offline">Abgelaufen</span>
}
else if (!key.IsActive)
{
<span class="status-badge" style="background-color: rgba(139, 148, 158, 0.15); color: var(--text-secondary); border: 1px solid var(--text-secondary);">Inaktiv</span>
}
else
{
<span class="status-badge status-online">Aktiv</span>
}
</td>
<td style="padding: 1rem; color: var(--text-secondary);">@key.CreatedAt.ToString("dd.MM.yyyy")</td>
<td style="padding: 1rem; color: var(--text-secondary);">@(key.ExpiresAt?.ToString("dd.MM.yyyy") ?? "Nie")</td>
<td style="padding: 1rem; color: var(--text-secondary);">@(key.LastUsedAt?.ToString("dd.MM.yyyy HH:mm") ?? "Nie")</td>
<td style="padding: 1rem;">
<form asp-action="ToggleApiKey" asp-route-userId="@Model.Id" asp-route-keyId="@key.Id" method="post" class="d-inline">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm" style="background-color: @(key.IsActive ? "var(--warning)" : "var(--success)"); color: #fff; margin-right: 0.5rem;">
@(key.IsActive ? "Deaktivieren" : "Aktivieren")
</button>
</form>
<form asp-action="DeleteApiKey" asp-route-userId="@Model.Id" asp-route-keyId="@key.Id" method="post" class="d-inline" onsubmit="return confirm('Möchten Sie diesen API-Key wirklich löschen?');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm" style="background-color: var(--danger); color: #fff;">Löschen</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p style="color: var(--text-muted); margin-bottom: 0;">Noch keine API-Keys vorhanden.</p>
}
</div>
</div>
</div>
</div>
<!-- Modal für neuen API-Key -->
<div class="modal fade" id="createApiKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content" style="background-color: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary);">
<form asp-action="GenerateApiKey" asp-route-id="@Model.Id" method="post">
@Html.AntiForgeryToken()
<div class="modal-header" style="border-bottom: 1px solid var(--border-color);">
<h5 class="modal-title" style="color: var(--text-primary);">Neuen API-Key erstellen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" style="filter: invert(1);"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="keyName" class="form-label" style="color: var(--text-primary); font-weight: 500;">Name *</label>
<input type="text" class="form-control" id="keyName" name="keyName" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required>
<div style="color: var(--text-muted); font-size: 0.875rem; margin-top: 0.25rem;">Ein eindeutiger Name für diesen API-Key</div>
</div>
<div class="mb-3">
<label for="description" class="form-label" style="color: var(--text-primary); font-weight: 500;">Beschreibung</label>
<textarea class="form-control" id="description" name="description" rows="2" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;"></textarea>
</div>
<div class="mb-3">
<label for="expiresAt" class="form-label" style="color: var(--text-primary); font-weight: 500;">Ablaufdatum (optional)</label>
<input type="datetime-local" class="form-control" id="expiresAt" name="expiresAt" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;">
<div style="color: var(--text-muted); font-size: 0.875rem; margin-top: 0.25rem;">Leer lassen für unbegrenzte Gültigkeit</div>
</div>
</div>
<div class="modal-footer" style="border-top: 1px solid var(--border-color);">
<button type="button" class="btn" data-bs-dismiss="modal" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary);">Abbrechen</button>
<button type="submit" class="btn btn-primary">API-Key erstellen</button>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script>
function copyApiKey() {
var copyText = document.getElementById("newApiKey");
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
var btn = event.target;
var originalText = btn.textContent;
btn.textContent = "Kopiert!";
setTimeout(function() {
btn.textContent = originalText;
}, 2000);
}
</script>
}

View File

@@ -0,0 +1,46 @@
@model watcher_monitoring.Models.User
@{
ViewData["Title"] = "Benutzer bearbeiten";
}
<div class="container-fluid px-4">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1 class="section-title mb-4">Benutzer bearbeiten</h1>
<div class="card">
<form asp-action="Edit" method="post">
<div asp-validation-summary="ModelOnly" class="alert" style="background-color: rgba(248, 81, 73, 0.15); border: 1px solid var(--danger); color: var(--danger); border-radius: 6px; padding: 1rem; margin-bottom: 1rem;"></div>
<input type="hidden" asp-for="Id" />
<div class="mb-3">
<label asp-for="Username" class="form-label" style="color: var(--text-primary); font-weight: 500; margin-bottom: 0.5rem;">Benutzername</label>
<input asp-for="Username" class="form-control" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required />
<span asp-validation-for="Username" style="color: var(--danger); font-size: 0.875rem;"></span>
</div>
<div class="mb-3">
<label asp-for="Email" class="form-label" style="color: var(--text-primary); font-weight: 500; margin-bottom: 0.5rem;">E-Mail-Adresse</label>
<input asp-for="Email" type="email" class="form-control" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.75rem; border-radius: 6px;" required />
<span asp-validation-for="Email" style="color: var(--danger); font-size: 0.875rem;"></span>
</div>
<div class="mb-3 form-check">
<input asp-for="IsActive" type="checkbox" class="form-check-input" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color);" />
<label asp-for="IsActive" class="form-check-label" style="color: var(--text-primary); margin-left: 0.5rem;">Benutzer ist aktiv</label>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Speichern</button>
<a asp-action="Details" asp-route-id="@Model.Id" class="btn" style="background-color: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.5rem 1rem; border-radius: 6px; text-decoration: none;">Abbrechen</a>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@@ -0,0 +1,62 @@
@model IEnumerable<watcher_monitoring.Models.User>
@{
ViewData["Title"] = "Benutzerverwaltung";
}
<div class="container-fluid px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="section-title mb-0">Benutzerverwaltung</h1>
<a asp-action="Create" class="btn btn-primary">
Neuer Benutzer
</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover" style="color: var(--text-primary); margin-bottom: 0;">
<thead style="border-bottom: 1px solid var(--border-color);">
<tr>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Benutzername</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">E-Mail</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Status</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">API-Keys</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Erstellt am</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Letzter Login</th>
<th style="color: var(--text-secondary); font-weight: 600; padding: 1rem;">Aktionen</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 1rem;">
<strong style="color: var(--text-primary);">@user.Username</strong>
</td>
<td style="padding: 1rem; color: var(--text-secondary);">@user.Email</td>
<td style="padding: 1rem;">
@if (user.IsActive)
{
<span class="status-badge status-online">Aktiv</span>
}
else
{
<span class="status-badge status-offline">Inaktiv</span>
}
</td>
<td style="padding: 1rem;">
<span class="status-badge" style="background-color: rgba(88, 166, 255, 0.15); color: var(--info); border: 1px solid var(--info);">@user.ApiKeys.Count</span>
</td>
<td style="padding: 1rem; color: var(--text-secondary);">@user.CreatedAt.ToString("dd.MM.yyyy HH:mm")</td>
<td style="padding: 1rem; color: var(--text-secondary);">@user.LastLogin.ToString("dd.MM.yyyy HH:mm")</td>
<td style="padding: 1rem;">
<a asp-action="Details" asp-route-id="@user.Id" class="btn btn-sm" style="background-color: var(--info); color: #fff; margin-right: 0.5rem;">Details</a>
<a asp-action="Edit" asp-route-id="@user.Id" class="btn btn-sm" style="background-color: var(--warning); color: #fff;">Bearbeiten</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>