23 Commits

Author SHA1 Message Date
MarcWieland 94c10aebdd Onboarding 2026-06-09 00:22:30 +02:00
MarcWieland ddb2b4af9f Neue agb seite implementiert 2026-06-09 00:15:06 +02:00
MarcWieland 1def618e34 Neue navbar 2026-06-09 00:06:18 +02:00
MarcWieland 2029524379 Design updates 2026-06-08 23:52:22 +02:00
MarcWieland f92dd2659c Increased version 2026-06-08 23:36:04 +02:00
MarcWieland 82626bc5b3 Timebot implementation 2026-06-08 23:34:08 +02:00
mrcwlnd b8b01871ed Merge pull request 'Dynamischer page title' (#4) from wasm-integration into main
Reviewed-on: #4
2026-06-08 14:43:02 +00:00
MarcWieland d79ee1671d Dynamischer page title 2026-06-08 16:42:46 +02:00
mrcwlnd 69a38761f3 Merge pull request 'peformance verbesserungen' (#3) from wasm-integration into main
Reviewed-on: #3
2026-06-08 14:39:44 +00:00
MarcWieland 708aa3991a peformance verbesserungen 2026-06-08 16:39:28 +02:00
mrcwlnd 3f262910ad Merge pull request 'fixed dockerfile' (#2) from wasm-integration into main
Reviewed-on: #2
2026-06-08 14:32:13 +00:00
MarcWieland b3e578308f fixed dockerfile 2026-06-08 16:29:40 +02:00
mrcwlnd ed36d0e1ec Merge pull request 'WASM Mode activated' (#1) from wasm-integration into main
Reviewed-on: #1
2026-06-08 14:25:30 +00:00
MarcWieland 58e562adb1 WASM Mode activated 2026-06-08 16:24:51 +02:00
Wieland, Marc fe294e288a wasm migration plan added 2026-06-08 15:50:42 +02:00
Wieland, Marc dd2d47e57d Userverwaltung improved 2026-06-08 15:45:27 +02:00
Wieland, Marc ee4d6cb5b1 Userverwaltung improved 2026-06-08 15:24:20 +02:00
Wieland, Marc 9030b332a1 Removed obj and bin 2026-06-08 15:10:00 +02:00
Wieland, Marc 2987f530fd Added gitignore 2026-06-08 15:09:25 +02:00
Wieland, Marc 0e03e41ee2 added gitignore 2026-06-08 15:08:55 +02:00
Wieland, Marc a41abf4f11 neuste version 2026-06-08 15:07:07 +02:00
mrcwlnd ab505dbad5 Neuste docker version 2026-06-07 23:46:37 +02:00
mrcwlnd f636392e52 Neuste Version 2026-06-07 23:36:45 +02:00
399 changed files with 4242 additions and 7111 deletions
Vendored
BIN
View File
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
# SQLite Datenbanken und Journal-Dateien
*.db
*.db-shm
*.db-wal
# Build-Artefakte
bin/
obj/
*.user
*.suo
*.vs/
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

-26
View File
@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="_content/MudBlazor/MudBlazor.min.css" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["timetracker.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="alternate icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>
-51
View File
@@ -1,51 +0,0 @@
@inherits LayoutComponentBase
<MudThemeProvider Theme="_theme" />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar Elevation="2" Style="background: linear-gradient(90deg, #3F51B5 0%, #1A237E 100%);">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="ml-2">
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Style="color:white; font-size:1.6rem" />
<MudText Typo="Typo.h6" Style="color:white; font-weight:700; letter-spacing:0.5px">Timetracker</MudText>
</MudStack>
<MudSpacer />
</MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<NavMenu />
</MudDrawer>
<MudMainContent>
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-4 pb-8">
@Body
</MudContainer>
</MudMainContent>
</MudLayout>
@code {
private bool _drawerOpen = true;
private readonly MudTheme _theme = new()
{
PaletteLight = new PaletteLight
{
Primary = "#3F51B5",
PrimaryDarken = "#1A237E",
PrimaryLighten = "#7986CB",
Secondary = "#009688",
SecondaryDarken = "#00695C",
AppbarBackground = "#3F51B5",
Background = "#F4F6F9",
DrawerBackground = "#FFFFFF",
Surface = "#FFFFFF",
TextPrimary = "#212121",
TextSecondary = "#757575",
}
};
private void ToggleDrawer() => _drawerOpen = !_drawerOpen;
}
-98
View File
@@ -1,98 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
-20
View File
@@ -1,20 +0,0 @@
<MudNavMenu>
<MudText Typo="Typo.h6" Class="px-4 mt-4 mb-2">Navigation</MudText>
<MudDivider Class="mb-2" />
<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="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</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>
-105
View File
@@ -1,105 +0,0 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
min-height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
-31
View File
@@ -1,31 +0,0 @@
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
<dialog id="components-reconnect-modal" data-nosnippet>
<div class="components-reconnect-container">
<div class="components-rejoining-animation" aria-hidden="true">
<div></div>
<div></div>
</div>
<p class="components-reconnect-first-attempt-visible">
Rejoining the server...
</p>
<p class="components-reconnect-repeated-attempt-visible">
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
</p>
<p class="components-reconnect-failed-visible">
Failed to rejoin.<br />Please retry or reload the page.
</p>
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
Retry
</button>
<p class="components-pause-visible">
The session has been paused by the server.
</p>
<p class="components-resume-failed-visible">
Failed to resume the session.<br />Please retry or reload the page.
</p>
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
Resume
</button>
</div>
</dialog>
-157
View File
@@ -1,157 +0,0 @@
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
display: none;
}
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
display: block;
}
#components-reconnect-modal {
background-color: white;
width: 20rem;
margin: 20vh auto;
padding: 2rem;
border: 0;
border-radius: 0.5rem;
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
opacity: 0;
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
&[open]
{
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
animation-fill-mode: both;
}
}
#components-reconnect-modal::backdrop {
background-color: rgba(0, 0, 0, 0.4);
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
opacity: 1;
}
@keyframes components-reconnect-modal-slideUp {
0% {
transform: translateY(30px) scale(0.95);
}
100% {
transform: translateY(0);
}
}
@keyframes components-reconnect-modal-fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes components-reconnect-modal-fadeOutOpacity {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.components-reconnect-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
#components-reconnect-modal p {
margin: 0;
text-align: center;
}
#components-reconnect-modal button {
border: 0;
background-color: #6b9ed2;
color: white;
padding: 4px 24px;
border-radius: 4px;
}
#components-reconnect-modal button:hover {
background-color: #3b6ea2;
}
#components-reconnect-modal button:active {
background-color: #6b9ed2;
}
.components-rejoining-animation {
position: relative;
width: 80px;
height: 80px;
}
.components-rejoining-animation div {
position: absolute;
border: 3px solid #0087ff;
opacity: 1;
border-radius: 50%;
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.components-rejoining-animation div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes components-rejoining-animation {
0% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
4.9% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
5% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 80px;
height: 80px;
opacity: 0;
}
}
-63
View File
@@ -1,63 +0,0 @@
// Set up event handlers
const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
const retryButton = document.getElementById("components-reconnect-button");
retryButton.addEventListener("click", retry);
const resumeButton = document.getElementById("components-resume-button");
resumeButton.addEventListener("click", resume);
function handleReconnectStateChanged(event) {
if (event.detail.state === "show") {
reconnectModal.showModal();
} else if (event.detail.state === "hide") {
reconnectModal.close();
} else if (event.detail.state === "failed") {
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
} else if (event.detail.state === "rejected") {
location.reload();
}
}
async function retry() {
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
try {
// Reconnect will asynchronously return:
// - true to mean success
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
// - exception to mean we didn't reach the server (this can be sync or async)
const successful = await Blazor.reconnect();
if (!successful) {
// We have been able to reach the server, but the circuit is no longer available.
// We'll reload the page so the user can continue using the app as quickly as possible.
const resumeSuccessful = await Blazor.resumeCircuit();
if (!resumeSuccessful) {
location.reload();
} else {
reconnectModal.close();
}
}
} catch (err) {
// We got an exception, server is currently unavailable
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
}
}
async function resume() {
try {
const successful = await Blazor.resumeCircuit();
if (!successful) {
location.reload();
}
} catch {
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
}
}
async function retryWhenDocumentBecomesVisible() {
if (document.visibilityState === "visible") {
await retry();
}
}
-130
View File
@@ -1,130 +0,0 @@
@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;
}
}
-114
View File
@@ -1,114 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using timetracker.Components;
using timetracker.Data;
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.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddMudServices();
builder.Services.AddHttpClient<HolidayService>();
var dbPath = Environment.GetEnvironmentVariable("TIMETRACKER_DB_PATH")
?? Path.Combine(builder.Environment.ContentRootPath, "timetracker.db");
builder.Services.AddDbContextFactory<TimetrackerDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}"));
builder.Services.AddScoped<TimetrackerService>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var factory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<TimetrackerDbContext>>();
await using var db = await factory.CreateDbContextAsync();
await db.Database.MigrateAsync();
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelopment()))
{
app.UseHttpsRedirection();
}
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.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();
@@ -1,68 +0,0 @@
<?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.
@@ -1,171 +0,0 @@
{
"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"
}
}
}
@@ -1,14 +0,0 @@
{
"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.
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

Some files were not shown because too many files have changed in this diff Show More