Separate admin access from finance lock
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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 पता",
|
||||
|
||||
@@ -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.
|
||||
@@ -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`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user