Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f6d070e6b | |||
| 7ab824e7c1 | |||
| 73599c00d1 | |||
| 64c5f6aa2c | |||
| 5431618d43 | |||
| 598243ddc9 | |||
| 9fd50f86c0 | |||
| 0467f45036 | |||
| 88ac175190 |
-11
@@ -1,11 +0,0 @@
|
|||||||
# SQLite Datenbanken und Journal-Dateien
|
|
||||||
*.db
|
|
||||||
*.db-shm
|
|
||||||
*.db-wal
|
|
||||||
|
|
||||||
# Build-Artefakte
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
*.user
|
|
||||||
*.suo
|
|
||||||
*.vs/
|
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
|
||||||
@inject NavigationManager Nav
|
|
||||||
@inject timetracker.Data.UserNotificationService UserNotificationService
|
|
||||||
@implements IDisposable
|
|
||||||
@using System.Security.Claims
|
|
||||||
|
|
||||||
<MudThemeProvider Theme="_theme" />
|
<MudThemeProvider Theme="_theme" />
|
||||||
<MudPopoverProvider />
|
<MudPopoverProvider />
|
||||||
@@ -34,26 +29,6 @@
|
|||||||
@code {
|
@code {
|
||||||
private bool _drawerOpen = true;
|
private bool _drawerOpen = true;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
UserNotificationService.OnUserDeleted += HandleUserDeleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleUserDeleted(int deletedUserId)
|
|
||||||
{
|
|
||||||
var state = await AuthStateProvider.GetAuthenticationStateAsync();
|
|
||||||
var idClaim = state.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
||||||
if (idClaim != null && int.TryParse(idClaim, out var myId) && myId == deletedUserId)
|
|
||||||
{
|
|
||||||
await InvokeAsync(() => Nav.NavigateTo("/auth/logout", forceLoad: true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
UserNotificationService.OnUserDeleted -= HandleUserDeleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly MudTheme _theme = new()
|
private readonly MudTheme _theme = new()
|
||||||
{
|
{
|
||||||
PaletteLight = new PaletteLight
|
PaletteLight = new PaletteLight
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div style="display:flex; flex-direction:column; height:100%;">
|
<MudNavMenu>
|
||||||
<MudNavMenu Style="flex:1">
|
<MudText Typo="Typo.h6" Class="px-4 mt-4 mb-2">Navigation</MudText>
|
||||||
<MudDivider Class="mb-2" />
|
<MudDivider Class="mb-2" />
|
||||||
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.CalendarMonth">Wochenübersicht</MudNavLink>
|
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.CalendarMonth">Wochenübersicht</MudNavLink>
|
||||||
<MudNavLink Href="month" Icon="@Icons.Material.Filled.CalendarViewMonth">Monatsübersicht</MudNavLink>
|
<MudNavLink Href="month" Icon="@Icons.Material.Filled.CalendarViewMonth">Monatsübersicht</MudNavLink>
|
||||||
@@ -7,30 +7,14 @@
|
|||||||
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
||||||
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
||||||
<MudDivider Class="mt-2 mb-2" />
|
<MudDivider Class="mt-2 mb-2" />
|
||||||
<AuthorizeView Policy="AdminOnly">
|
|
||||||
<MudNavLink Href="admin/users" Icon="@Icons.Material.Filled.AdminPanelSettings" IconColor="Color.Error">
|
|
||||||
Benutzerverwaltung
|
|
||||||
</MudNavLink>
|
|
||||||
</AuthorizeView>
|
|
||||||
</MudNavMenu>
|
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
<Authorized>
|
<Authorized>
|
||||||
<MudDivider />
|
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-1" Spacing="1">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-2" Spacing="1">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Color="Color.Primary" Size="Size.Small" />
|
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Color="Color.Primary" Size="Size.Small" />
|
||||||
<MudText Typo="Typo.body2" Style="font-weight:600">@context.User.Identity?.Name</MudText>
|
<MudText Typo="Typo.body2" Style="font-weight:600">@context.User.Identity?.Name</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudNavMenu>
|
|
||||||
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
|
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
|
||||||
</MudNavMenu>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
<MudTooltip Text="Changelog anzeigen" Placement="Placement.Top">
|
</MudNavMenu>
|
||||||
<MudNavMenu>
|
|
||||||
<MudNavLink Href="/changelog" Icon="@Icons.Material.Filled.NewReleases"
|
|
||||||
Style="color: var(--mud-palette-text-disabled); font-size:0.75rem;">
|
|
||||||
Version 1.1
|
|
||||||
</MudNavLink>
|
|
||||||
</MudNavMenu>
|
|
||||||
</MudTooltip>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
@page "/admin/users"
|
|
||||||
@rendermode InteractiveServer
|
|
||||||
@attribute [Authorize(Policy = "AdminOnly")]
|
|
||||||
@inject AuthService AuthService
|
|
||||||
@inject ISnackbar Snackbar
|
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
|
||||||
@inject timetracker.Data.UserNotificationService UserNotificationService
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
<PageTitle>Benutzerverwaltung – Timetracker</PageTitle>
|
|
||||||
|
|
||||||
@if (_loading)
|
|
||||||
{
|
|
||||||
<MudStack AlignItems="AlignItems.Center" Class="mt-16" Spacing="3">
|
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
|
||||||
<MudText Color="Color.Secondary">Lade Benutzer…</MudText>
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudStack Spacing="4">
|
|
||||||
|
|
||||||
@* ── Header ── *@
|
|
||||||
<MudPaper Elevation="4" Class="pa-5 rounded-xl"
|
|
||||||
Style="background: linear-gradient(135deg, #B71C1C 0%, #7F0000 100%); color:white;">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AdminPanelSettings" Style="color:white; font-size:2rem" />
|
|
||||||
<MudStack Spacing="0">
|
|
||||||
<MudText Typo="Typo.h5" Style="color:white; font-weight:700">Benutzerverwaltung</MudText>
|
|
||||||
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">
|
|
||||||
@_users.Count Benutzer registriert
|
|
||||||
</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
@* ── Tabelle ── *@
|
|
||||||
<MudCard Elevation="3" Class="rounded-xl">
|
|
||||||
<MudCardContent Class="pa-0">
|
|
||||||
<MudTable Items="_users" Hover="true" Striped="true" Dense="false"
|
|
||||||
SortLabel="Sortieren">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh><MudTableSortLabel SortBy="new Func<User, object>(u => u.Id)">ID</MudTableSortLabel></MudTh>
|
|
||||||
<MudTh><MudTableSortLabel SortBy="new Func<User, object>(u => u.Username)" InitialDirection="SortDirection.Ascending">Benutzername</MudTableSortLabel></MudTh>
|
|
||||||
<MudTh Style="text-align:right">Aktionen</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate>
|
|
||||||
<MudTd>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">@context.Id</MudText>
|
|
||||||
</MudTd>
|
|
||||||
<MudTd>
|
|
||||||
@if (_editUserId == context.Id)
|
|
||||||
{
|
|
||||||
<MudTextField @bind-Value="_editUsername"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Margin="Margin.Dense"
|
|
||||||
Immediate="true"
|
|
||||||
Style="max-width:220px"
|
|
||||||
OnKeyDown="@(async e => { if (e.Key == "Enter") await SaveRename(context); })" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountCircle"
|
|
||||||
Color="@(context.Username == "marc" ? Color.Error : Color.Default)"
|
|
||||||
Size="Size.Small" />
|
|
||||||
<MudText Style="@(context.Username == "marc" ? "font-weight:700" : "")">
|
|
||||||
@context.Username
|
|
||||||
</MudText>
|
|
||||||
@if (context.Username == "marc")
|
|
||||||
{
|
|
||||||
<MudChip T="string" Size="Size.Small" Color="Color.Error" Variant="Variant.Outlined">Admin</MudChip>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
</MudTd>
|
|
||||||
<MudTd Style="text-align:right">
|
|
||||||
@if (_editUserId == context.Id)
|
|
||||||
{
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Check"
|
|
||||||
Color="Color.Success"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="@(() => SaveRename(context))" />
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
|
||||||
Color="Color.Default"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="CancelEdit" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="@(() => StartEdit(context))" />
|
|
||||||
@if (context.Username != "marc")
|
|
||||||
{
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
|
|
||||||
Color="Color.Error"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="@(() => DeleteUser(context))" />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
<NoRecordsContent>
|
|
||||||
<MudText Color="Color.Secondary" Class="pa-4">Keine Benutzer gefunden.</MudText>
|
|
||||||
</NoRecordsContent>
|
|
||||||
</MudTable>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private List<User> _users = [];
|
|
||||||
private bool _loading = true;
|
|
||||||
private int? _editUserId;
|
|
||||||
private string _editUsername = "";
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
var claim = (await AuthStateProvider.GetAuthenticationStateAsync())
|
|
||||||
.User.FindFirst(ClaimTypes.NameIdentifier);
|
|
||||||
if (claim == null) return;
|
|
||||||
|
|
||||||
UserNotificationService.OnUsersChanged += RefreshUsers;
|
|
||||||
_users = await AuthService.GetAllUsersAsync();
|
|
||||||
_loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshUsers()
|
|
||||||
{
|
|
||||||
_users = await AuthService.GetAllUsersAsync();
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
UserNotificationService.OnUsersChanged -= RefreshUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartEdit(User user)
|
|
||||||
{
|
|
||||||
_editUserId = user.Id;
|
|
||||||
_editUsername = user.Username;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CancelEdit()
|
|
||||||
{
|
|
||||||
_editUserId = null;
|
|
||||||
_editUsername = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveRename(User user)
|
|
||||||
{
|
|
||||||
var trimmed = _editUsername.Trim();
|
|
||||||
var error = await AuthService.RenameUserAsync(user.Id, trimmed);
|
|
||||||
if (error != null)
|
|
||||||
{
|
|
||||||
Snackbar.Add(error, Severity.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user.Username = trimmed;
|
|
||||||
_editUserId = null;
|
|
||||||
_editUsername = "";
|
|
||||||
Snackbar.Add($"Benutzer umbenannt zu \"{trimmed}\".", Severity.Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteUser(User user)
|
|
||||||
{
|
|
||||||
await AuthService.DeleteUserAsync(user.Id);
|
|
||||||
_users.Remove(user);
|
|
||||||
Snackbar.Add($"Benutzer \"{user.Username}\" gelöscht.", Severity.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
@page "/changelog"
|
|
||||||
@rendermode InteractiveServer
|
|
||||||
@attribute [Authorize]
|
|
||||||
|
|
||||||
<PageTitle>Changelog – Timetracker</PageTitle>
|
|
||||||
|
|
||||||
<MudStack Spacing="4">
|
|
||||||
|
|
||||||
@* ── Header ── *@
|
|
||||||
<MudPaper Elevation="4" Class="pa-5 rounded-xl"
|
|
||||||
Style="background: linear-gradient(135deg, #3F51B5 0%, #1A237E 100%); color:white;">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.NewReleases" Style="color:white; font-size:2rem" />
|
|
||||||
<MudStack Spacing="0">
|
|
||||||
<MudText Typo="Typo.h5" Style="color:white; font-weight:700">Changelog</MudText>
|
|
||||||
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">Versionshistorie & Änderungen</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
@foreach (var release in _releases)
|
|
||||||
{
|
|
||||||
<MudCard Elevation="2" Class="rounded-xl">
|
|
||||||
<MudCardContent>
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
|
|
||||||
<MudChip T="string" Color="@(release.IsLatest ? Color.Primary : Color.Default)"
|
|
||||||
Variant="Variant.Filled" Size="Size.Medium" Style="font-weight:700">
|
|
||||||
@release.Version
|
|
||||||
</MudChip>
|
|
||||||
@if (release.IsLatest)
|
|
||||||
{
|
|
||||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">
|
|
||||||
Aktuell
|
|
||||||
</MudChip>
|
|
||||||
}
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">@release.Date</MudText>
|
|
||||||
</MudStack>
|
|
||||||
<MudDivider Class="mb-3" />
|
|
||||||
<MudStack Spacing="1">
|
|
||||||
@foreach (var entry in release.Entries)
|
|
||||||
{
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Start" Spacing="2">
|
|
||||||
<MudChip T="string" Color="@GetTagColor(entry.Tag)" Variant="Variant.Outlined"
|
|
||||||
Size="Size.Small" Style="min-width:72px; justify-content:center; font-size:0.7rem;">
|
|
||||||
@entry.Tag
|
|
||||||
</MudChip>
|
|
||||||
<MudText Typo="Typo.body2" Style="padding-top:2px">@entry.Text</MudText>
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
}
|
|
||||||
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private record ChangeEntry(string Tag, string Text);
|
|
||||||
private record Release(string Version, string Date, bool IsLatest, List<ChangeEntry> Entries);
|
|
||||||
|
|
||||||
private static Color GetTagColor(string tag) => tag switch
|
|
||||||
{
|
|
||||||
"Neu" => Color.Success,
|
|
||||||
"Fix" => Color.Error,
|
|
||||||
"Upgrade" => Color.Info,
|
|
||||||
_ => Color.Default
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly List<Release> _releases =
|
|
||||||
[
|
|
||||||
new("1.1", "08.06.2026", true,
|
|
||||||
[
|
|
||||||
new("Neu", "Versionsnummer in der Navbar mit Link zum Changelog"),
|
|
||||||
new("Neu", "Changelog-Seite"),
|
|
||||||
new("Neu", "Live-Aktualisierung der Benutzerliste bei neuer Registrierung"),
|
|
||||||
new("Neu", "Automatisches Abmelden gelöschter Benutzer"),
|
|
||||||
new("Neu", "Benutzernamen in der Benutzerverwaltung umbenennen"),
|
|
||||||
new("Upgrade", "Navbar: Benutzer und Abmelden-Button unten fixiert"),
|
|
||||||
]),
|
|
||||||
new("1.0", "20.05.2026", false,
|
|
||||||
[
|
|
||||||
new("Neu", "Erste Version des Timetrackers"),
|
|
||||||
new("Neu", "Wochenübersicht mit Arbeitszeiten und Pausen"),
|
|
||||||
new("Neu", "Monatsübersicht"),
|
|
||||||
new("Neu", "Feiertage-Verwaltung"),
|
|
||||||
new("Neu", "Urlaubs-Maximizer"),
|
|
||||||
new("Neu", "Einstellungen"),
|
|
||||||
new("Neu", "Benutzerverwaltung für Admins"),
|
|
||||||
new("Neu", "Registrierung und Login"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject HolidayService HolidayService
|
@inject HolidayService HolidayService
|
||||||
@inject TimetrackerService TrackerService
|
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
|
||||||
|
|
||||||
<PageTitle>Feiertage – Timetracker</PageTitle>
|
<PageTitle>Feiertage – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -188,25 +186,15 @@ else
|
|||||||
private List<PublicHoliday> _holidays = [];
|
private List<PublicHoliday> _holidays = [];
|
||||||
private string _subLabel = "";
|
private string _subLabel = "";
|
||||||
|
|
||||||
private int _userId;
|
|
||||||
private AppSettings _settings = new();
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
|
||||||
var claim = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
|
||||||
if (claim != null)
|
|
||||||
{
|
|
||||||
_userId = int.Parse(claim.Value);
|
|
||||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
|
||||||
}
|
|
||||||
await LoadHolidays();
|
await LoadHolidays();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadHolidays()
|
private async Task LoadHolidays()
|
||||||
{
|
{
|
||||||
_holidays = await HolidayService.GetHolidaysAsync(_year, _settings.GermanState);
|
_holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||||
var count = _holidays.Count;
|
var count = _holidays.Count;
|
||||||
var past = _holidays.Count(h => h.Date < DateOnly.FromDateTime(DateTime.Today));
|
var past = _holidays.Count(h => h.Date < DateOnly.FromDateTime(DateTime.Today));
|
||||||
_subLabel = count == 0
|
_subLabel = count == 0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@@ -367,7 +367,7 @@ else
|
|||||||
{
|
{
|
||||||
if (_monday.Year != _holidayYear)
|
if (_monday.Year != _holidayYear)
|
||||||
{
|
{
|
||||||
var list = await HolidayService.GetHolidaysAsync(_monday.Year, _settings.GermanState);
|
var list = await HolidayService.GetHolidaysAsync(_monday.Year);
|
||||||
_holidays = list.ToDictionary(h => h.Date, h => h.Name);
|
_holidays = list.ToDictionary(h => h.Date, h => h.Name);
|
||||||
_holidayYear = _monday.Year;
|
_holidayYear = _monday.Year;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@page "/login"
|
@page "/login"
|
||||||
|
@rendermode InteractiveServer
|
||||||
@attribute [AllowAnonymous]
|
@attribute [AllowAnonymous]
|
||||||
@inject NavigationManager Nav
|
@inject NavigationManager Nav
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
<PageTitle>Anmelden – Timetracker</PageTitle>
|
<PageTitle>Anmelden – Timetracker</PageTitle>
|
||||||
|
|
||||||
@@ -15,103 +17,86 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
||||||
@* ── Static Tab Navigation ── *@
|
<MudTabs @bind-ActivePanelIndex="_activeTab" Rounded="true" Centered="true" Color="Color.Primary">
|
||||||
<MudStack Row="true" Justify="Justify.Center" Class="mb-4">
|
|
||||||
<MudButton Href="/login"
|
|
||||||
Variant="@(_activeTab == 0 ? Variant.Filled : Variant.Text)"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Style="min-width: 120px; border-radius: 20px;">
|
|
||||||
Anmelden
|
|
||||||
</MudButton>
|
|
||||||
<MudButton Href="/login?tab=register"
|
|
||||||
Variant="@(_activeTab == 1 ? Variant.Filled : Variant.Text)"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Style="min-width: 120px; border-radius: 20px;">
|
|
||||||
Registrieren
|
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudDivider Class="mb-6" />
|
@* ── Login ── *@
|
||||||
|
<MudTabPanel Text="Anmelden">
|
||||||
@if (_activeTab == 0)
|
<MudStack Spacing="3" Class="mt-4">
|
||||||
{
|
@if (_error != null && _activeTab == 0)
|
||||||
@* ── Login Form ── *@
|
|
||||||
<MudStack Spacing="3">
|
|
||||||
@if (_error != null)
|
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||||
}
|
}
|
||||||
<form action="/auth/login" method="post">
|
<form action="/auth/login" method="post">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudTextField T="string"
|
<MudTextField @bind-Value="_loginUsername"
|
||||||
Label="Benutzername"
|
Label="Benutzername"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||||
name="username"
|
name="username"
|
||||||
Required="true"
|
|
||||||
AutoFocus="true" />
|
AutoFocus="true" />
|
||||||
<MudTextField T="string"
|
<MudTextField @bind-Value="_loginPassword"
|
||||||
Label="Passwort"
|
Label="Passwort"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
InputType="InputType.Password"
|
InputType="@(_showLoginPw ? InputType.Text : InputType.Password)"
|
||||||
|
AdornmentAriaLabel="Passwort anzeigen"
|
||||||
name="password"
|
name="password"
|
||||||
Required="true" />
|
OnAdornmentClick="@(() => _showLoginPw = !_showLoginPw)" />
|
||||||
<MudButton ButtonType="ButtonType.Submit"
|
<MudButton ButtonType="ButtonType.Submit"
|
||||||
Variant="Variant.Filled"
|
Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
FullWidth="true"
|
FullWidth="true"
|
||||||
Size="Size.Large"
|
Size="Size.Large"
|
||||||
StartIcon="@Icons.Material.Filled.Login"
|
StartIcon="@Icons.Material.Filled.Login">
|
||||||
Class="mt-2">
|
|
||||||
Anmelden
|
Anmelden
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</form>
|
</form>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
</MudTabPanel>
|
||||||
else
|
|
||||||
{
|
@* ── Registrieren ── *@
|
||||||
@* ── Register Form ── *@
|
<MudTabPanel Text="Registrieren">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3" Class="mt-4">
|
||||||
@if (_error != null)
|
@if (_error != null && _activeTab == 1)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||||
}
|
}
|
||||||
<form action="/auth/register" method="post">
|
<form action="/auth/register" method="post">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudTextField T="string"
|
<MudTextField @bind-Value="_regUsername"
|
||||||
Label="Benutzername"
|
Label="Benutzername"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||||
name="username"
|
name="username"
|
||||||
Required="true"
|
|
||||||
HelperText="Mindestens 3 Zeichen" />
|
HelperText="Mindestens 3 Zeichen" />
|
||||||
<MudTextField T="string"
|
<MudTextField @bind-Value="_regPassword"
|
||||||
Label="Passwort"
|
Label="Passwort"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Adornment="Adornment.Start"
|
Adornment="Adornment.Start"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
InputType="InputType.Password"
|
InputType="@(_showRegPw ? InputType.Text : InputType.Password)"
|
||||||
|
AdornmentAriaLabel="Passwort anzeigen"
|
||||||
name="password"
|
name="password"
|
||||||
Required="true"
|
HelperText="Mindestens 6 Zeichen"
|
||||||
HelperText="Mindestens 6 Zeichen" />
|
OnAdornmentClick="@(() => _showRegPw = !_showRegPw)" />
|
||||||
<MudButton ButtonType="ButtonType.Submit"
|
<MudButton ButtonType="ButtonType.Submit"
|
||||||
Variant="Variant.Filled"
|
Variant="Variant.Filled"
|
||||||
Color="Color.Secondary"
|
Color="Color.Secondary"
|
||||||
FullWidth="true"
|
FullWidth="true"
|
||||||
Size="Size.Large"
|
Size="Size.Large"
|
||||||
StartIcon="@Icons.Material.Filled.PersonAdd"
|
StartIcon="@Icons.Material.Filled.PersonAdd">
|
||||||
Class="mt-2">
|
|
||||||
Konto erstellen
|
Konto erstellen
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</form>
|
</form>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
</MudTabPanel>
|
||||||
|
|
||||||
|
</MudTabs>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
@@ -119,6 +104,12 @@
|
|||||||
@code {
|
@code {
|
||||||
private int _activeTab = 0;
|
private int _activeTab = 0;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
|
private string _loginUsername = "";
|
||||||
|
private string _loginPassword = "";
|
||||||
|
private string _regUsername = "";
|
||||||
|
private string _regPassword = "";
|
||||||
|
private bool _showLoginPw;
|
||||||
|
private bool _showRegPw;
|
||||||
|
|
||||||
[SupplyParameterFromQuery(Name = "error")]
|
[SupplyParameterFromQuery(Name = "error")]
|
||||||
public string? ErrorParam { get; set; }
|
public string? ErrorParam { get; set; }
|
||||||
@@ -137,4 +128,3 @@
|
|||||||
_activeTab = TabParam == "register" ? 1 : 0;
|
_activeTab = TabParam == "register" ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ else
|
|||||||
private async Task LoadMonth()
|
private async Task LoadMonth()
|
||||||
{
|
{
|
||||||
var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
|
var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
|
||||||
var holidays = await HolidayService.GetHolidaysAsync(_year, _settings.GermanState);
|
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||||
|
|
||||||
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
|
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject TimetrackerService TrackerService
|
@inject TimetrackerService TrackerService
|
||||||
@@ -128,65 +128,6 @@ else
|
|||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
@* ── Region & Feiertage ── *@
|
|
||||||
<MudItem xs="12" md="6">
|
|
||||||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
|
||||||
<MudCardHeader>
|
|
||||||
<CardHeaderContent>
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Public" Color="Color.Primary" />
|
|
||||||
<MudText Typo="Typo.h6" Style="font-weight:600">Region & Feiertage</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</CardHeaderContent>
|
|
||||||
</MudCardHeader>
|
|
||||||
<MudCardContent>
|
|
||||||
<MudStack Spacing="4">
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
|
||||||
Wähle dein Bundesland aus, um Bundesland-spezifische Feiertage zu berücksichtigen.
|
|
||||||
</MudText>
|
|
||||||
<MudSelect T="string" Label="Bundesland" Variant="Variant.Outlined" @bind-Value="_settings.GermanState" Clearable="true" Placeholder="Nur bundesweite Feiertage">
|
|
||||||
@foreach (var state in GermanStates)
|
|
||||||
{
|
|
||||||
<MudSelectItem Value="@state.Key">@state.Value</MudSelectItem>
|
|
||||||
}
|
|
||||||
</MudSelect>
|
|
||||||
</MudStack>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
@* ── Gleitzeitkonto ── *@
|
|
||||||
<MudItem xs="12" md="6">
|
|
||||||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
|
||||||
<MudCardHeader>
|
|
||||||
<CardHeaderContent>
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Primary" />
|
|
||||||
<MudText Typo="Typo.h6" Style="font-weight:600">Gleitzeitkonto-Start</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</CardHeaderContent>
|
|
||||||
</MudCardHeader>
|
|
||||||
<MudCardContent>
|
|
||||||
<MudStack Spacing="4">
|
|
||||||
<MudDatePicker Label="Berechnungsstart"
|
|
||||||
@bind-Date="_flexStartDate"
|
|
||||||
HelperText="Gleitzeitberechnung läuft ab diesem Datum. Wenn leer, ab dem ersten Arbeitseintrag."
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Clearable="true"
|
|
||||||
DateFormat="dd.MM.yyyy"
|
|
||||||
PickerVariant="PickerVariant.Inline" />
|
|
||||||
|
|
||||||
<MudNumericField @bind-Value="_settings.FlexTimeStartingBalanceHours"
|
|
||||||
Label="Anfangsüberstunden (h)"
|
|
||||||
Variant="Variant.Outlined"
|
|
||||||
Step="0.5"
|
|
||||||
Format="0.##"
|
|
||||||
HelperText="Stufensaldo (Guthaben/Schulden) zum Berechnungsstart" />
|
|
||||||
</MudStack>
|
|
||||||
</MudCardContent>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
|
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
@* ── Speichern-Button ── *@
|
@* ── Speichern-Button ── *@
|
||||||
@@ -437,32 +378,6 @@ else
|
|||||||
|
|
||||||
private AppSettings? _settings;
|
private AppSettings? _settings;
|
||||||
private int _userId;
|
private int _userId;
|
||||||
private DateTime? _flexStartDate
|
|
||||||
{
|
|
||||||
get => _settings?.FlexTimeStartDate?.ToDateTime(TimeOnly.MinValue);
|
|
||||||
set => _settings!.FlexTimeStartDate = value.HasValue ? DateOnly.FromDateTime(value.Value) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> GermanStates = new()
|
|
||||||
{
|
|
||||||
{ "DE-BW", "Baden-Württemberg" },
|
|
||||||
{ "DE-BY", "Bayern" },
|
|
||||||
{ "DE-BE", "Berlin" },
|
|
||||||
{ "DE-BB", "Brandenburg" },
|
|
||||||
{ "DE-HB", "Bremen" },
|
|
||||||
{ "DE-HH", "Hamburg" },
|
|
||||||
{ "DE-HE", "Hessen" },
|
|
||||||
{ "DE-MV", "Mecklenburg-Vorpommern" },
|
|
||||||
{ "DE-NI", "Niedersachsen" },
|
|
||||||
{ "DE-NW", "Nordrhein-Westfalen" },
|
|
||||||
{ "DE-RP", "Rheinland-Pfalz" },
|
|
||||||
{ "DE-SL", "Saarland" },
|
|
||||||
{ "DE-SN", "Sachsen" },
|
|
||||||
{ "DE-ST", "Sachsen-Anhalt" },
|
|
||||||
{ "DE-SH", "Schleswig-Holstein" },
|
|
||||||
{ "DE-TH", "Thüringen" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private int _vacYear = DateTime.Today.Year;
|
private int _vacYear = DateTime.Today.Year;
|
||||||
private List<VacationDay> _vacationDays = [];
|
private List<VacationDay> _vacationDays = [];
|
||||||
private DateTime? _newVacDateFrom;
|
private DateTime? _newVacDateFrom;
|
||||||
@@ -496,7 +411,7 @@ else
|
|||||||
_userId = int.Parse(claim.Value);
|
_userId = int.Parse(claim.Value);
|
||||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||||
await LoadVacations();
|
await LoadVacations();
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
|
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadVacations()
|
private async Task LoadVacations()
|
||||||
@@ -514,8 +429,6 @@ else
|
|||||||
{
|
{
|
||||||
if (_settings == null) return;
|
if (_settings == null) return;
|
||||||
await TrackerService.SaveSettingsAsync(_settings);
|
await TrackerService.SaveSettingsAsync(_settings);
|
||||||
// Nach dem Speichern Feiertage neu laden, falls sich das Bundesland geändert hat
|
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
|
|
||||||
Snackbar.Add("Einstellungen gespeichert", Severity.Success);
|
Snackbar.Add("Einstellungen gespeichert", Severity.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,14 +468,14 @@ else
|
|||||||
private async Task ChangeHolYear(int delta)
|
private async Task ChangeHolYear(int delta)
|
||||||
{
|
{
|
||||||
_holYear += delta;
|
_holYear += delta;
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FetchHolidays()
|
private async Task FetchHolidays()
|
||||||
{
|
{
|
||||||
_fetchingHolidays = true;
|
_fetchingHolidays = true;
|
||||||
var (success, message) = await HolidayService.FetchAndStoreAsync(_holYear);
|
var (success, message) = await HolidayService.FetchAndStoreAsync(_holYear);
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||||
_fetchingHolidays = false;
|
_fetchingHolidays = false;
|
||||||
Snackbar.Add(message, success ? Severity.Success : Severity.Error);
|
Snackbar.Add(message, success ? Severity.Success : Severity.Error);
|
||||||
}
|
}
|
||||||
@@ -570,7 +483,7 @@ else
|
|||||||
private async Task DeleteHoliday(int id)
|
private async Task DeleteHoliday(int id)
|
||||||
{
|
{
|
||||||
await HolidayService.DeleteAsync(id);
|
await HolidayService.DeleteAsync(id);
|
||||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||||
Snackbar.Add("Feiertag entfernt", Severity.Info);
|
Snackbar.Add("Feiertag entfernt", Severity.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ else
|
|||||||
|
|
||||||
private async Task LoadYear()
|
private async Task LoadYear()
|
||||||
{
|
{
|
||||||
var holidays = await HolidayService.GetHolidaysAsync(_year, _settings.GermanState);
|
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||||
_holidays = holidays.ToDictionary(h => h.Date, h => h.Name);
|
_holidays = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||||
_vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
_vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ public class AppSettings
|
|||||||
public double DailyTargetHours { get; set; } = 7.5;
|
public double DailyTargetHours { get; set; } = 7.5;
|
||||||
public int MinimumBreakMinutes { get; set; } = 30;
|
public int MinimumBreakMinutes { get; set; } = 30;
|
||||||
public int VacationDaysPerYear { get; set; } = 30;
|
public int VacationDaysPerYear { get; set; } = 30;
|
||||||
public string? GermanState { get; set; }
|
|
||||||
public DateOnly? FlexTimeStartDate { get; set; }
|
|
||||||
public double FlexTimeStartingBalanceHours { get; set; } = 0.0;
|
|
||||||
|
|
||||||
// Arbeitstage
|
// Arbeitstage
|
||||||
public bool WorkMonday { get; set; } = true;
|
public bool WorkMonday { get; set; } = true;
|
||||||
|
|||||||
+2
-41
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace timetracker.Data;
|
namespace timetracker.Data;
|
||||||
|
|
||||||
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNotificationService notifier)
|
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||||
{
|
{
|
||||||
public async Task<User?> LoginAsync(string username, string password)
|
public async Task<User?> LoginAsync(string username, string password)
|
||||||
{
|
{
|
||||||
@@ -15,46 +15,8 @@ public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNo
|
|||||||
return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
|
return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<User>> GetAllUsersAsync()
|
public async Task<(User? User, string? Error)> RegisterAsync(string username, string password)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
|
||||||
return await db.Users
|
|
||||||
.OrderBy(u => u.Username)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteUserAsync(int userId)
|
|
||||||
{
|
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
|
||||||
var user = await db.Users.FindAsync(userId);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
db.Users.Remove(user);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
await notifier.NotifyUserDeletedAsync(userId);
|
|
||||||
await notifier.NotifyUsersChangedAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> RenameUserAsync(int userId, string newUsername)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(newUsername) || newUsername.Length < 3)
|
|
||||||
return "Benutzername muss mindestens 3 Zeichen lang sein.";
|
|
||||||
|
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
|
||||||
if (await db.Users.AnyAsync(u => u.Username == newUsername && u.Id != userId))
|
|
||||||
return "Benutzername bereits vergeben.";
|
|
||||||
|
|
||||||
var user = await db.Users.FindAsync(userId);
|
|
||||||
if (user == null) return "Benutzer nicht gefunden.";
|
|
||||||
|
|
||||||
user.Username = newUsername;
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
await notifier.NotifyUsersChangedAsync();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(User? User, string? Error)> RegisterAsync(string username, string password) {
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
|
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
|
||||||
return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
|
return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
|
||||||
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
|
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
|
||||||
@@ -68,7 +30,6 @@ public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNo
|
|||||||
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
|
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
|
||||||
db.Users.Add(user);
|
db.Users.Add(user);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
await notifier.NotifyUsersChangedAsync();
|
|
||||||
return (user, null);
|
return (user, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-17
@@ -8,22 +8,13 @@ public class HolidayService(IDbContextFactory<TimetrackerDbContext> factory, Htt
|
|||||||
{
|
{
|
||||||
private const string ApiUrl = "https://date.nager.at/api/v3/PublicHolidays/{0}/DE";
|
private const string ApiUrl = "https://date.nager.at/api/v3/PublicHolidays/{0}/DE";
|
||||||
|
|
||||||
public async Task<List<PublicHoliday>> GetHolidaysAsync(int year, string? stateCode = null)
|
public async Task<List<PublicHoliday>> GetHolidaysAsync(int year)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
var holidays = await db.PublicHolidays
|
return await db.PublicHolidays
|
||||||
.Where(h => h.Date.Year == year)
|
.Where(h => h.Date.Year == year)
|
||||||
.OrderBy(h => h.Date)
|
.OrderBy(h => h.Date)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(stateCode))
|
|
||||||
{
|
|
||||||
// Default: return only global holidays (where Counties is null or empty)
|
|
||||||
return holidays.Where(h => string.IsNullOrEmpty(h.Counties)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return global holidays OR holidays that match the user's state code
|
|
||||||
return holidays.Where(h => string.IsNullOrEmpty(h.Counties) || h.Counties.Split(',').Contains(stateCode)).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool Success, string Message)> FetchAndStoreAsync(int year)
|
public async Task<(bool Success, string Message)> FetchAndStoreAsync(int year)
|
||||||
@@ -43,8 +34,7 @@ public class HolidayService(IDbContextFactory<TimetrackerDbContext> factory, Htt
|
|||||||
.Select(h => new PublicHoliday
|
.Select(h => new PublicHoliday
|
||||||
{
|
{
|
||||||
Date = DateOnly.Parse(h.Date),
|
Date = DateOnly.Parse(h.Date),
|
||||||
Name = h.LocalName,
|
Name = h.LocalName
|
||||||
Counties = h.Counties != null && h.Counties.Count > 0 ? string.Join(",", h.Counties) : null
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@@ -74,9 +64,5 @@ public class HolidayService(IDbContextFactory<TimetrackerDbContext> factory, Htt
|
|||||||
|
|
||||||
[JsonPropertyName("localName")]
|
[JsonPropertyName("localName")]
|
||||||
public string LocalName { get; set; } = "";
|
public string LocalName { get; set; } = "";
|
||||||
|
|
||||||
[JsonPropertyName("counties")]
|
|
||||||
public List<string>? Counties { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using timetracker.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace timetracker.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(TimetrackerDbContext))]
|
|
||||||
[Migration("20260607213215_AddFlexTimeAndHolidayState")]
|
|
||||||
partial class AddFlexTimeAndHolidayState
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.8");
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.AppSettings", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<double>("DailyTargetHours")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<DateOnly?>("FlexTimeStartDate")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double>("FlexTimeStartingBalanceHours")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("GermanState")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("MinimumBreakMinutes")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("VacationDaysPerYear")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkFriday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkMonday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkSaturday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkSunday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkThursday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkTuesday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("WorkWednesday")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("AppSettings");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<TimeOnly?>("EndTime")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<TimeOnly?>("StartTime")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("WorkDayId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("WorkDayId");
|
|
||||||
|
|
||||||
b.ToTable("BreakEntries");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Counties")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("Date")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("PublicHolidays");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("PasswordSalt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("Date")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Note")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("VacationDays");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("Date")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<TimeOnly?>("EndTime")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<TimeOnly?>("StartTime")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("WorkDays");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("timetracker.Data.WorkDay", "WorkDay")
|
|
||||||
.WithMany("Breaks")
|
|
||||||
.HasForeignKey("WorkDayId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("WorkDay");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Breaks");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace timetracker.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddFlexTimeAndHolidayState : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "Counties",
|
|
||||||
table: "PublicHolidays",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateOnly>(
|
|
||||||
name: "FlexTimeStartDate",
|
|
||||||
table: "AppSettings",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<double>(
|
|
||||||
name: "FlexTimeStartingBalanceHours",
|
|
||||||
table: "AppSettings",
|
|
||||||
type: "REAL",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: 0.0);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "GermanState",
|
|
||||||
table: "AppSettings",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Counties",
|
|
||||||
table: "PublicHolidays");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "FlexTimeStartDate",
|
|
||||||
table: "AppSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "FlexTimeStartingBalanceHours",
|
|
||||||
table: "AppSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "GermanState",
|
|
||||||
table: "AppSettings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,15 +26,6 @@ namespace timetracker.Data.Migrations
|
|||||||
b.Property<double>("DailyTargetHours")
|
b.Property<double>("DailyTargetHours")
|
||||||
.HasColumnType("REAL");
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
b.Property<DateOnly?>("FlexTimeStartDate")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double>("FlexTimeStartingBalanceHours")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("GermanState")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("MinimumBreakMinutes")
|
b.Property<int>("MinimumBreakMinutes")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@@ -98,9 +89,6 @@ namespace timetracker.Data.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Counties")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("Date")
|
b.Property<DateOnly>("Date")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ public class PublicHoliday
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
public string? Counties { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,76 +111,24 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
|||||||
public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
|
public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
|
||||||
{
|
{
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
|
var allDays = await db.WorkDays
|
||||||
// 1. Finde das Startdatum für die Berechnung
|
|
||||||
var firstDay = await db.WorkDays
|
|
||||||
.Where(w => w.UserId == userId)
|
|
||||||
.OrderBy(w => w.Date)
|
|
||||||
.Select(w => (DateOnly?)w.Date)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
if (firstDay == null && !settings.FlexTimeStartDate.HasValue)
|
|
||||||
return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);
|
|
||||||
|
|
||||||
var startDate = settings.FlexTimeStartDate ?? firstDay!.Value;
|
|
||||||
var endDate = DateOnly.FromDateTime(DateTime.Today);
|
|
||||||
|
|
||||||
if (startDate > endDate)
|
|
||||||
return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);
|
|
||||||
|
|
||||||
// 2. Lade alle erfassten Arbeitstage in diesem Zeitraum
|
|
||||||
var workDays = await db.WorkDays
|
|
||||||
.Include(w => w.Breaks)
|
.Include(w => w.Breaks)
|
||||||
.Where(w => w.UserId == userId && w.Date >= startDate && w.Date <= endDate)
|
.Where(w => w.UserId == userId && w.StartTime != null && w.EndTime != null)
|
||||||
.ToDictionaryAsync(w => w.Date);
|
|
||||||
|
|
||||||
// 3. Lade alle Feiertage in diesem Zeitraum
|
|
||||||
var holidaysList = await db.PublicHolidays
|
|
||||||
.Where(h => h.Date >= startDate && h.Date <= endDate)
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var state = settings.GermanState;
|
var total = TimeSpan.Zero;
|
||||||
var holidaySet = holidaysList
|
foreach (var wd in allDays)
|
||||||
.Where(h => string.IsNullOrEmpty(h.Counties) || (!string.IsNullOrEmpty(state) && h.Counties.Split(',').Contains(state)))
|
|
||||||
.Select(h => h.Date)
|
|
||||||
.ToHashSet();
|
|
||||||
|
|
||||||
// 4. Lade alle Urlaubstage in diesem Zeitraum
|
|
||||||
var vacationSet = await db.VacationDays
|
|
||||||
.Where(v => v.UserId == userId && v.Date >= startDate && v.Date <= endDate)
|
|
||||||
.Select(v => v.Date)
|
|
||||||
.ToHashSetAsync();
|
|
||||||
|
|
||||||
double totalOvertimeHours = settings.FlexTimeStartingBalanceHours;
|
|
||||||
|
|
||||||
for (var date = startDate; date <= endDate; date = date.AddDays(1))
|
|
||||||
{
|
|
||||||
bool isWorkDay = settings.IsWorkDay(date.DayOfWeek);
|
|
||||||
bool isHoliday = holidaySet.Contains(date);
|
|
||||||
bool isVacation = vacationSet.Contains(date);
|
|
||||||
|
|
||||||
// Sollzeit gilt nur an regulären Arbeitstagen, die weder Feiertag noch Urlaub sind
|
|
||||||
double target = (isWorkDay && !isHoliday && !isVacation) ? settings.DailyTargetHours : 0.0;
|
|
||||||
double actual = 0.0;
|
|
||||||
|
|
||||||
if (workDays.TryGetValue(date, out var wd) && wd.StartTime != null && wd.EndTime != null)
|
|
||||||
{
|
|
||||||
var gross = wd.EndTime.Value.ToTimeSpan() - wd.StartTime.Value.ToTimeSpan();
|
|
||||||
if (gross > TimeSpan.Zero)
|
|
||||||
{
|
{
|
||||||
|
if (!settings.IsWorkDay(wd.Date.DayOfWeek)) continue;
|
||||||
|
var gross = wd.EndTime!.Value.ToTimeSpan() - wd.StartTime!.Value.ToTimeSpan();
|
||||||
|
if (gross <= TimeSpan.Zero) continue;
|
||||||
var breakTotal = wd.Breaks
|
var breakTotal = wd.Breaks
|
||||||
.Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime)
|
.Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime)
|
||||||
.Aggregate(TimeSpan.Zero, (s, b) =>
|
.Aggregate(TimeSpan.Zero, (s, b) =>
|
||||||
s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan()));
|
s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan()));
|
||||||
actual = (gross - breakTotal).TotalHours;
|
total += gross - breakTotal - TimeSpan.FromHours(settings.DailyTargetHours);
|
||||||
if (actual < 0) actual = 0.0;
|
|
||||||
}
|
}
|
||||||
}
|
return total;
|
||||||
|
|
||||||
totalOvertimeHours += (actual - target);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TimeSpan.FromHours(totalOvertimeHours);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Monatsübersicht ───────────────────────────────────────────────────
|
// ── Monatsübersicht ───────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace timetracker.Data;
|
|
||||||
|
|
||||||
public class UserNotificationService
|
|
||||||
{
|
|
||||||
public event Func<Task>? OnUsersChanged;
|
|
||||||
public event Func<int, Task>? OnUserDeleted;
|
|
||||||
|
|
||||||
public async Task NotifyUsersChangedAsync()
|
|
||||||
{
|
|
||||||
if (OnUsersChanged != null)
|
|
||||||
await OnUsersChanged.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task NotifyUserDeletedAsync(int userId)
|
|
||||||
{
|
|
||||||
if (OnUserDeleted != null)
|
|
||||||
await OnUserDeleted.Invoke(userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-2
@@ -7,7 +7,7 @@ COPY timetracker.csproj ./
|
|||||||
RUN dotnet restore timetracker.csproj
|
RUN dotnet restore timetracker.csproj
|
||||||
|
|
||||||
COPY . ./
|
COPY . ./
|
||||||
RUN dotnet publish -c Release -o /app/publish
|
RUN dotnet publish -c Release -o /app/publish --no-restore
|
||||||
|
|
||||||
# ── Runtime Stage ─────────────────────────────────────────────────────────────
|
# ── Runtime Stage ─────────────────────────────────────────────────────────────
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||||
@@ -19,7 +19,6 @@ RUN mkdir -p /data
|
|||||||
|
|
||||||
ENV ASPNETCORE_HTTP_PORTS=8080
|
ENV ASPNETCORE_HTTP_PORTS=8080
|
||||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
|
|
||||||
ENV TIMETRACKER_DB_PATH=/data/timetracker.db
|
ENV TIMETRACKER_DB_PATH=/data/timetracker.db
|
||||||
ENV EnableHttpsRedirect=false
|
ENV EnableHttpsRedirect=false
|
||||||
|
|
||||||
|
|||||||
+1
-19
@@ -1,7 +1,6 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using timetracker.Components;
|
using timetracker.Components;
|
||||||
@@ -17,14 +16,9 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
|||||||
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||||
options.SlidingExpiration = true;
|
options.SlidingExpiration = true;
|
||||||
});
|
});
|
||||||
builder.Services.AddAuthorization(options =>
|
builder.Services.AddAuthorization();
|
||||||
{
|
|
||||||
options.AddPolicy("AdminOnly", policy =>
|
|
||||||
policy.RequireClaim(System.Security.Claims.ClaimTypes.Name, "marc"));
|
|
||||||
});
|
|
||||||
builder.Services.AddCascadingAuthenticationState();
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddSingleton<timetracker.Data.UserNotificationService>();
|
|
||||||
builder.Services.AddScoped<AuthService>();
|
builder.Services.AddScoped<AuthService>();
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
@@ -48,14 +42,6 @@ using (var scope = app.Services.CreateScope())
|
|||||||
await db.Database.MigrateAsync();
|
await db.Database.MigrateAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var forwardedHeadersOptions = new ForwardedHeadersOptions
|
|
||||||
{
|
|
||||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
|
||||||
};
|
|
||||||
forwardedHeadersOptions.KnownProxies.Clear();
|
|
||||||
forwardedHeadersOptions.KnownIPNetworks.Clear();
|
|
||||||
app.UseForwardedHeaders(forwardedHeadersOptions);
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@@ -69,10 +55,6 @@ if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelop
|
|||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statische Dateien (inkl. _framework/, _content/) vor Auth bedienen,
|
|
||||||
// damit Blazor-JS und MudBlazor-CSS nie durch Auth-Middleware geblockt werden
|
|
||||||
app.UseStaticFiles();
|
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
BIN
Binary file not shown.
+68
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.IO.Redist" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.1.0.0" newVersion="6.1.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.4.0" newVersion="4.0.4.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.0.1.0" newVersion="6.0.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+171
@@ -0,0 +1,171 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v8.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v8.0": {
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/5.0.0-2.25567.12": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Build.Locator": "1.10.2",
|
||||||
|
"Newtonsoft.Json": "13.0.3",
|
||||||
|
"System.Collections.Immutable": "9.0.0",
|
||||||
|
"System.CommandLine": "2.0.0-rtm.25509.106"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": {}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Build.Locator/1.10.2": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.Build.Locator.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.10.2.26959"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||||
|
"assemblyVersion": "13.0.0.0",
|
||||||
|
"fileVersion": "13.0.3.27908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Collections.Immutable/9.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.Collections.Immutable.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.CommandLine/2.0.0-rtm.25509.106": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.CommandLine.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.0.25.51006"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net8.0/cs/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/System.CommandLine.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/5.0.0-2.25567.12": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"Microsoft.Build.Locator/1.10.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-F+nLS7IpgtslyxNvtD6Jalnf5WU08lu8yfJBNQl3cbEF3AMUphs4t7nPuRYaaU8QZyGrqtVi7i73LhAe/yHx7A==",
|
||||||
|
"path": "microsoft.build.locator/1.10.2",
|
||||||
|
"hashPath": "microsoft.build.locator.1.10.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||||
|
"path": "newtonsoft.json/13.0.3",
|
||||||
|
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Collections.Immutable/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
|
||||||
|
"path": "system.collections.immutable/9.0.0",
|
||||||
|
"hashPath": "system.collections.immutable.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.CommandLine/2.0.0-rtm.25509.106": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-IdCQOFNHQfK0hu3tzWOHFJLMaiEOR/4OynmOh+IfukrTIsCR4TTDm7lpuXQyMZ0eRfIyUcz06gHGJNlILAq/6A==",
|
||||||
|
"path": "system.commandline/2.0.0-rtm.25509.106",
|
||||||
|
"hashPath": "system.commandline.2.0.0-rtm.25509.106.nupkg.sha512"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net8.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "8.0.0"
|
||||||
|
},
|
||||||
|
"rollForward": "Major",
|
||||||
|
"configProperties": {
|
||||||
|
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user