Update x gestione pagina config Sync:

- nav laterale controllata da permessi utente
- fix conf appsetting.json (dev e prod)
- fix servizi redis in startup
This commit is contained in:
Samuele Locatelli
2024-09-03 17:21:09 +02:00
parent d621f7e6fc
commit a6411a4fc5
10 changed files with 393 additions and 60 deletions
+172 -6
View File
@@ -12,6 +12,8 @@ using System.Text;
using Newtonsoft.Json;
using System.Diagnostics.Eventing.Reader;
using MP.AppAuth.Models;
using StackExchange.Redis;
using MP.AppAuth.Controllers;
namespace MP.Land.Data
{
@@ -19,17 +21,36 @@ namespace MP.Land.Data
{
#region Public Fields
// diritti (cablòato)
public const string RoleSuperAdmin = "MoonPro_SuperAdmin";
public static AppAuth.Controllers.AppAuthController dbController;
public static AppAuth.Controllers.MPController MpDbController;
public static AppAuth.Controllers.MPUserController userController;
#endregion Public Fields
#region Public Constructors
public AppAuthService(IConfiguration configuration, ILogger<AppAuthService> logger, IMemoryCache memoryCache, IDistributedCache distributedCache)
public AppAuthService(IConfiguration configuration, ILogger<AppAuthService> logger, IConnectionMultiplexer redisConnMult, IMemoryCache memoryCache, IDistributedCache distributedCache)
{
_logger = logger;
_configuration = configuration;
// cod app
CodApp = _configuration.GetValue<string>("ServerConf:CodApp");
Modulo = _configuration.GetValue<string>("ServerConf:Modulo");
// Conf cache
redisConn = redisConnMult;
redisDb = this.redisConn.GetDatabase();
// json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/
JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
// conf cache
this.memoryCache = memoryCache;
this.distributedCache = distributedCache;
@@ -41,8 +62,9 @@ namespace MP.Land.Data
}
else
{
dbController = new AppAuth.Controllers.AppAuthController(configuration);
MpDbController = new AppAuth.Controllers.MPController(configuration);
dbController = new AppAuthController(configuration);
MpDbController = new MPController(configuration);
userController = new MPUserController(configuration);
_logger.LogInformation("DbController OK");
}
}
@@ -200,6 +222,48 @@ namespace MP.Land.Data
return await Task.FromResult(answ);
}
/// <summary>
/// Elenco diritti dato utente
/// </summary>
/// <param name="UserName"></param>
/// <returns></returns>
public async Task<List<UserDirittiModel>> DirittiGetByUser(string UserName)
{
string source = "DB";
List<UserDirittiModel>? dbResult = new List<UserDirittiModel>();
string currKey = $"{rKeyDirittiUser}:{UserName}";
Stopwatch sw = new Stopwatch();
sw.Start();
string? rawData = await redisDb.StringGetAsync(currKey);
if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2)
{
source = "REDIS";
var tempResult = JsonConvert.DeserializeObject<List<UserDirittiModel>>(rawData);
if (tempResult == null)
{
dbResult = new List<UserDirittiModel>();
}
else
{
dbResult = tempResult;
}
}
else
{
// recupero diritti utente
dbResult = userController.DirittiUtente(UserName, Modulo);
rawData = JsonConvert.SerializeObject(dbResult, JSSettings);
await redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
}
if (dbResult == null)
{
dbResult = new List<UserDirittiModel>();
}
sw.Stop();
Log.Debug($"DirittiGetByUser | {source} | {sw.ElapsedMilliseconds} ms");
return dbResult;
}
public void Dispose()
{
// Clear database controller
@@ -207,6 +271,56 @@ namespace MP.Land.Data
MpDbController.Dispose();
}
/// <summary>
/// Elenco permessi dato utente
/// </summary>
/// <param name="UserName"></param>
/// <returns></returns>
public async Task<List<PermessiModel>> PermessiGetByUser(string UserName)
{
string source = "DB";
List<PermessiModel>? dbResult = new List<PermessiModel>();
string currKey = $"{rKeyPermUser}:{UserName}";
Stopwatch sw = new Stopwatch();
sw.Start();
string? rawData = await redisDb.StringGetAsync(currKey);
if (!string.IsNullOrEmpty(rawData))
{
source = "REDIS";
var tempResult = JsonConvert.DeserializeObject<List<PermessiModel>>(rawData);
if (tempResult == null)
{
dbResult = new List<PermessiModel>();
}
else
{
dbResult = tempResult;
}
}
else
{
// recupero diritti utente
var userRightList = userController.DirittiUtente(UserName, Modulo);
// proietto come funzioni...
var ListFunc = userRightList.Select(x => x.Funzione).ToList();
// trasformo i permessi utente
if (ListFunc == null)
{
ListFunc = new List<string>();
}
dbResult = dbController.PermessiGetByFunc(ListFunc);
rawData = JsonConvert.SerializeObject(dbResult, JSSettings);
await redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
}
if (dbResult == null)
{
dbResult = new List<PermessiModel>();
}
sw.Stop();
Log.Debug($"PermessiGetByUser | {source} | {sw.ElapsedMilliseconds} ms");
return dbResult;
}
public async Task ResetCache()
{
string cacheKey = ":MP:VOCAB";
@@ -324,18 +438,32 @@ namespace MP.Land.Data
#region Private Fields
// gestione key Redis
private const string redisBaseAddr = "MP:LAND";
private const string rKeyDirittiUser = $"{redisBaseAddr}:DIR_USER";
private const string rKeyPermUser = $"{redisBaseAddr}:PERM_USER";
private static IConfiguration _configuration;
private static ILogger<AppAuthService> _logger;
private static List<Macchine> ElencoMacchine = new List<Macchine>();
private static JsonSerializerSettings? JSSettings;
private static Logger Log = LogManager.GetCurrentClassLogger();
private static string Modulo = "";
private readonly IDistributedCache distributedCache;
private readonly IMemoryCache memoryCache;
/// <summary>
/// Durata cache lunga IN SECONDI
/// </summary>
private int cacheTtlLong = 60 * 5;
/// <summary>
/// Durata cache breve IN SECONDI
/// </summary>
private int cacheTtlShort = 60 * 1;
/// <summary>
/// Durata assoluta massima della cache
/// </summary>
@@ -347,6 +475,18 @@ namespace MP.Land.Data
/// </summary>
private int chSliExp = 5;
/// <summary>
/// Oggetto per connessione a REDIS
/// </summary>
private IConnectionMultiplexer redisConn;
//ISubscriber sub = redis.GetSubscriber();
/// <summary>
/// Oggetto DB redis da impiegare x chiamate R/W
/// </summary>
private IDatabase redisDb = null!;
private Random rnd = new Random();
private Dictionary<string, string> Vocabolario = new Dictionary<string, string>();
#endregion Private Fields
@@ -369,6 +509,32 @@ namespace MP.Land.Data
}
}
private string CodApp { get; set; } = "";
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
private TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Private Properties
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>MP.Land</RootNamespace>
<Version>6.16.2407.3117</Version>
<Version>6.16.2409.0317</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo Tablet MAPO - DotNet6</i>
<h4>Versione: 6.16.2407.3117</h4>
<h4>Versione: 6.16.2409.0317</h4>
<br />
Note di rilascio:
<ul>
+1 -1
View File
@@ -1 +1 @@
6.16.2407.3117
6.16.2409.0317
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>6.16.2407.3117</version>
<version>6.16.2409.0317</version>
<url>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+50 -50
View File
@@ -9,56 +9,56 @@
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="" Match="NavLinkMatch.All">
<span class="fas fa-home fa-2x pe-2" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="UserQr">
<span class="fas fa-qrcode fa-2x pe-2" aria-hidden="true"></span> QR Card User
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="UpdateManager">
<span class="fas fa-download fa-2x pe-2" aria-hidden="true"></span> Update Manager
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="ConfSync">
<span class="fas fa-file-import fa-2x pe-2" aria-hidden="true"></span> Config Sync
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="About">
<span class="fas fa-info-circle fa-2x pe-2" aria-hidden="true"></span> About
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="Contacts">
<span class="fas fa-envelope fa-2x pe-2" aria-hidden="true"></span> Contacts
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="SysInfo">
<span class="fas fa-wrench fa-2x pe-2" aria-hidden="true"></span> System Info
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="RefreshData">
<span class="fas fa-sync-alt fa-2x pe-2" aria-hidden="true"></span> Refresh Data
</NavLink>
</div>
@if (isLoading)
{
<LoadingData DisplayMode="LoadingData.SpinMode.Growl" DisplaySize="LoadingData.CtrlSize.Small"></LoadingData>
}
else
{
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="" Match="NavLinkMatch.All">
<span class="fas fa-home fa-2x pe-2" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="UserQr">
<span class="fas fa-qrcode fa-2x pe-2" aria-hidden="true"></span> QR Card User
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="UpdateManager">
<span class="fas fa-download fa-2x pe-2" aria-hidden="true"></span> Update Manager
</NavLink>
</div>
@if (IsSuperAdmin)
{
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="ConfSync">
<span class="fas fa-file-import fa-2x pe-2" aria-hidden="true"></span> Config Sync
</NavLink>
</div>
}
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="About">
<span class="fas fa-info-circle fa-2x pe-2" aria-hidden="true"></span> About
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="Contacts">
<span class="fas fa-envelope fa-2x pe-2" aria-hidden="true"></span> Contacts
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="SysInfo">
<span class="fas fa-wrench fa-2x pe-2" aria-hidden="true"></span> System Info
</NavLink>
</div>
<div class="nav-item px-2">
<NavLink class="nav-link py-0 px-2 mb-0" href="RefreshData">
<span class="fas fa-sync-alt fa-2x pe-2" aria-hidden="true"></span> Refresh Data
</NavLink>
</div>
}
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
+150
View File
@@ -0,0 +1,150 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using MP.AppAuth.Models;
using MP.Land.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Shared
{
public partial class NavMenu
{
#region Protected Properties
[Inject]
protected AuthenticationStateProvider AuthStateProvider { get; set; } = null!;
[Inject]
protected AppAuthService DataService { get; set; }
protected bool IsSuperAdmin
{
get => HasRight(AppAuthService.RoleSuperAdmin);
}
[Inject]
protected NavigationManager NavManager { get; set; } = null!;
protected string pageName
{
get
{
string pName = NavManager.ToBaseRelativePath(NavManager.Uri).ToLower();
if (pName.Contains("?"))
{
pName = pName.Substring(0, pName.IndexOf("?"));
}
return pName;
}
}
protected override async Task OnInitializedAsync()
{
await ReloadData();
}
protected List<PermessiModel> UserPerm { get; set; } = new List<PermessiModel>();
protected List<UserDirittiModel> UserRight { get; set; } = new List<UserDirittiModel>();
#endregion Protected Properties
#region Protected Methods
protected bool HasRight(string codFunz)
{
bool answ = false;
if (UserRight != null && UserRight.Count > 0)
{
answ = UserRight
.Where(x => x.Funzione.Equals(codFunz, System.StringComparison.InvariantCultureIgnoreCase))
.Count() > 0;
}
return answ;
}
#endregion Protected Methods
#region Private Fields
private bool collapseNavMenu = true;
private bool isLoading { get; set; } = false;
private string SafePages = "";
private string userName = "";
#endregion Private Fields
#region Private Properties
[Inject]
private IConfiguration ConfMan { get; set; } = null!;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
#endregion Private Properties
#region Private Methods
private async Task checkAuth()
{
// verifico pagina tra i permessi, se manca --> rimando a pagina unauth... se contiene
// index --> salto
if (!pageName.Contains("index"))
{
bool isAuth = false;
if (UserPerm != null)
{
isAuth = UserPerm.Where(x => x.Url.ToLower() == pageName).Count() > 0;
bool pageIsSafe = SafePages.ToLower().Contains($"|{pageName.ToLower()}|");
if (!pageIsSafe && !isAuth)
{
NavManager.NavigateTo("Unauthorized", true);
}
}
}
}
private async Task ReloadData()
{
isLoading = true;
await Task.Delay(1);
// sistemo elenco pagine safe...
SafePages = ConfMan.GetValue<string>("Application:SafePages").ToLower();
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity != null && user.Identity.IsAuthenticated)
{
userName = $"{user.Identity.Name}";
}
else
{
userName = "N.A.";
}
// carico diritti...
var domUser = userName.Split("\\");
if (domUser.Length > 0)
{
string dominio = domUser[0];
string uName = domUser[1];
UserRight = await DataService.DirittiGetByUser(uName);
UserPerm = await DataService.PermessiGetByUser(uName);
}
await checkAuth();
await Task.Delay(1);
isLoading = false;
await Task.Delay(1);
//await InvokeAsync(StateHasChanged);
}
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
#endregion Private Methods
}
}
+10
View File
@@ -11,8 +11,10 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MP.Land.Data;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
@@ -124,6 +126,13 @@ namespace MP.Land
options.InstanceName = "MP:Land";
});
// REDIS setup
string connStringRedis = Configuration.GetConnectionString("Redis");
string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":"));
// avvio oggetto shared x redis...
var redisMultiplexer = ConnectionMultiplexer.Connect(connStringRedis);
services.AddLocalization();
services.AddRazorPages();
@@ -135,6 +144,7 @@ namespace MP.Land
services.AddScoped<AppAuthService>();
services.AddScoped<MessageService>();
services.AddSingleton<IConnectionMultiplexer>(redisMultiplexer);
services.AddBlazoredLocalStorage();
services.AddBlazoredSessionStorage();
+1
View File
@@ -14,6 +14,7 @@
"ConnectionStrings": {
"DefaultConnection": "Server=SQL2016DEV;Database=MoonPro;Trusted_Connection=True;MultipleActiveResultSets=true",
"MP.Land": "Server=SQL2016DEV;Database=MoonPro;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
"MP.Land.Auth": "Server=SQL2016DEV;Database=MoonPro_Anagrafica;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
"Redis": "localhost:26379,serviceName=devel,defaultDatabase=5,keepAlive=180,asyncTimeout=5000"
},
"ServerConf": {
+6
View File
@@ -13,10 +13,16 @@
"ConnectionStrings": {
"DefaultConnection": "Server=SQL2016DEV;Database=MoonPro;Trusted_Connection=True;MultipleActiveResultSets=true",
"MP.Land": "Server=SQL2016DEV;Database=MoonPro;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
"MP.Land.Auth": "Server=SQL2016DEV;Database=MoonPro_Anagrafica;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
//"MP.Land": "Server=SQL2016DEV;Database=MoonPro_ONE;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
"Redis": "localhost:26379, serviceName=devel, defaultDatabase=1, keepAlive=180, connectTimeout=5000, syncTimeout=5000, asyncTimeout=5000, abortConnect=false, ssl=false, allowAdmin=true"
},
"Application": {
"SafePages": "||LAND|Home|Index|About|Help|Unauthorized|"
},
"ServerConf": {
"CodApp": "MP-LAND",
"Modulo": "MoonPro",
"BaseUrl": "https://localhost:44309/",
"downloadPath": "C:\\Steamware\\installers\\MP"
}