Separate admin access from finance lock

This commit is contained in:
2026-05-21 13:55:23 +02:00
parent 9471c5c310
commit 5f3c3497b8
12 changed files with 406 additions and 4 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<title>Trafag Finanze/Sales Management Cockpit</title>
<base href="@BaseHref" />
<link href="css/app.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,800&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<HeadOutlet @rendermode="@Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer" />
</head>
@@ -3,6 +3,7 @@
@using TrafagSalesExporter.Services
@inject IAccessSessionTracker SessionTracker
@inject IAdminAccessService AdminAccess
@inject ILandingPageSettingsService LandingSettings
@inject IUiTextService UiText
<PageTitle>@T("Aktive Logins", "Active logins")</PageTitle>
@@ -16,6 +17,19 @@
else
{
<MudPaper Class="pa-4 mb-4" Elevation="1">
<MudStack Row AlignItems="AlignItems.Center" Class="mb-4">
<div>
<MudText Typo="Typo.h6">@T("Startseite", "Landing page")</MudText>
<MudText Typo="Typo.caption">
@T("Optionale Animation unter dem Willkommens-Text.", "Optional animation below the welcome text.")
</MudText>
</div>
<MudSpacer />
<MudSwitch T="bool" Value="LandingSettings.ShowWalkingLabFigure" ValueChanged="SetWalkingFigure"
Color="Color.Primary"
Label="@T("Strichmännchen anzeigen", "Show stick figure")" />
</MudStack>
<MudDivider Class="mb-4" />
<MudStack Row AlignItems="AlignItems.Center" Class="mb-3">
<div>
<MudText Typo="Typo.h6">@T("HR-/Finance-Cockpit Sessions", "HR/Finance cockpit sessions")</MudText>
@@ -77,6 +91,11 @@ else
_sessions = [];
}
private void SetWalkingFigure(bool value)
{
LandingSettings.SetShowWalkingLabFigure(value);
}
private static string FormatDate(DateTimeOffset value)
=> value.ToString("dd.MM.yyyy HH:mm:ss");
@@ -1,6 +1,7 @@
@page "/"
@using TrafagSalesExporter.Services
@inject IUiTextService UiText
@inject ILandingPageSettingsService LandingSettings
<PageTitle>@T("Trafag Cockpit", "Trafag Cockpit")</PageTitle>
@@ -30,6 +31,21 @@
<circle cx="300" cy="260" r="28" fill="#050505" />
</svg>
<div class="home-welcome">@T("Willkommen im Trafag Analyse Dashboard", "Welcome to the Trafag Analytical Dashboard")</div>
@if (LandingSettings.ShowWalkingLabFigure)
{
<div class="walking-stage" aria-label="Walking lab figure">
<div class="walking-person">
<span class="head"></span>
<span class="body"></span>
<span class="coat coat-left"></span>
<span class="coat coat-right"></span>
<span class="arm arm-left"></span>
<span class="arm arm-right"></span>
<span class="leg leg-left"></span>
<span class="leg leg-right"></span>
</div>
</div>
}
</div>
</div>
@@ -63,6 +79,114 @@
letter-spacing: 0;
}
.walking-stage {
width: min(360px, 70vw);
height: 96px;
position: relative;
overflow: hidden;
background: #fff;
border-bottom: 2px solid #050505;
}
.walking-person {
position: absolute;
left: 0;
bottom: 4px;
width: 48px;
height: 82px;
animation: lab-walk-path 7s linear infinite;
}
.walking-person span {
position: absolute;
display: block;
background: #050505;
}
.walking-person .head {
left: 15px;
top: 0;
width: 18px;
height: 18px;
border: 3px solid #050505;
border-radius: 50%;
background: #fff;
}
.walking-person .body {
left: 22px;
top: 22px;
width: 4px;
height: 34px;
}
.walking-person .coat {
top: 25px;
width: 17px;
height: 36px;
border: 3px solid #050505;
background: #fff;
}
.walking-person .coat-left {
left: 8px;
transform: skewY(10deg);
border-right: 0;
}
.walking-person .coat-right {
left: 23px;
transform: skewY(-10deg);
border-left: 0;
}
.walking-person .arm,
.walking-person .leg {
width: 4px;
border-radius: 4px;
transform-origin: 50% 0;
}
.walking-person .arm {
top: 28px;
height: 28px;
animation: limb-swing 0.72s ease-in-out infinite alternate;
}
.walking-person .arm-left {
left: 13px;
}
.walking-person .arm-right {
left: 31px;
animation-direction: alternate-reverse;
}
.walking-person .leg {
top: 56px;
height: 28px;
animation: limb-swing 0.72s ease-in-out infinite alternate-reverse;
}
.walking-person .leg-left {
left: 20px;
}
.walking-person .leg-right {
left: 27px;
animation-direction: alternate;
}
@@keyframes lab-walk-path {
0% { transform: translateX(-54px); }
100% { transform: translateX(calc(min(360px, 70vw) + 54px)); }
}
@@keyframes limb-swing {
0% { transform: rotate(-24deg); }
100% { transform: rotate(24deg); }
}
.gauge-outer,
.gauge-inner,
.gauge-tick,
@@ -50,7 +50,6 @@
"transformations" or
"finance-rules" or
"settings" or
"admin/sessions" or
"logs" or
"source-viewer";
}
+2
View File
@@ -49,6 +49,7 @@ builder.Services.Configure<HrKpiDataSourceOptions>(builder.Configuration.GetSect
builder.Services.Configure<HrKpiAccessOptions>(builder.Configuration.GetSection(HrKpiAccessOptions.SectionName));
builder.Services.Configure<FinanceCockpitAccessOptions>(builder.Configuration.GetSection(FinanceCockpitAccessOptions.SectionName));
builder.Services.Configure<AdminAccessOptions>(builder.Configuration.GetSection(AdminAccessOptions.SectionName));
builder.Services.Configure<LandingPageOptions>(builder.Configuration.GetSection(LandingPageOptions.SectionName));
builder.Services.AddDbContextFactory<AppDbContext>(options =>
options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=60"));
@@ -88,6 +89,7 @@ builder.Services.AddSingleton<IDatabaseSeedService, DatabaseSeedService>();
builder.Services.AddSingleton<IDatabaseInitializationService, DatabaseInitializationService>();
builder.Services.AddSingleton<IUiTextService, UiTextService>();
builder.Services.AddSingleton<IAccessSessionTracker, AccessSessionTracker>();
builder.Services.AddSingleton<ILandingPageSettingsService, LandingPageSettingsService>();
// Datenquellen-Adapter (Strategy per ConnectionKind).
builder.Services.AddSingleton<IDataSourceAdapter, HanaDataSourceAdapter>();
@@ -0,0 +1,8 @@
namespace TrafagSalesExporter.Security;
public sealed class LandingPageOptions
{
public const string SectionName = "LandingPage";
public bool ShowWalkingLabFigure { get; set; }
}
@@ -0,0 +1,58 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Options;
using TrafagSalesExporter.Security;
namespace TrafagSalesExporter.Services;
public interface ILandingPageSettingsService
{
bool ShowWalkingLabFigure { get; }
void SetShowWalkingLabFigure(bool value);
}
public sealed class LandingPageSettingsService : ILandingPageSettingsService
{
private static readonly object FileLock = new();
private readonly LandingPageOptions _options;
private readonly IHostEnvironment _environment;
public LandingPageSettingsService(IOptions<LandingPageOptions> options, IHostEnvironment environment)
{
_options = options.Value;
_environment = environment;
}
public bool ShowWalkingLabFigure => _options.ShowWalkingLabFigure;
public void SetShowWalkingLabFigure(bool value)
{
_options.ShowWalkingLabFigure = value;
SaveSetting(value);
}
private void SaveSetting(bool value)
{
var path = Path.Combine(_environment.ContentRootPath, "appsettings.json");
lock (FileLock)
{
var json = File.Exists(path)
? File.ReadAllText(path, Encoding.UTF8)
: "{}";
var root = JsonNode.Parse(json)?.AsObject() ?? new JsonObject();
var section = root[LandingPageOptions.SectionName] as JsonObject;
if (section is null)
{
section = new JsonObject();
root[LandingPageOptions.SectionName] = section;
}
section[nameof(LandingPageOptions.ShowWalkingLabFigure)] = value;
var options = new JsonSerializerOptions { WriteIndented = true };
File.WriteAllText(path, root.ToJsonString(options), new UTF8Encoding(false));
}
}
}
@@ -69,6 +69,9 @@ public sealed class UiTextService : IUiTextService
["Passwort konnte nicht geändert werden. Name oder aktuelles Passwort prüfen."] = "No se pudo cambiar la contraseña. Compruebe el nombre o la contraseña actual.",
["Passwort wurde geändert."] = "La contraseña se ha cambiado.",
["HR-/Finance-Cockpit Sessions"] = "Sesiones de cockpit HR/Finance",
["Startseite"] = "Página de inicio",
["Optionale Animation unter dem Willkommens-Text."] = "Animación opcional debajo del texto de bienvenida.",
["Strichmännchen anzeigen"] = "Mostrar figura de líneas",
["Gezählt werden App-interne Entsperrungen seit dem letzten App-Start."] = "Se cuentan los desbloqueos internos de la app desde el último inicio.",
["Bereich"] = "Área",
["IP-Adresse"] = "Dirección IP",
@@ -299,6 +302,9 @@ public sealed class UiTextService : IUiTextService
["Passwort konnte nicht geändert werden. Name oder aktuelles Passwort prüfen."] = "Impossibile modificare la password. Controllare nome o password attuale.",
["Passwort wurde geändert."] = "La password è stata modificata.",
["HR-/Finance-Cockpit Sessions"] = "Sessioni cockpit HR/Finance",
["Startseite"] = "Pagina iniziale",
["Optionale Animation unter dem Willkommens-Text."] = "Animazione opzionale sotto il testo di benvenuto.",
["Strichmännchen anzeigen"] = "Mostra omino",
["Gezählt werden App-interne Entsperrungen seit dem letzten App-Start."] = "Vengono contati gli sblocchi interni dell'app dall'ultimo avvio.",
["Bereich"] = "Area",
["IP-Adresse"] = "Indirizzo IP",
@@ -529,6 +535,9 @@ public sealed class UiTextService : IUiTextService
["Passwort konnte nicht geändert werden. Name oder aktuelles Passwort prüfen."] = "पासवर्ड बदला नहीं जा सका. नाम या वर्तमान पासवर्ड जांचें.",
["Passwort wurde geändert."] = "पासवर्ड बदल दिया गया.",
["HR-/Finance-Cockpit Sessions"] = "HR/Finance cockpit sessions",
["Startseite"] = "होम पेज",
["Optionale Animation unter dem Willkommens-Text."] = "स्वागत पाठ के नीचे वैकल्पिक animation.",
["Strichmännchen anzeigen"] = "स्टिक फिगर दिखाएं",
["Gezählt werden App-interne Entsperrungen seit dem letzten App-Start."] = "ऐप के पिछले प्रारंभ के बाद से आंतरिक अनलॉक गिने जाते हैं.",
["Bereich"] = "क्षेत्र",
["IP-Adresse"] = "IP पता",
+3
View File
@@ -43,5 +43,8 @@
"Enabled": true,
"Username": "admin",
"PasswordHash": "F0101E12FBCCDD6D2645B214B8732F5AEDFFB2DABBE7EE98043E68DB3BD9ADA4"
},
"LandingPage": {
"ShowWalkingLabFigure": false
}
}
@@ -0,0 +1,137 @@
# Admin Bereich und Startseite, Stand 2026-05-21
## Admin Bereich
Der Menüpunkt `Admin Bereich` ist ein eigener Hauptmenüpunkt und liegt nicht unter `Finance`.
Wichtig:
- Der Admin Bereich darf nicht durch den Finance-Cockpit-Login blockiert werden.
- Route: `/admin/sessions`
- Schutz: eigener App-interner Admin-Login über `AdminAccess`
- Initialer Benutzer: `admin`
- Initiales Passwort: `TrafagAdmin2026!`
- Das Admin-Passwort ist unabhängig vom Finance-Cockpit-Passwort.
- Das Passwort kann direkt im Admin-Loginbereich geändert werden.
Technische Dateien:
- `Components/Pages/AdminSessions.razor`
- `Components/AdminAccessPanel.razor`
- `Services/AdminAccessService.cs`
- `Security/AdminAccessOptions.cs`
- `appsettings.json`
Korrektur 2026-05-21:
- `/admin/sessions` wurde aus der globalen Finance-Sperrliste in `Components/Routes.razor` entfernt.
- Dadurch erscheint im Admin Bereich nicht mehr zuerst der Text `Finance Cockpit ist geschützt. Bitte separat anmelden.`
- Der Admin Bereich bleibt trotzdem geschützt, aber mit dem separaten Admin-Passwort.
## Aktive Logins
Der Admin Bereich zeigt aktive HR-/Finance-App-Entsperrungen seit dem letzten App-Start.
Einschränkung:
- HR und Finance verwenden aktuell gemeinsame App-Logins.
- Die Anzeige zeigt deshalb den verwendeten Login-Namen, IP-Adresse und Session-Zeitpunkte.
- Sie beweist nicht zwingend die echte Windows-Person hinter dem Zugriff.
## Startseite
Die Startseite `/` ist bewusst neutral und verlangt keinen Finance-Login.
Aktueller Aufbau:
- weißer Hintergrund
- schwarzer animierter Manometer
- Trafag-Schriftzug im Manometer
- Willkommenstext in der gewählten Sprache
- optionales animiertes Strichmännchen mit Kittel unter dem Willkommenstext
Die Corporate-Schrift wurde an die Trafag-Webseite angenähert:
- Google Font `Open Sans`
- Fallbacks: `Helvetica Neue`, `Helvetica`, `Arial`, `sans-serif`
Technische Dateien:
- `Components/App.razor`
- `wwwroot/css/app.css`
- `Components/Pages/Dashboard.razor`
- `Services/UiTextService.cs`
## Schalter für Strichmännchen
Das Strichmännchen ist standardmäßig deaktiviert.
Aktivierung:
1. `Admin Bereich` öffnen.
2. Mit Admin-Passwort anmelden.
3. Schalter `Strichmännchen anzeigen` aktivieren.
Speicherung:
- Einstellung: `LandingPage.ShowWalkingLabFigure`
- Datei: `appsettings.json`
- Service: `LandingPageSettingsService`
Technische Dateien:
- `Security/LandingPageOptions.cs`
- `Services/LandingPageSettingsService.cs`
- `Program.cs`
- `Components/Pages/AdminSessions.razor`
## Lokaler Übergangsserver
Für Tests auf dem eigenen PC wurde Port `5000` vorbereitet.
Firewall-Regel:
```text
Name: Local Dev Web Port 5000
Richtung: Eingehend
Protokoll: TCP
Lokaler Port: 5000
Profile: Domäne, Privat, Öffentlich
Aktion: Zulassen
```
Startprofil:
- `Properties/launchSettings.json`
- enthält `http://0.0.0.0:5000`
Aufruf für andere Benutzer im Netzwerk/VPN:
```text
http://172.16.9.185:5000/
```
Hinweise:
- Die IP kann sich nach Neustart oder Netzwerkwechsel ändern.
- Die Firewall-Regel bleibt nach Neustart aktiv.
- Die Anwendung muss trotzdem auf dem PC laufen.
- Lokal ist das ohne Zertifikat `http`, nicht `https`.
## Serverproblem
Die Veröffentlichung auf den Server wurde technisch ausgeführt, aber HTTPS-Aufrufe werden vor der App durch IIS/TLS blockiert.
Beobachtung:
- IIS fordert ein Client-Zertifikat an.
- Der Fehler passiert vor der Blazor-App.
- Kopieren ins Root-Verzeichnis löst das nicht.
Marco/IT muss auf dem IIS für die Site bzw. Anwendung prüfen:
- SSL Settings
- Client certificates: `Ignore` oder höchstens `Accept`
- nicht `Require`
Erst danach ist sinnvoll zu prüfen, ob die veröffentlichte App normal erreichbar ist.
+34
View File
@@ -2182,3 +2182,37 @@ Hinweis:
- Der direkte Vergleich gegen die lokale SQLite-Datei `trafag_exporter.db` war nicht aussagekraeftig, weil diese lokale DB keine passenden `CentralSalesRecords` fuer diesen Stand enthaelt.
- Die Excel-interne Summenpruefung und der Vergleich gegen `output\Sales_All_2026-05-20.xlsx` waren konsistent.
## Admin Bereich und Startseite aktualisiert 2026-05-21
Admin Bereich:
- `/admin/sessions` ist nicht mehr durch den Finance-Cockpit-Login vorgeschaltet.
- Der Admin Bereich nutzt weiterhin ein eigenes Admin-Passwort über `AdminAccess`.
- Initialer Benutzer: `admin`
- Initiales Passwort: `TrafagAdmin2026!`
- Das Admin-Passwort ist unabhängig vom Finance-Cockpit-Passwort.
- Dokumentation ergänzt: `docs/ADMIN_BEREICH_STARTSEITE_2026-05-21.md`
Startseite:
- Corporate-Schrift auf `Open Sans` mit Trafag-nahen Fallbacks angepasst.
- Manometer-Startgrafik bleibt auf weißem Hintergrund, schwarz gezeichnet und mit Trafag-Schriftzug.
- Willkommenstext ist sprachabhängig.
- Optionales Strichmännchen mit Kittel unter dem Willkommenstext ergänzt.
- Das Strichmännchen ist standardmäßig deaktiviert.
- Aktivierung über `Admin Bereich` -> `Strichmännchen anzeigen`.
- Einstellung wird in `appsettings.json` unter `LandingPage.ShowWalkingLabFigure` gespeichert.
Technische Dateien:
- `Components/Routes.razor`
- `Components/App.razor`
- `wwwroot/css/app.css`
- `Components/Pages/Dashboard.razor`
- `Components/Pages/AdminSessions.razor`
- `Program.cs`
- `Security/LandingPageOptions.cs`
- `Services/LandingPageSettingsService.cs`
- `Services/UiTextService.cs`
- `appsettings.json`
+11 -2
View File
@@ -1,5 +1,14 @@
html, body {
font-family: 'Roboto', sans-serif;
html,
body,
.mud-typography,
.mud-button-root,
.mud-input,
.mud-table,
.mud-nav-link,
.mud-chip,
.mud-list-item,
.mud-menu-item {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
}
.app-title {