Auth integration

This commit is contained in:
Wieland, Marc
2026-05-22 10:28:02 +02:00
parent 64c5f6aa2c
commit 7ab824e7c1
39 changed files with 681 additions and 57 deletions
+10
View File
@@ -6,5 +6,15 @@
<MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink> <MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
<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" />
<AuthorizeView>
<Authorized>
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-1" Spacing="1">
<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>
</MudStack>
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
</Authorized>
</AuthorizeView>
</MudNavMenu> </MudNavMenu>
+1
View File
@@ -1,5 +1,6 @@
@page "/feiertage" @page "/feiertage"
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize]
@inject HolidayService HolidayService @inject HolidayService HolidayService
<PageTitle>Feiertage Timetracker</PageTitle> <PageTitle>Feiertage Timetracker</PageTitle>
+16 -6
View File
@@ -1,8 +1,10 @@
@page "/" @page "/"
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize]
@inject TimetrackerService TrackerService @inject TimetrackerService TrackerService
@inject HolidayService HolidayService @inject HolidayService HolidayService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
<PageTitle>KW @_kw Wochenübersicht Timetracker</PageTitle> <PageTitle>KW @_kw Wochenübersicht Timetracker</PageTitle>
@@ -332,6 +334,7 @@ else
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE"); private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
private bool _loading = true; private bool _loading = true;
private int _userId;
private DateOnly _monday; private DateOnly _monday;
private List<DayVm> _days = []; private List<DayVm> _days = [];
private AppSettings _settings = new(); private AppSettings _settings = new();
@@ -349,10 +352,14 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null) return; // Prerender-Pass Circuit noch nicht authentifiziert
_userId = int.Parse(claim.Value);
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today)); _monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
_settings = await TrackerService.GetSettingsAsync(); _settings = await TrackerService.GetSettingsAsync(_userId);
await LoadWeek(); await LoadWeek();
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings); _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
_loading = false; _loading = false;
} }
@@ -364,11 +371,11 @@ else
_holidays = list.ToDictionary(h => h.Date, h => h.Name); _holidays = list.ToDictionary(h => h.Date, h => h.Name);
_holidayYear = _monday.Year; _holidayYear = _monday.Year;
} }
var dbDays = await TrackerService.GetWeekAsync(_monday); var dbDays = await TrackerService.GetWeekAsync(_userId, _monday);
_days = Enumerable.Range(0, 7).Select(i => _days = Enumerable.Range(0, 7).Select(i =>
{ {
var date = _monday.AddDays(i); var date = _monday.AddDays(i);
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date); return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId);
}).ToList(); }).ToList();
BuildWeekLabels(); BuildWeekLabels();
} }
@@ -421,7 +428,7 @@ else
private async Task SaveDay(DayVm day) private async Task SaveDay(DayVm day)
{ {
await TrackerService.UpsertWorkDayAsync(day.ToWorkDay()); await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings); _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
BuildWeekLabels(); BuildWeekLabels();
} }
@@ -486,6 +493,7 @@ else
private sealed class DayVm private sealed class DayVm
{ {
public int Id { get; set; } public int Id { get; set; }
public int UserId { get; set; }
public DateOnly Date { get; set; } public DateOnly Date { get; set; }
public TimeSpan? Start { get; set; } public TimeSpan? Start { get; set; }
public TimeSpan? End { get; set; } public TimeSpan? End { get; set; }
@@ -500,9 +508,10 @@ else
public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null; public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;
public static DayVm From(WorkDay? wd, DateOnly date) => new() public static DayVm From(WorkDay? wd, DateOnly date, int userId) => new()
{ {
Id = wd?.Id ?? 0, Id = wd?.Id ?? 0,
UserId = wd?.UserId ?? userId,
Date = date, Date = date,
Start = wd?.StartTime?.ToTimeSpan(), Start = wd?.StartTime?.ToTimeSpan(),
End = wd?.EndTime?.ToTimeSpan(), End = wd?.EndTime?.ToTimeSpan(),
@@ -517,6 +526,7 @@ else
public WorkDay ToWorkDay() => new() public WorkDay ToWorkDay() => new()
{ {
Id = Id, Id = Id,
UserId = UserId,
Date = Date, Date = Date,
StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null, StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null, EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
+130
View File
@@ -0,0 +1,130 @@
@page "/login"
@rendermode InteractiveServer
@attribute [AllowAnonymous]
@inject NavigationManager Nav
@inject ISnackbar Snackbar
<PageTitle>Anmelden Timetracker</PageTitle>
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-16">
<MudStack AlignItems="AlignItems.Center" Spacing="4">
@* ── Logo / Header ── *@
<MudStack AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.AccessTime"
Style="font-size:4rem; color:#1565C0" />
<MudText Typo="Typo.h4" Style="font-weight:700; color:#1565C0">Timetracker</MudText>
</MudStack>
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
<MudTabs @bind-ActivePanelIndex="_activeTab" Rounded="true" Centered="true" Color="Color.Primary">
@* ── Login ── *@
<MudTabPanel Text="Anmelden">
<MudStack Spacing="3" Class="mt-4">
@if (_error != null && _activeTab == 0)
{
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
}
<form action="/auth/login" method="post">
<MudStack Spacing="3">
<MudTextField @bind-Value="_loginUsername"
Label="Benutzername"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Person"
name="username"
AutoFocus="true" />
<MudTextField @bind-Value="_loginPassword"
Label="Passwort"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lock"
InputType="@(_showLoginPw ? InputType.Text : InputType.Password)"
AdornmentAriaLabel="Passwort anzeigen"
name="password"
OnAdornmentClick="@(() => _showLoginPw = !_showLoginPw)" />
<MudButton ButtonType="ButtonType.Submit"
Variant="Variant.Filled"
Color="Color.Primary"
FullWidth="true"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.Login">
Anmelden
</MudButton>
</MudStack>
</form>
</MudStack>
</MudTabPanel>
@* ── Registrieren ── *@
<MudTabPanel Text="Registrieren">
<MudStack Spacing="3" Class="mt-4">
@if (_error != null && _activeTab == 1)
{
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
}
<form action="/auth/register" method="post">
<MudStack Spacing="3">
<MudTextField @bind-Value="_regUsername"
Label="Benutzername"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Person"
name="username"
HelperText="Mindestens 3 Zeichen" />
<MudTextField @bind-Value="_regPassword"
Label="Passwort"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lock"
InputType="@(_showRegPw ? InputType.Text : InputType.Password)"
AdornmentAriaLabel="Passwort anzeigen"
name="password"
HelperText="Mindestens 6 Zeichen"
OnAdornmentClick="@(() => _showRegPw = !_showRegPw)" />
<MudButton ButtonType="ButtonType.Submit"
Variant="Variant.Filled"
Color="Color.Secondary"
FullWidth="true"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.PersonAdd">
Konto erstellen
</MudButton>
</MudStack>
</form>
</MudStack>
</MudTabPanel>
</MudTabs>
</MudPaper>
</MudStack>
</MudContainer>
@code {
private int _activeTab = 0;
private string? _error;
private string _loginUsername = "";
private string _loginPassword = "";
private string _regUsername = "";
private string _regPassword = "";
private bool _showLoginPw;
private bool _showRegPw;
[SupplyParameterFromQuery(Name = "error")]
public string? ErrorParam { get; set; }
[SupplyParameterFromQuery(Name = "tab")]
public string? TabParam { get; set; }
protected override void OnParametersSet()
{
_error = ErrorParam switch
{
"invalid" => "Benutzername oder Passwort falsch.",
not null => Uri.UnescapeDataString(ErrorParam),
_ => null
};
_activeTab = TabParam == "register" ? 1 : 0;
}
}
+10 -3
View File
@@ -1,7 +1,9 @@
@page "/month" @page "/month"
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize]
@inject TimetrackerService TrackerService @inject TimetrackerService TrackerService
@inject HolidayService HolidayService @inject HolidayService HolidayService
@inject AuthenticationStateProvider AuthStateProvider
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year Monatsübersicht Timetracker</PageTitle> <PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year Monatsübersicht Timetracker</PageTitle>
@@ -150,6 +152,7 @@ else
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE"); private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
private bool _loading = true; private bool _loading = true;
private int _userId;
private int _year = DateTime.Today.Year; private int _year = DateTime.Today.Year;
private int _month = DateTime.Today.Month; private int _month = DateTime.Today.Month;
private List<MonthDayVm> _days = []; private List<MonthDayVm> _days = [];
@@ -167,16 +170,20 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_settings = await TrackerService.GetSettingsAsync(); var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null) return;
_userId = int.Parse(claim.Value);
_settings = await TrackerService.GetSettingsAsync(_userId);
await LoadMonth(); await LoadMonth();
_loading = false; _loading = false;
} }
private async Task LoadMonth() private async Task LoadMonth()
{ {
var workDays = await TrackerService.GetMonthAsync(_year, _month); var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
var holidays = await HolidayService.GetHolidaysAsync(_year); var holidays = await HolidayService.GetHolidaysAsync(_year);
var vacations = await TrackerService.GetVacationDaysAsync(_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);
var vacationSet = vacations.Select(v => v.Date).ToHashSet(); var vacationSet = vacations.Select(v => v.Date).ToHashSet();
+11 -4
View File
@@ -1,8 +1,10 @@
@page "/settings" @page "/settings"
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize]
@inject TimetrackerService TrackerService @inject TimetrackerService TrackerService
@inject HolidayService HolidayService @inject HolidayService HolidayService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
<PageTitle>Einstellungen Timetracker</PageTitle> <PageTitle>Einstellungen Timetracker</PageTitle>
@@ -375,6 +377,7 @@ else
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE"); private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
private AppSettings? _settings; private AppSettings? _settings;
private int _userId;
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;
@@ -402,14 +405,18 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_settings = await TrackerService.GetSettingsAsync(); var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null) return;
_userId = int.Parse(claim.Value);
_settings = await TrackerService.GetSettingsAsync(_userId);
await LoadVacations(); await LoadVacations();
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear); _holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
} }
private async Task LoadVacations() private async Task LoadVacations()
{ {
_vacationDays = await TrackerService.GetVacationDaysAsync(_vacYear); _vacationDays = await TrackerService.GetVacationDaysAsync(_userId, _vacYear);
} }
private async Task ChangeYear(int delta) private async Task ChangeYear(int delta)
@@ -438,7 +445,7 @@ else
{ {
if (_settings!.IsWorkDay(current.DayOfWeek)) if (_settings!.IsWorkDay(current.DayOfWeek))
{ {
await TrackerService.AddVacationDayAsync(new VacationDay { Date = current, Note = note }); await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = current, Note = note });
added++; added++;
} }
current = current.AddDays(1); current = current.AddDays(1);
@@ -452,7 +459,7 @@ else
private async Task RemoveVacation(int id) private async Task RemoveVacation(int id)
{ {
await TrackerService.RemoveVacationDayAsync(id); await TrackerService.RemoveVacationDayAsync(_userId, id);
await LoadVacations(); await LoadVacations();
Snackbar.Add("Urlaubstag entfernt", Severity.Info); Snackbar.Add("Urlaubstag entfernt", Severity.Info);
} }
+10 -3
View File
@@ -1,8 +1,10 @@
@page "/urlaub-maximizer" @page "/urlaub-maximizer"
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize]
@inject TimetrackerService TrackerService @inject TimetrackerService TrackerService
@inject HolidayService HolidayService @inject HolidayService HolidayService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
<PageTitle>Urlaubs-Maximizer Timetracker</PageTitle> <PageTitle>Urlaubs-Maximizer Timetracker</PageTitle>
@@ -232,10 +234,15 @@ else
private int _remainingDays; private int _remainingDays;
private List<Suggestion> _suggestions = []; private List<Suggestion> _suggestions = [];
private string _subLabel = ""; private string _subLabel = "";
private int _userId;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_settings = await TrackerService.GetSettingsAsync(); var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null) return;
_userId = int.Parse(claim.Value);
_settings = await TrackerService.GetSettingsAsync(_userId);
await LoadYear(); await LoadYear();
_loading = false; _loading = false;
} }
@@ -243,7 +250,7 @@ else
private async Task LoadYear() private async Task LoadYear()
{ {
var holidays = await HolidayService.GetHolidaysAsync(_year); var holidays = await HolidayService.GetHolidaysAsync(_year);
var vacations = await TrackerService.GetVacationDaysAsync(_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();
_remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count); _remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count);
@@ -262,7 +269,7 @@ else
private async Task TakeSuggestion(Suggestion s) private async Task TakeSuggestion(Suggestion s)
{ {
foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d))) foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d)))
await TrackerService.AddVacationDayAsync(new VacationDay { Date = d, Note = "Urlaubs-Maximizer" }); await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = d, Note = "Urlaubs-Maximizer" });
await LoadYear(); await LoadYear();
var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage"; var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage";
Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen {s.TotalFreeDays} Tage frei!", Severity.Success); Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen {s.TotalFreeDays} Tage frei!", Severity.Success);
+9
View File
@@ -0,0 +1,9 @@
@inject NavigationManager Nav
@code {
protected override void OnInitialized()
{
var returnUrl = Uri.EscapeDataString(Nav.Uri);
Nav.NavigateTo($"/login?returnUrl={returnUrl}", forceLoad: true);
}
}
+5 -1
View File
@@ -2,7 +2,11 @@
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)"> <Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="routeData" Selector="h1" /> <FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found> </Found>
</Router> </Router>
+3
View File
@@ -1,5 +1,8 @@
@using System.Net.Http @using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
+1
View File
@@ -3,6 +3,7 @@ namespace timetracker.Data;
public class AppSettings public class AppSettings
{ {
public int Id { get; set; } public int Id { get; set; }
public int UserId { get; set; }
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;
+57
View File
@@ -0,0 +1,57 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
namespace timetracker.Data;
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
{
public async Task<User?> LoginAsync(string username, string password)
{
await using var db = await factory.CreateDbContextAsync();
var user = await db.Users
.FirstOrDefaultAsync(u => u.Username == username);
if (user == null) return null;
return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
}
public async Task<(User? User, string? Error)> RegisterAsync(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
return (null, "Passwort muss mindestens 6 Zeichen lang sein.");
await using var db = await factory.CreateDbContextAsync();
if (await db.Users.AnyAsync(u => u.Username == username))
return (null, "Benutzername bereits vergeben.");
var (hash, salt) = HashPassword(password);
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
db.Users.Add(user);
await db.SaveChangesAsync();
return (user, null);
}
private static (string hash, string salt) HashPassword(string password)
{
var saltBytes = RandomNumberGenerator.GetBytes(32);
var salt = Convert.ToBase64String(saltBytes);
var hash = ComputeHash(password, salt);
return (hash, salt);
}
private static bool VerifyPassword(string password, string hash, string salt)
=> ComputeHash(password, salt) == hash;
private static string ComputeHash(string password, string salt)
{
var hash = Rfc2898DeriveBytes.Pbkdf2(
Encoding.UTF8.GetBytes(password),
Convert.FromBase64String(salt),
iterations: 200_000,
HashAlgorithmName.SHA256,
outputLength: 32);
return Convert.ToBase64String(hash);
}
}
+191
View File
@@ -0,0 +1,191 @@
// <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("20260522081459_AddMultiUser")]
partial class AddMultiUser
{
/// <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<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<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
}
}
}
@@ -0,0 +1,69 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace timetracker.Data.Migrations
{
/// <inheritdoc />
public partial class AddMultiUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "WorkDays",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "VacationDays",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "AppSettings",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Username = table.Column<string>(type: "TEXT", nullable: false),
PasswordHash = table.Column<string>(type: "TEXT", nullable: false),
PasswordSalt = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropColumn(
name: "UserId",
table: "WorkDays");
migrationBuilder.DropColumn(
name: "UserId",
table: "VacationDays");
migrationBuilder.DropColumn(
name: "UserId",
table: "AppSettings");
}
}
}
@@ -29,6 +29,9 @@ namespace timetracker.Data.Migrations
b.Property<int>("MinimumBreakMinutes") b.Property<int>("MinimumBreakMinutes")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("VacationDaysPerYear") b.Property<int>("VacationDaysPerYear")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -58,24 +61,6 @@ namespace timetracker.Data.Migrations
b.ToTable("AppSettings"); b.ToTable("AppSettings");
}); });
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateOnly>("Date")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PublicHolidays");
});
modelBuilder.Entity("timetracker.Data.BreakEntry", b => modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -98,6 +83,47 @@ namespace timetracker.Data.Migrations
b.ToTable("BreakEntries"); b.ToTable("BreakEntries");
}); });
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
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 => modelBuilder.Entity("timetracker.Data.VacationDay", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -110,6 +136,9 @@ namespace timetracker.Data.Migrations
b.Property<string>("Note") b.Property<string>("Note")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("VacationDays"); b.ToTable("VacationDays");
@@ -130,6 +159,9 @@ namespace timetracker.Data.Migrations
b.Property<TimeOnly?>("StartTime") b.Property<TimeOnly?>("StartTime")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("WorkDays"); b.ToTable("WorkDays");
+1
View File
@@ -4,6 +4,7 @@ namespace timetracker.Data;
public class TimetrackerDbContext(DbContextOptions<TimetrackerDbContext> options) : DbContext(options) public class TimetrackerDbContext(DbContextOptions<TimetrackerDbContext> options) : DbContext(options)
{ {
public DbSet<User> Users => Set<User>();
public DbSet<WorkDay> WorkDays => Set<WorkDay>(); public DbSet<WorkDay> WorkDays => Set<WorkDay>();
public DbSet<BreakEntry> BreakEntries => Set<BreakEntry>(); public DbSet<BreakEntry> BreakEntries => Set<BreakEntry>();
public DbSet<AppSettings> AppSettings => Set<AppSettings>(); public DbSet<AppSettings> AppSettings => Set<AppSettings>();
+16 -16
View File
@@ -4,12 +4,12 @@ namespace timetracker.Data;
public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory) public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
{ {
public async Task<List<WorkDay>> GetWeekAsync(DateOnly monday) public async Task<List<WorkDay>> GetWeekAsync(int userId, DateOnly monday)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
return await db.WorkDays return await db.WorkDays
.Include(w => w.Breaks) .Include(w => w.Breaks)
.Where(w => w.Date >= monday && w.Date < monday.AddDays(7)) .Where(w => w.UserId == userId && w.Date >= monday && w.Date < monday.AddDays(7))
.OrderBy(w => w.Date) .OrderBy(w => w.Date)
.ToListAsync(); .ToListAsync();
} }
@@ -19,7 +19,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
var existing = await db.WorkDays var existing = await db.WorkDays
.Include(w => w.Breaks) .Include(w => w.Breaks)
.FirstOrDefaultAsync(w => w.Date == workDay.Date); .FirstOrDefaultAsync(w => w.UserId == workDay.UserId && w.Date == workDay.Date);
if (existing == null) if (existing == null)
{ {
@@ -45,17 +45,17 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
public async Task<AppSettings> GetSettingsAsync() public async Task<AppSettings> GetSettingsAsync(int userId)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
return await db.AppSettings.FindAsync(1) ?? new AppSettings { Id = 1 }; return await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == userId)
?? new AppSettings { UserId = userId };
} }
public async Task SaveSettingsAsync(AppSettings settings) public async Task SaveSettingsAsync(AppSettings settings)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
settings.Id = 1; var existing = await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == settings.UserId);
var existing = await db.AppSettings.FindAsync(1);
if (existing == null) if (existing == null)
db.AppSettings.Add(settings); db.AppSettings.Add(settings);
else else
@@ -75,11 +75,11 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
} }
// ── Urlaub ──────────────────────────────────────────────────────────── // ── Urlaub ────────────────────────────────────────────────────────────
public async Task<List<VacationDay>> GetVacationDaysAsync(int year) public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
return await db.VacationDays return await db.VacationDays
.Where(v => v.Date.Year == year) .Where(v => v.UserId == userId && v.Date.Year == year)
.OrderBy(v => v.Date) .OrderBy(v => v.Date)
.ToListAsync(); .ToListAsync();
} }
@@ -87,7 +87,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
public async Task AddVacationDayAsync(VacationDay vacationDay) public async Task AddVacationDayAsync(VacationDay vacationDay)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
var exists = await db.VacationDays.AnyAsync(v => v.Date == vacationDay.Date); var exists = await db.VacationDays.AnyAsync(v => v.UserId == vacationDay.UserId && v.Date == vacationDay.Date);
if (!exists) if (!exists)
{ {
vacationDay.Id = 0; vacationDay.Id = 0;
@@ -96,10 +96,10 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
} }
} }
public async Task RemoveVacationDayAsync(int id) public async Task RemoveVacationDayAsync(int userId, int id)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
var v = await db.VacationDays.FindAsync(id); var v = await db.VacationDays.FirstOrDefaultAsync(v => v.Id == id && v.UserId == userId);
if (v != null) if (v != null)
{ {
db.VacationDays.Remove(v); db.VacationDays.Remove(v);
@@ -108,12 +108,12 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
} }
// ── Gleitzeitkonto ─────────────────────────────────────────────────── // ── Gleitzeitkonto ───────────────────────────────────────────────────
public async Task<TimeSpan> GetTotalOvertimeAsync(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 var allDays = await db.WorkDays
.Include(w => w.Breaks) .Include(w => w.Breaks)
.Where(w => w.StartTime != null && w.EndTime != null) .Where(w => w.UserId == userId && w.StartTime != null && w.EndTime != null)
.ToListAsync(); .ToListAsync();
var total = TimeSpan.Zero; var total = TimeSpan.Zero;
@@ -132,14 +132,14 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
} }
// ── Monatsübersicht ─────────────────────────────────────────────────── // ── Monatsübersicht ───────────────────────────────────────────────────
public async Task<List<WorkDay>> GetMonthAsync(int year, int month) public async Task<List<WorkDay>> GetMonthAsync(int userId, int year, int month)
{ {
await using var db = await factory.CreateDbContextAsync(); await using var db = await factory.CreateDbContextAsync();
var from = new DateOnly(year, month, 1); var from = new DateOnly(year, month, 1);
var to = from.AddMonths(1); var to = from.AddMonths(1);
return await db.WorkDays return await db.WorkDays
.Include(w => w.Breaks) .Include(w => w.Breaks)
.Where(w => w.Date >= from && w.Date < to) .Where(w => w.UserId == userId && w.Date >= from && w.Date < to)
.OrderBy(w => w.Date) .OrderBy(w => w.Date)
.ToListAsync(); .ToListAsync();
} }
+9
View File
@@ -0,0 +1,9 @@
namespace timetracker.Data;
public class User
{
public int Id { get; set; }
public string Username { get; set; } = "";
public string PasswordHash { get; set; } = "";
public string PasswordSalt { get; set; } = "";
}
+1
View File
@@ -3,6 +3,7 @@ namespace timetracker.Data;
public class VacationDay public class VacationDay
{ {
public int Id { get; set; } public int Id { get; set; }
public int UserId { get; set; }
public DateOnly Date { get; set; } public DateOnly Date { get; set; }
public string? Note { get; set; } public string? Note { get; set; }
} }
+1
View File
@@ -3,6 +3,7 @@ namespace timetracker.Data;
public class WorkDay public class WorkDay
{ {
public int Id { get; set; } public int Id { get; set; }
public int UserId { get; set; }
public DateOnly Date { get; set; } public DateOnly Date { get; set; }
public TimeOnly? StartTime { get; set; } public TimeOnly? StartTime { get; set; }
public TimeOnly? EndTime { get; set; } public TimeOnly? EndTime { get; set; }
+66
View File
@@ -1,3 +1,6 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MudBlazor.Services; using MudBlazor.Services;
using timetracker.Components; using timetracker.Components;
@@ -5,6 +8,19 @@ using timetracker.Data;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/auth/logout";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
});
builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<AuthService>();
// Add services to the container. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); .AddInteractiveServerComponents();
@@ -39,10 +55,60 @@ if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelop
app.UseHttpsRedirection(); app.UseHttpsRedirection();
} }
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery(); app.UseAntiforgery();
app.MapStaticAssets(); app.MapStaticAssets();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
// ── Auth-Endpoints ────────────────────────────────────────────────────────────
app.MapPost("/auth/login", async (HttpContext ctx, AuthService authService) =>
{
var form = await ctx.Request.ReadFormAsync();
var username = form["username"].ToString();
var password = form["password"].ToString();
var user = await authService.LoginAsync(username, password);
if (user == null)
return Results.Redirect("/login?error=invalid");
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true });
return Results.Redirect("/");
}).DisableAntiforgery();
app.MapPost("/auth/register", async (HttpContext ctx, AuthService authService) =>
{
var form = await ctx.Request.ReadFormAsync();
var username = form["username"].ToString();
var password = form["password"].ToString();
var (user, error) = await authService.RegisterAsync(username, password);
if (user == null)
return Results.Redirect($"/login?tab=register&error={Uri.EscapeDataString(error!)}");
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true });
return Results.Redirect("/");
}).DisableAntiforgery();
app.MapGet("/auth/logout", async (HttpContext ctx) =>
{
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Results.Redirect("/login");
});
app.Run(); app.Run();
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
{"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}}
+1 -1
View File
@@ -1 +1 @@
{"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}} {"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}}
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("timetracker")] [assembly: System.Reflection.AssemblyCompanyAttribute("timetracker")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0467f450368a3b05b63a5d2a2cc8779571042711")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+64c5f6aa2c77c868467dca487ae6b581bca6862d")]
[assembly: System.Reflection.AssemblyProductAttribute("timetracker")] [assembly: System.Reflection.AssemblyProductAttribute("timetracker")]
[assembly: System.Reflection.AssemblyTitleAttribute("timetracker")] [assembly: System.Reflection.AssemblyTitleAttribute("timetracker")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
d82bd1af2676da228608191aea963cac3166fa85ba461e9bcfa8554e5f0bdffb 452eedcfc45da63be9bf8c68bebf3387bcd4a96ecf3b483a73c4affbb5b93b4e
@@ -50,6 +50,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xIb21lLnJhem9y build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xIb21lLnJhem9y
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Login.razor]
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xMb2dpbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Month.razor] [C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Month.razor]
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xNb250aC5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xNb250aC5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@@ -66,6 +70,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xVcmxhdWJzTWF4aW1pemVyLnJhem9y build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xVcmxhdWJzTWF4aW1pemVyLnJhem9y
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/RedirectToLogin.razor]
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSZWRpcmVjdFRvTG9naW4ucmF6b3I=
build_metadata.AdditionalFiles.CssScope =
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Routes.razor] [C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Routes.razor]
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSb3V0ZXMucmF6b3I= build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSb3V0ZXMucmF6b3I=
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@@ -1 +1 @@
ec4c252fd2023b232a1f6088ccec5cbdd1025e488be8a7c43e01c6aa2c479e89 73be10256eedd125d5f8f4d2e9dd0d1388a3fccf5758d7c2e52426f3812e17fd
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.