Merge remote-tracking branch 'origin/develop'
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33205.214
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.4.11620.152 stable
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.Data", "MP.Data\MP.Data.csproj", "{A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}"
|
||||
EndProject
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using EgwCoreLib.Razor;
|
||||
using global::Microsoft.AspNetCore.Components;
|
||||
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
|
||||
using Microsoft.JSInterop;
|
||||
using MP.Data.DbModels;
|
||||
using MP.Data.Services;
|
||||
using NLog;
|
||||
using System;
|
||||
|
||||
namespace MP_TAB3.Components
|
||||
{
|
||||
@@ -480,7 +478,7 @@ namespace MP_TAB3.Components
|
||||
bool doForce = false;
|
||||
if (SDService.MachNumPzGet(IdxMaccCur) != CRecMSE.PezziProd || SDService.MachProdStGet(IdxMaccCur).IdxOdl != CRecMSE.IdxOdl || (datiProdAct != null && datiProdAct.PzTotODL != CRecMSE.PezziProd))
|
||||
{
|
||||
datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccCur, DateTime.Now);
|
||||
datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccCur, DateTime.Now);
|
||||
SDService.MachProdStSet(IdxMaccCur, datiProdAct);
|
||||
SDService.MachNumPzSet(IdxMaccCur, CRecMSE.PezziProd);
|
||||
doForce = true;
|
||||
|
||||
@@ -819,7 +819,7 @@ namespace MP_TAB3.Components
|
||||
}
|
||||
}
|
||||
// verifica conferma produzione
|
||||
datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccSel, DateTime.Now);
|
||||
datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccSel, DateTime.Now);
|
||||
await checkConfProd();
|
||||
}
|
||||
|
||||
@@ -1303,7 +1303,7 @@ namespace MP_TAB3.Components
|
||||
// var pUpd = Task.Run(async () =>
|
||||
// {
|
||||
// // controllo ANCHE che la tav correntemente selezionata NON abbia pezzi da confermare
|
||||
// datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccMain, adesso);
|
||||
// datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccMain, adesso);
|
||||
// answ = datiProdAct.PzConfBuoni == 0 && datiProdAct.Pz2RecScarto == 0;
|
||||
// });
|
||||
// pUpd.Wait();
|
||||
@@ -1554,7 +1554,7 @@ namespace MP_TAB3.Components
|
||||
if (isMulti && !needConfProd)
|
||||
{
|
||||
// verifica conferma produzione
|
||||
var datiProdAltra = await TabDServ.StatoProdMacchina(idxMaccAltraTav, DateTime.Now);
|
||||
var datiProdAltra = await TabDServ.StatoProdMacchinaAsync(idxMaccAltraTav, DateTime.Now);
|
||||
needConfProd = (datiProdAltra.Pz2RecTot > 0 || datiProdAltra.Pz2RecScarto > 0 || datiProdAltra.Pz2RecRilav > 0) && !isSlave;
|
||||
}
|
||||
if (needConfProd)
|
||||
@@ -1591,7 +1591,7 @@ namespace MP_TAB3.Components
|
||||
else // se NON sono in setup verifico se ho pz da confermare
|
||||
{
|
||||
// recupero pz da confermare
|
||||
datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccSel, adesso);
|
||||
datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccSel, adesso);
|
||||
|
||||
await checkConfProd();
|
||||
|
||||
@@ -1802,7 +1802,7 @@ namespace MP_TAB3.Components
|
||||
if (!isMulti || (isMulti && IdxMaccSel.IndexOf("#") > 0))
|
||||
{
|
||||
var rawData = await TabDServ.PezziProdMacchina(IdxMaccSel);
|
||||
datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccSel, DateTime.Now);
|
||||
datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccSel, DateTime.Now);
|
||||
await checkConfProd();
|
||||
Log.Trace("OdlMan.ReloadData | check checkConfProd done");
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace MP_TAB3.Components
|
||||
{
|
||||
isProcessing = true;
|
||||
await Task.Delay(1);
|
||||
datiProdAct = await TabDServ.StatoProdMacchina(IdxMaccSel, dtReqUpdate);
|
||||
datiProdAct = await TabDServ.StatoProdMacchinaAsync(IdxMaccSel, dtReqUpdate);
|
||||
// aggiorno visualizzazione...
|
||||
numPzProdotti = datiProdAct.PzTotODL;
|
||||
numPz2Rec = datiProdAct.Pz2RecTot;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>6.16.2603.411</Version>
|
||||
<Version>6.16.2604.1109</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>MP_TAB3</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<script>
|
||||
Blazor.start({
|
||||
reconnectionOptions: {
|
||||
maxRetries: 600,
|
||||
maxRetries: 3600,
|
||||
retryIntervalMilliseconds: 1000
|
||||
},
|
||||
}).then(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>Modulo MAPOSPEC </i>
|
||||
<h4>Versione: 6.16.2603.411</h4>
|
||||
<h4>Versione: 6.16.2604.1109</h4>
|
||||
<br /> Note di rilascio:
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.16.2603.411
|
||||
6.16.2604.1109
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>6.16.2603.411</version>
|
||||
<version>6.16.2604.1109</version>
|
||||
<url>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/MP-TAB3.zip</url>
|
||||
<changelog>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/ChangeLog.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
@@ -431,8 +431,14 @@ namespace MP.AppAuth.Services
|
||||
public void Dispose()
|
||||
{
|
||||
// Clear database controller
|
||||
dbController.Dispose();
|
||||
MpDbController.Dispose();
|
||||
if (dbController != null)
|
||||
{
|
||||
dbController.Dispose();
|
||||
}
|
||||
if (MpDbController != null)
|
||||
{
|
||||
MpDbController.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task FlushRedisCache()
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using static MP.Core.Objects.Enums;
|
||||
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Struttura conf tipo dati
|
||||
/// </summary>
|
||||
public class DataConfDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Struttura Valore composto a partire da altre variabili read/write esistenti
|
||||
/// </summary>
|
||||
public string ComposedVal { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Decodifica per tipoMem = BitMap:
|
||||
/// Array di valori assegnati ai singoli bit
|
||||
/// </summary>
|
||||
public List<string> decodeMap { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// DESCRIZIONE parametro
|
||||
/// </summary>
|
||||
public string description { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Valore per determinare Display Ordinal
|
||||
/// </summary>
|
||||
public int displOrdinal { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Fattore di MOLTIPLICA per eventuale scalatura
|
||||
/// es: leggo 1'234 con factor=0.01 --> 12.34, con factor=100 --> 123'400
|
||||
/// </summary>
|
||||
public double factor { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Indice nell'area di memoria (da valore iniziale = 0)
|
||||
/// </summary>
|
||||
public int index { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Valore massimo ammesso (ove usato, es simulazione)
|
||||
/// </summary>
|
||||
public double maxVal { get; set; } = 9999;
|
||||
|
||||
/// <summary>
|
||||
/// Nome "assoluto" della posizione nell'area di memoria (anche diverso da indice)
|
||||
/// </summary>
|
||||
public string memAddr { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Valore minimo ammesso (ove usato, es simulazione)
|
||||
/// </summary>
|
||||
public double minVal { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// NOME parametro
|
||||
/// </summary>
|
||||
public string name { get; set; } = "none";
|
||||
|
||||
/// <summary>
|
||||
/// Size in byte
|
||||
/// </summary>
|
||||
public int size { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di dato
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public plcDataType tipoMem { get; set; } = plcDataType.Int;
|
||||
|
||||
/// <summary>
|
||||
/// Unità di misura del parametro
|
||||
/// </summary>
|
||||
public string unit { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Valore parametro (come stringa, decimali con ",", default VUOTO), poi LETTO da PLC (o appena scritto)
|
||||
/// </summary>
|
||||
public string value { get; set; } = "";
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using static MP.Core.Objects.Enums;
|
||||
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Struttura conf tipo dati con estensione TimeSeries + VarCalc
|
||||
/// </summary>
|
||||
public class DataConfTsVcDto : DataConfDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di funzione da applicare al dato
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public VC_func func { get; set; } = VC_func.MAX;
|
||||
|
||||
/// <summary>
|
||||
/// Indica se il parametro sia semidefinito-positivo (=possa solo crescere) + relativa gestione
|
||||
/// es: i contatori energia, contatori TOT generali non resettabili ore impiego / pezzi
|
||||
/// </summary>
|
||||
public bool onlyIncr { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Periodo campionamento
|
||||
/// </summary>
|
||||
public int period { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Indica se invio parametro abilitato (false = filtrato, no FluxLog send)
|
||||
/// </summary>
|
||||
public bool sendEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Valore della DeadBand per rilevare variazioni in valore assoluto, se nullo non impiegato
|
||||
/// </summary>
|
||||
public double? thresholdAbs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Valore della DeadBand per rilevare variazioni in valore percentuale (5% = 0.05), se nullo non impiegato
|
||||
/// </summary>
|
||||
public double? thresholdPerc { get; set; }
|
||||
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracciato InputEvents in formato JSON valido
|
||||
/// Derivato da input realtime valore=3&dtEve=20181206180600000&dtCurr=20181206180600000&cnt=999
|
||||
/// </summary>
|
||||
public class EvDataDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Contatore incrementale x riordino invio (opzionale)
|
||||
/// </summary>
|
||||
public int cnt { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// DataOra corrente della trasmissione
|
||||
/// </summary>
|
||||
public DateTime dtCurr { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// DataOra evento
|
||||
/// </summary>
|
||||
public DateTime dtEve { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// Valore del dato di flusso registrato
|
||||
/// </summary>
|
||||
public string valore { get; set; } = "-";
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracciato FluxLog in formato JSON valido
|
||||
/// </summary>
|
||||
public class FLogDataDto : EvDataDto
|
||||
{
|
||||
/// <summary>
|
||||
/// nome del flusso
|
||||
/// </summary>
|
||||
public string flux { get; set; } = "ND";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Array valori tipo flogData inviati come JSon
|
||||
/// </summary>
|
||||
public class FLogJsonPayloadDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public List<FLogDataDto> fluxData { get; set; } = new();
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Struttura conf memorie PLC (inizialmente SIEMENS)
|
||||
/// </summary>
|
||||
public class PlcMemMapDto
|
||||
{
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// Parametri ammessi per IOB x lettura
|
||||
/// </summary>
|
||||
public Dictionary<string, DataConfTsVcDto> mMapRead = new Dictionary<string, DataConfTsVcDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Parametri ammessi per IOB x scrittura
|
||||
/// </summary>
|
||||
public Dictionary<string, DataConfDto> mMapWrite = new Dictionary<string, DataConfDto>();
|
||||
|
||||
#endregion Public Fields
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
public class StatDataDTO
|
||||
{
|
||||
public string Label { get; set; } = "";
|
||||
public double Value { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracciato UserLog in formato JSON valido
|
||||
/// </summary>
|
||||
public class ULogDataDto : EvDataDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Nome del flusso (RC/RS/DI)
|
||||
/// </summary>
|
||||
public string flux { get; set; } = "DI";
|
||||
|
||||
/// <summary>
|
||||
/// [OPZIONALE] label = causale scarto / tagCode
|
||||
/// </summary>
|
||||
public string label { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Operatore di riferimento
|
||||
/// </summary>
|
||||
public int matrOpr { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// [OPZIONALE] valNum = esitoOk (0/1) / Quantità di scarto associata
|
||||
/// </summary>
|
||||
public int valNum { get; set; } = 0;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// Array valori tipo UserLogData inviati come JSon
|
||||
/// </summary>
|
||||
public class ULogJsonPayloadDto
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public List<ULogDataDto> fluxData { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
public class WeightDTO
|
||||
{
|
||||
public string Method { get; set; } = "";
|
||||
public int OldWeight { get; set; } = 100;
|
||||
public int NewWeight { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="DTO\ChartDataPointDto.cs" />
|
||||
<Compile Remove="DTO\ChartSeriesDto.cs" />
|
||||
<Compile Remove="DTO\FermiCommentatiDTO.cs" />
|
||||
<Compile Remove="DTO\userTknDTO.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
+156
-31
@@ -1,8 +1,4 @@
|
||||
#if false
|
||||
using MP.Core.DbModels;
|
||||
#endif
|
||||
|
||||
using StackExchange.Redis;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace MP.Core
|
||||
{
|
||||
@@ -34,6 +30,11 @@ namespace MP.Core
|
||||
|
||||
public const string redisGiacenzaList = redisBaseAddr + "Cache:GiacenzaList";
|
||||
|
||||
public const string redisKit = redisBaseAddr + "Cache:Kit";
|
||||
public const string redisKitInst = redisBaseAddr + "Cache:Kit:Inst";
|
||||
public const string redisKitScore = redisBaseAddr + "Cache:Kit:Score";
|
||||
public const string redisKitTempl = redisBaseAddr + "Cache:Kit:Templ";
|
||||
public const string redisKitWip = redisBaseAddr + "Cache:Kit:Wip";
|
||||
public const string redisMacByFlux = redisBaseAddr + "Cache:MacByFlux";
|
||||
|
||||
public const string redisMacList = redisBaseAddr + "Cache:MacList";
|
||||
@@ -45,6 +46,7 @@ namespace MP.Core
|
||||
public const string redisOdlByBatch = redisXdlData + "OdlByBatch";
|
||||
|
||||
public const string redisOdlCurrByMac = redisXdlData + "OdlByMac";
|
||||
public const string redisOdlLastByMac = redisXdlData + "LastOdlByMac";
|
||||
|
||||
public const string redisOdlList = redisXdlData + "OdlList";
|
||||
|
||||
@@ -53,18 +55,15 @@ namespace MP.Core
|
||||
public const string redisParamPageExp = redisBaseAddr + "Cache:ParamPage";
|
||||
|
||||
public const string redisParetoFLKey = redisBaseAddr + "Cache:ParetoFL";
|
||||
public const string redisStatoMacch = redisBaseAddr + "Cache:StatoMacch";
|
||||
|
||||
public const string redisPOdlByOdl = redisXdlData + "POdlByOdl";
|
||||
|
||||
public const string redisPOdlByCodArt = redisXdlData + "POdlByCodArt";
|
||||
public const string redisPOdlByOdl = redisXdlData + "POdlByOdl";
|
||||
public const string redisPOdlByPOdl = redisXdlData + "POdlByPOdl";
|
||||
|
||||
public const string redisPOdlList = redisXdlData + "POdlList";
|
||||
|
||||
public const string redisRecipeConf = redisBaseAddr + "Cache:Recipe:Conf";
|
||||
|
||||
public const string redisRemRebLog = redisBaseAddr + "Cache:RemReb";
|
||||
public const string redisStatoCom = redisBaseAddr + "Cache:StatoCom";
|
||||
public const string redisStatoMacch = redisBaseAddr + "Cache:StatoMacch";
|
||||
public const string redisStatoProd = redisBaseAddr + "Cache:StatoProd";
|
||||
|
||||
public const string redisStatsDbMaint = redisBaseAddr + "Stats:DbMaint";
|
||||
|
||||
@@ -76,12 +75,6 @@ namespace MP.Core
|
||||
|
||||
public const string redisXdlData = redisBaseAddr + "Cache:XDL:";
|
||||
|
||||
public const string redisKit = redisBaseAddr + "Cache:Kit";
|
||||
public const string redisKitInst = redisBaseAddr + "Cache:Kit:Inst";
|
||||
public const string redisKitScore = redisBaseAddr + "Cache:Kit:Score";
|
||||
public const string redisKitTempl = redisBaseAddr + "Cache:Kit:Templ";
|
||||
public const string redisKitWip = redisBaseAddr + "Cache:Kit:Wip";
|
||||
|
||||
#endregion Public Fields
|
||||
|
||||
#region Public Properties
|
||||
@@ -100,6 +93,17 @@ namespace MP.Core
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Effettua la masking di un valore (INT) rispetto ad una BitMask (INT)
|
||||
/// </summary>
|
||||
/// <param name="valore">Valore da filtrare</param>
|
||||
/// <param name="bitMask">BitMask (espresso in valore INT: es 1001 --> 9)</param>
|
||||
/// <returns></returns>
|
||||
public static int bMaskInt(int valore, int bitMask)
|
||||
{
|
||||
return valore & bitMask;
|
||||
}
|
||||
|
||||
public static string ConvMinToTime(double minutes)
|
||||
{
|
||||
// FIXME TODO: da rendere parametrico da appsettings.json...
|
||||
@@ -116,16 +120,6 @@ namespace MP.Core
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati STATUS x la macchina specificata
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey dtMaccHash(string idxMacchina)
|
||||
{
|
||||
return (RedisKey)$"{redisBaseAddr}DtMac:{idxMacchina}";
|
||||
}
|
||||
|
||||
public static string FormDurata(double durataMinuti)
|
||||
{
|
||||
string answ = "";
|
||||
@@ -146,9 +140,9 @@ namespace MP.Core
|
||||
/// </summary>
|
||||
/// <param name="idxFamIn"></param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey hSMI(int idxFamIn)
|
||||
public static RedisKey GetHashSMI(int idxFamIn)
|
||||
{
|
||||
return (RedisKey)$"{redisBaseAddr}hSMI:{idxFamIn}";
|
||||
return (RedisKey)$"{redisBaseAddr}GetHashSMI:{idxFamIn}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -184,6 +178,30 @@ namespace MP.Core
|
||||
return (RedisKey)$"MP:Data:{keyName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chiave x CurrObjItems macchina
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyCurrObjItems(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:CurrentParameters:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati STATUS x la macchina specificata
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyDatiMacc(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:DtMac:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formato RedisKey delal chaive richeista (completa)
|
||||
/// </summary>
|
||||
@@ -192,6 +210,114 @@ namespace MP.Core
|
||||
return (RedisKey)$"{redisBaseAddr}{keyName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati MemMap di un IOB
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyIobMemMap(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:MemMap:{idxMacchina}";
|
||||
}
|
||||
/// <summary>
|
||||
/// Hash dati MemMap di un IOB
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyIobConfYaml(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:IOB:{idxMacchina}:ConfYaml";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati ultimo FluxLog ricevuto (ex LiveFLogKeyHash)
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="flog"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyLastFLog(string idxMacchina, string flog, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:FLOG:{idxMacchina}:{flog}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash associazione Iob e macchina acquisizione (WIN)
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr"></param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyMach2Iob(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:hM2IOB:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati Conf Iob
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr"></param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyMachIobConf(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:IOB:{idxMacchina}:MachIobConf";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati Macchine Multi SM Ingressi
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyMsmi(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:hMSMI:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati countapezzi macchina
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyPzCount(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:PzCount:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati SAVED (EXE) TASK x la macchina specificata x poter ripristinare in caso di
|
||||
/// perdita valore WRITE (ex savedTaskHash)
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeySavedTask2ExeMacc(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:SavedTask:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash dati Task2Exe x la macchina specificata (ex exeTaskHash)
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina"></param>
|
||||
/// <param name="baseAddr">Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy)</param>
|
||||
/// <returns></returns>
|
||||
public static RedisKey RedKeyTask2ExeMacc(string idxMacchina, string baseAddr = null)
|
||||
{
|
||||
var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':');
|
||||
return (RedisKey)$"{prefix}:ExeTask:{idxMacchina}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formato RedisValue delal chaive richeista (completa)
|
||||
/// </summary>
|
||||
@@ -201,6 +327,5 @@ namespace MP.Core
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,14 @@ using Microsoft.Extensions.Configuration;
|
||||
using MP.Core.DTO;
|
||||
using MP.Core.Objects;
|
||||
using MP.Data.DbModels;
|
||||
using MP.Data.DTO;
|
||||
using MP.Data.Translate;
|
||||
using NLog;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ZXing;
|
||||
using static EgwCoreLib.Utils.DtUtils;
|
||||
using static MP.Core.Objects.Enums;
|
||||
|
||||
namespace MP.Data.Controllers
|
||||
{
|
||||
@@ -531,7 +526,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
return fatto;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Elenco da tabella Config
|
||||
/// </summary>
|
||||
@@ -682,7 +677,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redisConn
|
||||
/// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache _redisConn
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina">macchina</param>
|
||||
/// <param name="maxSec">Num massimo secondi per recuperare dati correnti</param>
|
||||
@@ -705,7 +700,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redisConn
|
||||
/// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache _redisConn
|
||||
/// </summary>
|
||||
/// <param name="idxMacchina">macchina</param>
|
||||
/// <param name="dtMin">Data min x selezione</param>
|
||||
|
||||
@@ -27,6 +27,36 @@ namespace MP.Data.Controllers
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Insert record allarme
|
||||
/// </summary>
|
||||
/// <param name="dtRif">Data evento</param>
|
||||
/// <param name="machineId">Id macchina</param>
|
||||
/// <param name="memAddress">area memoria</param>
|
||||
/// <param name="memAddress">indice memoria</param>
|
||||
/// <param name="memAddress">valore status</param>
|
||||
/// <param name="memAddress">valore decodificato</param>
|
||||
/// <returns></returns>
|
||||
public bool AlarmLogInsert(DateTime dtRif, string machineId, string memAddress, int memIndex, int statusVal, string valDecoded)
|
||||
{
|
||||
bool fatto = false;
|
||||
using (var dbCtx = new MoonProContext(_configuration))
|
||||
{
|
||||
var DtRif = new SqlParameter("@DtRif", dtRif);
|
||||
var MachineId = new SqlParameter("@MachineId", machineId);
|
||||
var MemAddress = new SqlParameter("@MemAddress", memAddress);
|
||||
var MemIndex = new SqlParameter("@MemIndex", memIndex);
|
||||
var StatusVal = new SqlParameter("@StatusVal", statusVal);
|
||||
var ValDecoded = new SqlParameter("@ValDecoded", valDecoded);
|
||||
|
||||
var result = dbCtx
|
||||
.Database
|
||||
.ExecuteSqlRaw("EXEC stp_AL_insertQuery @DtRif, @MachineId, @MemAddress, @MemIndex, @StatusVal, @ValDecoded, ", DtRif, MachineId, MemAddress, MemIndex, StatusVal, ValDecoded);
|
||||
fatto = result != 0;
|
||||
}
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco allarmi macchina
|
||||
/// </summary>
|
||||
@@ -66,13 +96,13 @@ namespace MP.Data.Controllers
|
||||
bool fatto = false;
|
||||
using (var dbCtx = new MoonProContext(_configuration))
|
||||
{
|
||||
var AlarmLogId = new SqlParameter("@AlarmLogId", alarmLogId);
|
||||
var DtAck = new SqlParameter("@DtAck", dtAck);
|
||||
var UserAck = new SqlParameter("@UserAck", userAck);
|
||||
var AlarmLogId = new SqlParameter("@MachineId", alarmLogId);
|
||||
var DtAck = new SqlParameter("@DtRif", dtAck);
|
||||
var UserAck = new SqlParameter("@MemAddress", userAck);
|
||||
|
||||
var result = dbCtx
|
||||
.Database
|
||||
.ExecuteSqlRaw("EXEC stp_AL_setAck @AlarmLogId, @DtAck, @UserAck", AlarmLogId, DtAck, UserAck);
|
||||
.ExecuteSqlRaw("EXEC stp_AL_setAck @MachineId, @DtRif, @MemAddress", AlarmLogId, DtAck, UserAck);
|
||||
fatto = result != 0;
|
||||
}
|
||||
return fatto;
|
||||
@@ -89,12 +119,12 @@ namespace MP.Data.Controllers
|
||||
bool fatto = false;
|
||||
using (var dbCtx = new MoonProContext(_configuration))
|
||||
{
|
||||
var AlarmLogId = new SqlParameter("@AlarmLogId", alarmLogId);
|
||||
var AlarmLogId = new SqlParameter("@MachineId", alarmLogId);
|
||||
var DtNotify = new SqlParameter("@DtNotify", dtNotify);
|
||||
|
||||
var result = dbCtx
|
||||
.Database
|
||||
.ExecuteSqlRaw("EXEC stp_AL_setNotify @AlarmLogId, @DtNotify", AlarmLogId, DtNotify);
|
||||
.ExecuteSqlRaw("EXEC stp_AL_setNotify @MachineId, @DtNotify", AlarmLogId, DtNotify);
|
||||
fatto = result != 0;
|
||||
}
|
||||
return fatto;
|
||||
@@ -1180,7 +1210,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
Log.Error($"Eccezione durante OdlCurrByMacc{Environment.NewLine}{exc}");
|
||||
Log.Error($"Eccezione durante OdlCurrByMaccAsync{Environment.NewLine}{exc}");
|
||||
}
|
||||
}
|
||||
return dbResult;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MP.Data.DTO
|
||||
{
|
||||
public class ChartSeriesDto
|
||||
{
|
||||
public string SeriesName { get; set; }
|
||||
public List<chartJsData.chartJsTSerie> DataPoints { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -34,18 +34,31 @@ namespace MP.Data.DbModels
|
||||
{
|
||||
get
|
||||
{
|
||||
string answ = "";
|
||||
DateTime end = DataFine != null ? (DateTime)DataFine : DateTime.Now;
|
||||
var tsDurata = (end).Subtract((DateTime)DataInizio);
|
||||
if (DataInizio == null)
|
||||
return "";
|
||||
|
||||
var start = DataInizio.Value;
|
||||
var end = DataFine ?? DateTime.Now;
|
||||
|
||||
var tsDurata = end - start;
|
||||
|
||||
if (tsDurata.TotalDays < 1)
|
||||
{
|
||||
answ = $"{tsDurata.Hours:00}h {tsDurata.Minutes:00}'";
|
||||
}
|
||||
else
|
||||
{
|
||||
answ = $"{tsDurata.Days}gg {tsDurata.Hours:00}h";
|
||||
}
|
||||
return answ;
|
||||
return $"{tsDurata.Hours:00}h {tsDurata.Minutes:00}'";
|
||||
|
||||
return $"{tsDurata.Days}gg {tsDurata.Hours:00}h";
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
public TimeSpan DurataMinutiTimeSpan
|
||||
{
|
||||
get
|
||||
{
|
||||
if (DataInizio == null)
|
||||
return TimeSpan.FromSeconds(0);
|
||||
|
||||
DateTime end = DataFine != null ? (DateTime)DataFine : DateTime.Now;
|
||||
TimeSpan tsDurata = (end).Subtract((DateTime)DataInizio);
|
||||
return tsDurata;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
// </Auto-Generated>
|
||||
namespace MP.Data.DbModels
|
||||
{
|
||||
//[Table("DiarioDichiarazioni")]
|
||||
[Table("DiarioDichiarazioni")]
|
||||
public partial class RegistroDichiarazioniModel
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
|
||||
// <Auto-Generated>
|
||||
// This is here so CodeMaid doesn't reorganize this document
|
||||
// </Auto-Generated>
|
||||
namespace MP.Data.DbModels
|
||||
{
|
||||
[Table("SignalLog")]
|
||||
public partial class SignalLogModel
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
|
||||
[Column("idxMsg")]
|
||||
public int IdxMsg { get; set; }
|
||||
|
||||
[Column("dtCurr")]
|
||||
public DateTime? DtCurr { get; set; } = DateTime.Now;
|
||||
|
||||
[Column("idxMacchina")]
|
||||
[MaxLength(50)]
|
||||
public string IdxMacchina { get; set; } = "NA";
|
||||
|
||||
[MaxLength(50)]
|
||||
[Column("valore")]
|
||||
public string Valore { get; set; } = "";
|
||||
|
||||
[Column("dtEve")]
|
||||
public DateTime? DtEve { get; set; }
|
||||
|
||||
[Column("contatore")]
|
||||
public int Contatore { get; set; } = 0;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
// <Auto-Generated>
|
||||
// This is here so CodeMaid doesn't reorganize this document
|
||||
// </Auto-Generated>
|
||||
namespace MP.Data.DbModels.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Classe statistiche impiego orario complessive
|
||||
/// </summary>
|
||||
[Table("stats_aggr")]
|
||||
public class StatsAggregatedModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Ambiente di destinazione IOC/IO (new/old)
|
||||
/// </summary>
|
||||
public string Destination { get; set; } = "";
|
||||
/// <summary>
|
||||
/// DataOra riferimento
|
||||
/// </summary>
|
||||
public DateTime Hour { get; set; }
|
||||
/// <summary>
|
||||
/// Num richieste complete/concluse (con ritorno)
|
||||
/// </summary>
|
||||
public long RequestCount { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata media esecuzione
|
||||
/// </summary>
|
||||
public double AvgDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata minima esecuzione
|
||||
/// </summary>
|
||||
public double MinDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata massima esecuzione
|
||||
/// </summary>
|
||||
public double MaxDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// numero richieste senza risposta (errori)
|
||||
/// </summary>
|
||||
public long NoReply { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
// <Auto-Generated>
|
||||
// This is here so CodeMaid doesn't reorganize this document
|
||||
// </Auto-Generated>
|
||||
namespace MP.Data.DbModels.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Classe statistiche impiego (orarie) di dettaglio (per envir/tipo)
|
||||
/// </summary>
|
||||
[Table("stats_detail")]
|
||||
public class StatsDetailModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Ambiente di destinazione IOC/IO (new/old)
|
||||
/// </summary>
|
||||
public string Destination { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Codice tipo (metodo chiamato)
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "";
|
||||
/// <summary>
|
||||
/// DataOra riferimento
|
||||
/// </summary>
|
||||
public DateTime Hour { get; set; }
|
||||
/// <summary>
|
||||
/// Num richieste complete/concluse (con ritorno)
|
||||
/// </summary>
|
||||
public long RequestCount { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata media esecuzione
|
||||
/// </summary>
|
||||
public double AvgDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata minima esecuzione
|
||||
/// </summary>
|
||||
public double MinDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Durata massima esecuzione
|
||||
/// </summary>
|
||||
public double MaxDuration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// numero richieste senza risposta (errori)
|
||||
/// </summary>
|
||||
public long NoReply { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using MP.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MP.Data.Migrations.MoonPro_Utils
|
||||
{
|
||||
[DbContext(typeof(MoonPro_UtilsContext))]
|
||||
[Migration("20260408062523_InitDb")]
|
||||
partial class InitDb
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.UseCollation("SQL_Latin1_General_CP1_CI_AS")
|
||||
.HasAnnotation("ProductVersion", "6.0.36")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
|
||||
|
||||
modelBuilder.Entity("MP.Data.DbModels.Utils.StatsAggregatedModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1);
|
||||
|
||||
b.Property<double>("AvgDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Destination")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime>("Hour")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<double>("MaxDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double>("MinDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<long>("NoReply")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RequestCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Destination", "Hour")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_statsaggr_env_hour")
|
||||
.HasFilter("[Destination] IS NOT NULL");
|
||||
|
||||
b.ToTable("stats_aggr");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MP.Data.DbModels.Utils.StatsDetailModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1);
|
||||
|
||||
b.Property<double>("AvgDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Destination")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime>("Hour")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<double>("MaxDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double>("MinDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<long>("NoReply")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RequestCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Destination", "Type", "Hour")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_statsdet_hour_env_type")
|
||||
.HasFilter("[Destination] IS NOT NULL AND [Type] IS NOT NULL");
|
||||
|
||||
b.ToTable("stats_detail");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MP.Data.Migrations.MoonPro_Utils
|
||||
{
|
||||
public partial class InitDb : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stats_aggr",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Destination = table.Column<string>(type: "nvarchar(450)", nullable: true),
|
||||
Hour = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
RequestCount = table.Column<long>(type: "bigint", nullable: false),
|
||||
AvgDuration = table.Column<double>(type: "float", nullable: false),
|
||||
MinDuration = table.Column<double>(type: "float", nullable: false),
|
||||
MaxDuration = table.Column<double>(type: "float", nullable: false),
|
||||
NoReply = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stats_aggr", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stats_detail",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Destination = table.Column<string>(type: "nvarchar(450)", nullable: true),
|
||||
Type = table.Column<string>(type: "nvarchar(450)", nullable: true),
|
||||
Hour = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
RequestCount = table.Column<long>(type: "bigint", nullable: false),
|
||||
AvgDuration = table.Column<double>(type: "float", nullable: false),
|
||||
MinDuration = table.Column<double>(type: "float", nullable: false),
|
||||
MaxDuration = table.Column<double>(type: "float", nullable: false),
|
||||
NoReply = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stats_detail", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx_statsaggr_env_hour",
|
||||
table: "stats_aggr",
|
||||
columns: new[] { "Destination", "Hour" },
|
||||
unique: true,
|
||||
filter: "[Destination] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx_statsdet_hour_env_type",
|
||||
table: "stats_detail",
|
||||
columns: new[] { "Destination", "Type", "Hour" },
|
||||
unique: true,
|
||||
filter: "[Destination] IS NOT NULL AND [Type] IS NOT NULL");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "stats_aggr");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stats_detail");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using MP.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MP.Data.Migrations.MoonPro_Utils
|
||||
{
|
||||
[DbContext(typeof(MoonPro_UtilsContext))]
|
||||
partial class MoonPro_UtilsContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.UseCollation("SQL_Latin1_General_CP1_CI_AS")
|
||||
.HasAnnotation("ProductVersion", "6.0.36")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
|
||||
|
||||
modelBuilder.Entity("MP.Data.DbModels.Utils.StatsAggregatedModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1);
|
||||
|
||||
b.Property<double>("AvgDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Destination")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime>("Hour")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<double>("MaxDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double>("MinDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<long>("NoReply")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RequestCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Destination", "Hour")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_statsaggr_env_hour")
|
||||
.HasFilter("[Destination] IS NOT NULL");
|
||||
|
||||
b.ToTable("stats_aggr");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MP.Data.DbModels.Utils.StatsDetailModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1);
|
||||
|
||||
b.Property<double>("AvgDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Destination")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime>("Hour")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<double>("MaxDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double>("MinDuration")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<long>("NoReply")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("RequestCount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Destination", "Type", "Hour")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_statsdet_hour_env_type")
|
||||
.HasFilter("[Destination] IS NOT NULL AND [Type] IS NOT NULL");
|
||||
|
||||
b.ToTable("stats_detail");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MP.Data.DbModels;
|
||||
using MP.Data.DTO;
|
||||
using NLog;
|
||||
|
||||
#nullable disable
|
||||
@@ -82,6 +79,7 @@ namespace MP.Data
|
||||
public virtual DbSet<RegistroScartiKitModel> DbSetRegScartiKit { get; set; }
|
||||
public virtual DbSet<RegistroDichiarazioniModel> DbSetRegDich { get; set; }
|
||||
public virtual DbSet<RegCheckModel> DbSetRegWithCheck { get; set; }
|
||||
public virtual DbSet<SignalLogModel> DbSetSignalLog { get; set; }
|
||||
|
||||
|
||||
public virtual DbSet<ST_Act> DbSetStAct { get; set; }
|
||||
@@ -197,7 +195,7 @@ namespace MP.Data
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
});
|
||||
|
||||
|
||||
modelBuilder.Entity<ElencoConfermeProdModel>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.DataOraConf, e.IdxMacchina });
|
||||
@@ -601,7 +599,7 @@ namespace MP.Data
|
||||
});
|
||||
modelBuilder.Entity<WipSetupKitModel>(entity =>
|
||||
{
|
||||
entity.HasKey(e => new { e.KeyFilt, e.CodOrd});
|
||||
entity.HasKey(e => new { e.KeyFilt, e.CodOrd });
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using NLog;
|
||||
using System;
|
||||
|
||||
#nullable disable
|
||||
// <Auto-Generated>
|
||||
// This is here so CodeMaid doesn't reorganize this document
|
||||
// </Auto-Generated>
|
||||
namespace MP.Data
|
||||
{
|
||||
public partial class MoonPro_UtilsContext : DbContext
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private IConfiguration _configuration;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Indispensabile x prima generazione migrations EFCore
|
||||
/// </summary>
|
||||
|
||||
[Obsolete("This constructor should never be used directly, and is only needed to generate entityframework stuff. Connection string can be adapted as pleased.")]
|
||||
public MoonPro_UtilsContext()
|
||||
{
|
||||
}
|
||||
|
||||
//public MoonPro_UtilsContext(IConfiguration configuration)
|
||||
//{
|
||||
// _configuration = configuration;
|
||||
//}
|
||||
|
||||
public MoonPro_UtilsContext(DbContextOptions<MoonPro_UtilsContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Properties
|
||||
|
||||
|
||||
public virtual DbSet<StatsDetailModel> DbSetStatsDet { get; set; }
|
||||
public virtual DbSet<StatsAggregatedModel> DbSetStatsAggr { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
string connString = _configuration.GetConnectionString("MP.Utils");
|
||||
if (!string.IsNullOrEmpty(connString))
|
||||
{
|
||||
optionsBuilder.UseSqlServer(connString);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionsBuilder.UseSqlServer("Server=SQL2016DEV;Database=MoonPro_Utils;Trusted_Connection=True;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
|
||||
|
||||
// indici tabelle stats
|
||||
modelBuilder.Entity<StatsDetailModel>()
|
||||
.HasIndex(x => new { x.Destination, x.Type, x.Hour })
|
||||
.HasDatabaseName("idx_statsdet_hour_env_type")
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<StatsAggregatedModel>()
|
||||
.HasIndex(x => new { x.Destination, x.Hour })
|
||||
.HasDatabaseName("idx_statsaggr_env_hour")
|
||||
.IsUnique();
|
||||
|
||||
// modelBuilder.Entity<AnagArticoliModel>(entity =>
|
||||
// {
|
||||
// entity.HasKey(e => e.CodArticolo);
|
||||
|
||||
// entity.ToView("AnagArticoli");
|
||||
|
||||
// entity.Property(e => e.CodArticolo)
|
||||
// .IsRequired()
|
||||
// .HasMaxLength(50);
|
||||
|
||||
// entity.Property(e => e.DescArticolo)
|
||||
// .IsRequired()
|
||||
// .HasMaxLength(250);
|
||||
|
||||
// entity.Property(e => e.Disegno)
|
||||
// .IsRequired()
|
||||
// .HasMaxLength(50);
|
||||
|
||||
// entity.Property(e => e.Tipo)
|
||||
// .IsRequired()
|
||||
// .HasMaxLength(50);
|
||||
|
||||
// entity.Property(e => e.Azienda)
|
||||
// .IsRequired()
|
||||
// .HasMaxLength(50);
|
||||
// });
|
||||
|
||||
// modelBuilder.Entity<ParetoFluxLogDTO>(entity =>
|
||||
// {
|
||||
// entity.HasKey(e => new { e.IdxMacchina, e.CodFlux });
|
||||
// });
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Repository
|
||||
{
|
||||
public abstract class BaseRepository : IBaseRepository
|
||||
{
|
||||
#region Protected Fields
|
||||
|
||||
protected readonly IDbContextFactory<MoonPro_UtilsContext> _ctxFactory;
|
||||
|
||||
#endregion Protected Fields
|
||||
|
||||
#region Protected Constructors
|
||||
|
||||
protected BaseRepository(IDbContextFactory<MoonPro_UtilsContext> ctxFactory)
|
||||
=> _ctxFactory = ctxFactory;
|
||||
|
||||
#endregion Protected Constructors
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creazione dbcontext per singola transazione
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task<MoonPro_UtilsContext> CreateContextAsync()
|
||||
=> await _ctxFactory.CreateDbContextAsync();
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Salvataggio dati asincrono
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task<bool> SaveChangesAsync(DataLayerContext ctx)
|
||||
=> await ctx.SaveChangesAsync() > 0;
|
||||
#endif
|
||||
|
||||
#if false
|
||||
protected readonly DataLayerContext _dbCtx;
|
||||
protected BaseRepository(DataLayerContext db) => _dbCtx = db;
|
||||
public async Task<bool> SaveChangesAsync() => await _dbCtx.SaveChangesAsync() > 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MP.Data.Repository
|
||||
{
|
||||
public interface IBaseRepository
|
||||
{
|
||||
//Task<DataLayerContext> CreateContextAsync();
|
||||
//Task<bool> SaveChangesAsync(DataLayerContext ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Repository.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Interfaccia per le statistiche aggregate.
|
||||
/// </summary>
|
||||
public interface IStatsAggrRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Recupera l'elenco delle statistiche aggregate per un periodo specifico.
|
||||
/// </summary>
|
||||
/// <param name="dtStart">La data di inizio del periodo.</param>
|
||||
/// <param name="dtEnd">La data di fine del periodo.</param>
|
||||
/// <returns>L'elenco delle statistiche aggregate ordinate cronologicamente.</returns>
|
||||
Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd);
|
||||
|
||||
/// <summary>
|
||||
/// Recupera l'intervallo temporale disponibile nel database per le statistiche aggregate.
|
||||
/// </summary>
|
||||
/// <returns>L'intervallo di date (minima e massima ora presente).</returns>
|
||||
Task<DtUtils.Periodo> GetRangeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Inserisce o aggiorna in blocco le statistiche aggregate nel database.
|
||||
/// </summary>
|
||||
/// <param name="listRecords">L'elenco dei record da inserire.</param>
|
||||
/// <param name="removeOld">Se true, elimina preventivamente i record nel periodo richiesto.</param>
|
||||
/// <returns>Il numero di record inseriti.</returns>
|
||||
Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Repository.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Interfaccia per le statistiche di dettaglio.
|
||||
/// </summary>
|
||||
public interface IStatsDetailRepository
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Recupera l'elenco delle statistiche di dettaglio per un periodo specifico, con filtri opzionali.
|
||||
/// </summary>
|
||||
/// <param name="dtStart">La data di inizio del periodo.</param>
|
||||
/// <param name="dtEnd">La data di fine del periodo.</param>
|
||||
/// <param name="sEnvir">Filtro opzionale per l'ambiente (es. "DEV", "PROD").</param>
|
||||
/// <param name="sType">Filtro opzionale per il tipo di statistica.</param>
|
||||
/// <returns>L'elenco delle statistiche di dettaglio ordinate cronologicamente.</returns>
|
||||
Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "");
|
||||
|
||||
/// <summary>
|
||||
/// Elenco record in modalità pareto
|
||||
/// </summary>
|
||||
/// <param name="dtStart"></param>
|
||||
/// <param name="dtEnd"></param>
|
||||
/// <param name="maxRec"></param>
|
||||
/// <param name="sDest"></param>
|
||||
/// <param name="sType"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<StatsDetailModel>> GetParetoAsync(DateTime dtStart, DateTime dtEnd, int maxRec, string sDest = "", string sType = "");
|
||||
|
||||
/// <summary>
|
||||
/// Recupera l'intervallo temporale disponibile nel database per le statistiche di dettaglio.
|
||||
/// </summary>
|
||||
/// <param name="sEnvir">Filtro opzionale per l'ambiente.</param>
|
||||
/// <param name="sType">Filtro opzionale per il tipo.</param>
|
||||
/// <returns>L'intervallo di date (minima e massima ora presente).</returns>
|
||||
Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType);
|
||||
|
||||
/// <summary>
|
||||
/// Inserisce o aggiorna in blocco le statistiche di dettaglio nel database.
|
||||
/// </summary>
|
||||
/// <param name="listRecords">L'elenco dei record da inserire.</param>
|
||||
/// <param name="removeOld">Se true, elimina preventivamente i record nel periodo richiesto.</param>
|
||||
/// <returns>Il numero di record inseriti.</returns>
|
||||
Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld);
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Repository.Utils
|
||||
{
|
||||
public class StatsAggrRepository : BaseRepository, IStatsAggrRepository
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
public StatsAggrRepository(IDbContextFactory<MoonPro_UtilsContext> ctxFactory) : base(ctxFactory)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd)
|
||||
{
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
return await dbCtx
|
||||
.DbSetStatsAggr
|
||||
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd)
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Hour)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DtUtils.Periodo> GetRangeAsync()
|
||||
{
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
|
||||
var query = dbCtx.DbSetStatsAggr.AsQueryable();
|
||||
var minHour = await query.MinAsync(x => x.Hour);
|
||||
var maxHour = await query.MaxAsync(x => x.Hour);
|
||||
answ.Inizio = minHour;
|
||||
answ.Fine = maxHour;
|
||||
// ritorno!
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld)
|
||||
{
|
||||
if (listRecords == null || !listRecords.Any()) return 0;
|
||||
|
||||
int ans = 0;
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
await using var tx = await dbCtx.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Calcolo del range temporale della lista in arrivo per limitare la query di ricerca
|
||||
var minHour = listRecords.Min(x => x.Hour);
|
||||
var maxHour = listRecords.Max(x => x.Hour);
|
||||
|
||||
// 2. Se removeOld è true, manteniamo la logica originale (Eliminazione distruttiva)
|
||||
if (removeOld)
|
||||
{
|
||||
// uso direttamente ExecuteDelete quando in EFCore8...
|
||||
#if false
|
||||
await dbCtx
|
||||
.DbSetStatsAggr
|
||||
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
|
||||
.ExecuteDeleteAsync();
|
||||
#endif
|
||||
|
||||
var itemsToRemove = await dbCtx.DbSetStatsAggr
|
||||
.Where(x => x.Hour >= minHour && x.Hour <= maxHour)
|
||||
.ToListAsync();
|
||||
if (itemsToRemove.Any())
|
||||
{
|
||||
dbCtx.DbSetStatsAggr.RemoveRange(itemsToRemove);
|
||||
await dbCtx.SaveChangesAsync(); // Commit parziale per la cancellazione
|
||||
}
|
||||
}
|
||||
|
||||
// 3. LOGICA DI UPSERT (Merge)
|
||||
// Recuperiamo tutti i record esistenti nel database che cadono nello stesso range temporale
|
||||
// Questo ci permette di confrontare ciò che arriva con ciò che è già presente.
|
||||
var existingRecords = await dbCtx.DbSetStatsAggr
|
||||
.Where(x => x.Hour >= minHour && x.Hour <= maxHour)
|
||||
.ToListAsync();
|
||||
|
||||
// Creiamo un dizionario per ricerca rapida O(1) basato sulla chiave univoca (Dest + Hour)
|
||||
// Usiamo una Tupla come chiave del dizionario
|
||||
var lookup = existingRecords.ToDictionary(
|
||||
x => (x.Destination, x.Hour),
|
||||
x => x
|
||||
);
|
||||
|
||||
foreach (var incoming in listRecords)
|
||||
{
|
||||
var key = (incoming.Destination, incoming.Hour);
|
||||
if (lookup.TryGetValue(key, out var existing))
|
||||
{
|
||||
// --- CASO: UPDATE ---
|
||||
existing.RequestCount = incoming.RequestCount;
|
||||
existing.AvgDuration = incoming.AvgDuration;
|
||||
existing.MinDuration = incoming.MinDuration;
|
||||
existing.MaxDuration = incoming.MaxDuration;
|
||||
// Aggiungi altri campi se presenti (es. NoReply, ecc.)
|
||||
}
|
||||
else
|
||||
{
|
||||
// --- CASO: INSERT ---
|
||||
await dbCtx.DbSetStatsAggr.AddAsync(incoming);
|
||||
}
|
||||
}
|
||||
// 4. Salvataggio finale
|
||||
ans = await dbCtx.SaveChangesAsync();
|
||||
|
||||
// Commit della transazione
|
||||
await tx.CommitAsync();
|
||||
|
||||
// Pulizia memoria per evitare che il ChangeTracker diventi troppo pesante nei loop lunghi
|
||||
dbCtx.ChangeTracker.Clear();
|
||||
|
||||
return ans;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
Log.Error(ex, "Error during UpsertManyAsync");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsyncOrig(List<StatsAggregatedModel> listRecords, bool removeOld)
|
||||
{
|
||||
int answ = 0;
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
await using var tx = await dbCtx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
// in primis se richiesto calcolo range periodo e svuoto...
|
||||
if (removeOld)
|
||||
{
|
||||
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
|
||||
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
|
||||
|
||||
if (firstRec != null && lastRec != null)
|
||||
{
|
||||
DateTime startDate = firstRec.Hour;
|
||||
DateTime endDate = lastRec.Hour;
|
||||
|
||||
var items = await dbCtx.DbSetStatsAggr
|
||||
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
|
||||
.ToListAsync();
|
||||
|
||||
dbCtx.DbSetStatsAggr.RemoveRange(items);
|
||||
await dbCtx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
await dbCtx
|
||||
.DbSetStatsAggr
|
||||
.AddRangeAsync(listRecords);
|
||||
|
||||
// salvo!
|
||||
answ = await dbCtx.SaveChangesAsync();
|
||||
|
||||
// commit transazione
|
||||
await tx.CommitAsync();
|
||||
|
||||
// libero memoria del changeTracker
|
||||
dbCtx.ChangeTracker.Clear();
|
||||
return answ;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected static NLog.Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#endregion Protected Fields
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Repository.Utils
|
||||
{
|
||||
public class StatsDetailRepository : BaseRepository, IStatsDetailRepository
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
public StatsDetailRepository(IDbContextFactory<MoonPro_UtilsContext> ctxFactory) : base(ctxFactory)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
|
||||
{
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
List<StatsDetailModel> answ = new List<StatsDetailModel>();
|
||||
|
||||
// recupero ed ordino per data-ora
|
||||
var query = dbCtx.DbSetStatsDet
|
||||
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd);
|
||||
|
||||
if (!string.IsNullOrEmpty(sEnvir))
|
||||
query = query.Where(x => x.Destination == sEnvir);
|
||||
|
||||
if (!string.IsNullOrEmpty(sType))
|
||||
query = query.Where(x => x.Type == sType);
|
||||
|
||||
answ = await query
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Hour)
|
||||
.ThenBy(x => x.Destination)
|
||||
.ThenBy(x => x.Type)
|
||||
.ToListAsync();
|
||||
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsDetailModel>> GetParetoAsync(DateTime dtStart, DateTime dtEnd, int maxRec, string sDest = "", string sType = "")
|
||||
{
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
List<StatsDetailModel> answ = new List<StatsDetailModel>();
|
||||
|
||||
// recupero ed ordino per data-ora
|
||||
var query = dbCtx.DbSetStatsDet
|
||||
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd);
|
||||
|
||||
if (!string.IsNullOrEmpty(sDest))
|
||||
query = query.Where(x => x.Destination == sDest);
|
||||
|
||||
if (!string.IsNullOrEmpty(sType))
|
||||
query = query.Where(x => x.Type == sType);
|
||||
|
||||
answ = await query
|
||||
.AsNoTracking()
|
||||
.OrderByDescending(x => x.RequestCount)
|
||||
.ThenBy(x => x.Hour)
|
||||
.ThenBy(x => x.Destination)
|
||||
.ThenBy(x => x.Type)
|
||||
.Take(maxRec)
|
||||
.ToListAsync();
|
||||
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType)
|
||||
{
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
|
||||
|
||||
var query = dbCtx.DbSetStatsDet.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(sEnvir))
|
||||
query = query.Where(x => x.Destination == sEnvir);
|
||||
|
||||
if (!string.IsNullOrEmpty(sType))
|
||||
query = query.Where(x => x.Type == sType);
|
||||
|
||||
var minHour = await query.MinAsync(x => x.Hour);
|
||||
var maxHour = await query.MaxAsync(x => x.Hour);
|
||||
answ.Inizio = minHour;
|
||||
answ.Fine = maxHour;
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld)
|
||||
{
|
||||
if (listRecords == null || !listRecords.Any()) return 0;
|
||||
|
||||
int answ = 0;
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
await using var tx = await dbCtx.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Calcolo del range temporale basato sulla lista in arrivo per ottimizzare la query SQL
|
||||
var minHour = listRecords.Min(x => x.Hour);
|
||||
var maxHour = listRecords.Max(x => x.Hour);
|
||||
|
||||
// 2. Gestione eliminazione distruttiva (se richiesto)
|
||||
if (removeOld)
|
||||
{
|
||||
// uso direttamente ExecuteDelete quando in EFCore8...
|
||||
#if false
|
||||
await dbCtx
|
||||
.DbSetStatsDet
|
||||
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
|
||||
.ExecuteDeleteAsync();
|
||||
#endif
|
||||
|
||||
var itemsToRemove = await dbCtx.DbSetStatsDet
|
||||
.Where(x => x.Hour >= minHour && x.Hour <= maxHour)
|
||||
.ToListAsync();
|
||||
|
||||
if (itemsToRemove.Any())
|
||||
{
|
||||
dbCtx.DbSetStatsDet.RemoveRange(itemsToRemove);
|
||||
await dbCtx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. LOGICA DI UPSERT (Merge basato su Destination + Type + Hour)
|
||||
|
||||
// Recuperiamo dal DB solo i record che rientrano nel range temporale della lista in arrivo
|
||||
var existingRecords = await dbCtx.DbSetStatsDet
|
||||
.Where(x => x.Hour >= minHour && x.Hour <= maxHour)
|
||||
.ToListAsync();
|
||||
|
||||
// Creiamo il dizionario di lookup con la chiave composta (Dest, Type, Hour)
|
||||
var lookup = existingRecords.ToDictionary(
|
||||
x => (x.Destination, x.Type, x.Hour),
|
||||
x => x
|
||||
);
|
||||
|
||||
foreach (var incoming in listRecords)
|
||||
{
|
||||
// Creiamo la chiave di ricerca basata sul record in arrivo
|
||||
var key = (incoming.Destination, incoming.Type, incoming.Hour);
|
||||
|
||||
if (lookup.TryGetValue(key, out var existing))
|
||||
{
|
||||
// --- CASO: UPDATE ---
|
||||
// Aggiorniamo i valori del record esistente con quelli nuovi
|
||||
existing.RequestCount = incoming.RequestCount;
|
||||
existing.AvgDuration = incoming.AvgDuration;
|
||||
existing.MinDuration = incoming.MinDuration;
|
||||
existing.MaxDuration = incoming.MaxDuration;
|
||||
existing.NoReply = incoming.NoReply;
|
||||
}
|
||||
else
|
||||
{
|
||||
// --- CASO: INSERT ---
|
||||
// Il record non esiste per questa combinazione, lo aggiungiamo
|
||||
await dbCtx.DbSetStatsDet.AddAsync(incoming);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Salvataggio finale delle modifiche (Insert + Update)
|
||||
answ = await dbCtx.SaveChangesAsync();
|
||||
|
||||
// Commit della transazione
|
||||
await tx.CommitAsync();
|
||||
|
||||
// Pulizia memoria del ChangeTracker per evitare accumuli durante sessioni lunghe
|
||||
dbCtx.ChangeTracker.Clear();
|
||||
|
||||
return answ;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
Log.Error(ex, "Error during StatsDetail UpsertManyAsync");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#if false
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsyncOrig(List<StatsDetailModel> listRecords, bool removeOld)
|
||||
{
|
||||
int answ = 0;
|
||||
await using var dbCtx = await CreateContextAsync();
|
||||
await using var tx = await dbCtx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
// in primis se richiesto calcolo range periodo e svuoto...
|
||||
if (removeOld)
|
||||
{
|
||||
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
|
||||
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
|
||||
|
||||
if (firstRec != null && lastRec != null)
|
||||
{
|
||||
DateTime startDate = firstRec.Hour;
|
||||
DateTime endDate = lastRec.Hour;
|
||||
|
||||
var items = await dbCtx.DbSetStatsDet
|
||||
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
|
||||
.ToListAsync();
|
||||
|
||||
dbCtx.DbSetStatsDet.RemoveRange(items);
|
||||
await dbCtx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
await dbCtx
|
||||
.DbSetStatsDet
|
||||
.AddRangeAsync(listRecords);
|
||||
|
||||
// salvo!
|
||||
answ = await dbCtx.SaveChangesAsync();
|
||||
// commit transazione
|
||||
await tx.CommitAsync();
|
||||
|
||||
// libero memoria del changeTracker
|
||||
dbCtx.ChangeTracker.Clear();
|
||||
return answ;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await tx.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected static NLog.Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#endregion Protected Fields
|
||||
}
|
||||
}
|
||||
+196
-39
@@ -1,5 +1,4 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MP.Core.Objects;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
@@ -8,7 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Services
|
||||
@@ -16,39 +15,20 @@ namespace MP.Data.Services
|
||||
/// <summary>
|
||||
/// Classe di partenza x costruzione servizi di accesso dati + cache
|
||||
/// </summary>
|
||||
public class BaseServ:IDisposable
|
||||
public class BaseServ : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
private bool _disposed = false;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free managed resources here
|
||||
redisDb = null;
|
||||
}
|
||||
|
||||
// Free unmanaged resources here
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public BaseServ(IConfiguration configuration, IConnectionMultiplexer redConn)
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
// Verifica conf trace...
|
||||
_traceEnabled = _configuration.GetValue<bool>("Otel:EnableTracing", false);
|
||||
|
||||
// setup componenti REDIS
|
||||
this.redisConn = redConn;
|
||||
redisDb = redisConn.GetDatabase();
|
||||
_redisConn = redConn;
|
||||
_redisDb = _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()
|
||||
@@ -64,6 +44,12 @@ namespace MP.Data.Services
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recupero info IOB x TAB (da info registrate IOB-WIN--> MP-IO)
|
||||
/// </summary>
|
||||
@@ -75,9 +61,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
IOB_data? result = new IOB_data();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = RedHashMpIO($"hM2IOB:{IdxMacchina}");
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
RedisValue rawData = _redisDb.StringGet(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -87,7 +73,7 @@ namespace MP.Data.Services
|
||||
else
|
||||
{
|
||||
Log.Error($"Errore: non trovato valore <IOB_data> valido in REDIS | key: {currKey}");
|
||||
Log.Info($"REDIS | conf: {redisConn.Configuration}");
|
||||
Log.Info($"REDIS | conf: {_redisConn.Configuration}");
|
||||
Log.Info($" --> Valore trovato:{Environment.NewLine}{rawData}");
|
||||
}
|
||||
if (result == null)
|
||||
@@ -111,11 +97,11 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
Dictionary<string, string> result = new Dictionary<string, string>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = RedHashMpIO($"IOB:{IdxMacchina}:MachIobConf");
|
||||
try
|
||||
{
|
||||
result = redisDb
|
||||
result = _redisDb
|
||||
.HashGetAll(currKey)
|
||||
.ToDictionary(x => $"{x.Name}", x => $"{x.Value}");
|
||||
source = "REDIS";
|
||||
@@ -145,11 +131,11 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
string result = "";
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = RedHashMpIO($"IOB:{IdxMacchina}:ConfYaml");
|
||||
try
|
||||
{
|
||||
result = redisDb.StringGet(currKey);
|
||||
result = _redisDb.StringGet(currKey);
|
||||
source = "REDIS";
|
||||
}
|
||||
catch (Exception exc)
|
||||
@@ -170,20 +156,31 @@ namespace MP.Data.Services
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto per collezione dati Activity (span in Uptrace)
|
||||
/// </summary>
|
||||
protected static readonly ActivitySource ActivitySource = new ActivitySource("MP.IOC");
|
||||
|
||||
protected static IConfiguration _configuration = null!;
|
||||
protected JsonSerializerSettings? JSSettings;
|
||||
protected string MpIoNS = "";
|
||||
|
||||
/// <summary>
|
||||
/// Abilitazione operazioni tracing generiche
|
||||
/// </summary>
|
||||
protected readonly bool _traceEnabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto per connessione a REDIS
|
||||
/// </summary>
|
||||
protected IConnectionMultiplexer redisConn = null!;
|
||||
protected IConnectionMultiplexer _redisConn = null!;
|
||||
|
||||
//ISubscriber sub = redisConn.GetSubscriber();
|
||||
//ISubscriber sub = _redisConn.GetSubscriber();
|
||||
/// <summary>
|
||||
/// Oggetto DB redis da impiegare x chiamate R/W
|
||||
/// </summary>
|
||||
protected IDatabase redisDb = null!;
|
||||
protected IDatabase _redisDb = null!;
|
||||
|
||||
protected JsonSerializerSettings? JSSettings;
|
||||
protected string MpIoNS = "";
|
||||
|
||||
#endregion Protected Fields
|
||||
|
||||
@@ -225,6 +222,128 @@ namespace MP.Data.Services
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Helper avvio attività per la funzione tracciata
|
||||
/// </summary>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
protected static Activity? StartActivity([CallerMemberName] string? methodName = null)
|
||||
{
|
||||
var activity = ActivitySource.StartActivity(methodName ?? "UNDEF");
|
||||
activity?.SetTag("host.name", Environment.MachineName);
|
||||
return activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalida una o più chiavi/pattern in Redis
|
||||
/// </summary>
|
||||
protected async Task ClearCacheAsync(params string[] patterns)
|
||||
{
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
// Chiamata al tuo metodo esistente
|
||||
await ExecFlushRedisPatternAsync((RedisValue)pattern);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free managed resources here
|
||||
_redisDb = null;
|
||||
}
|
||||
|
||||
// Free unmanaged resources here
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo di flush dati cache Redis
|
||||
/// </summary>
|
||||
/// <param name="pattern"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task ExecFlushRedisPatternAsync(RedisValue pattern)
|
||||
{
|
||||
// Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan)
|
||||
// Esempio rapido via server scan:
|
||||
var endpoints = _redisConn.GetEndPoints();
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
var server = _redisConn.GetServer(endpoint);
|
||||
await foreach (var key in server.KeysAsync(_redisDb.Database, pattern))
|
||||
{
|
||||
await _redisDb.KeyDeleteAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="expiration"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task<T> GetOrSetCacheAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null)
|
||||
{
|
||||
using var activity = StartActivity();
|
||||
string source = "DB";
|
||||
|
||||
// 1. Provo Redis
|
||||
var cached = await _redisDb.StringGetAsync(key);
|
||||
if (cached.HasValue)
|
||||
{
|
||||
source = "REDIS";
|
||||
var cachedResult = JsonConvert.DeserializeObject<T>(cached!)!;
|
||||
|
||||
activity?.SetTag("data.source", source);
|
||||
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller);
|
||||
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
// 2. Chiamo il factory (DB)
|
||||
T result = await factory();
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
// 3. Salva in Redis per la prossima volta
|
||||
var serialized = JsonConvert.SerializeObject(result, JSSettings);
|
||||
await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache);
|
||||
}
|
||||
|
||||
// sistemo activity tracking data
|
||||
activity?.SetTag("data.source", source);
|
||||
activity?.Stop();
|
||||
|
||||
// log in console
|
||||
LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller);
|
||||
|
||||
return result!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper trace messaggio log (SE abilitato)
|
||||
/// </summary>
|
||||
/// <param name="traceMsg"></param>
|
||||
/// <param name="reqLevel"></param>
|
||||
/// <param name="methodName"></param>
|
||||
protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null)
|
||||
{
|
||||
if (!_traceEnabled)
|
||||
return;
|
||||
|
||||
reqLevel ??= NLog.LogLevel.Debug;
|
||||
|
||||
// Loggo!
|
||||
Log.Log(reqLevel, $"{methodName} | {traceMsg}");
|
||||
}
|
||||
|
||||
protected string RedHashMpIO(string keyName)
|
||||
{
|
||||
string result = keyName;
|
||||
@@ -240,11 +359,44 @@ namespace MP.Data.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper generale per la telemetria e gestione eccezioni
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task<T> TraceAsync<T>(string name, Func<Activity?, Task<T>> body, object? parameters = null)
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity(name);
|
||||
try
|
||||
{
|
||||
if (parameters != null)
|
||||
{
|
||||
activity?.SetTag("params", JsonConvert.SerializeObject(parameters));
|
||||
}
|
||||
var result = await body(activity);
|
||||
activity?.SetStatus(ActivityStatusCode.Ok);
|
||||
activity?.Stop();
|
||||
LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
|
||||
//Log.Error(ex, "Errore in {MethodName}", name);
|
||||
LogTrace($"Errore in {name}", NLog.LogLevel.Error, name);
|
||||
throw; // Riesponi l'eccezione per il tracking globale
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Durata cache lunga IN SECONDI
|
||||
@@ -258,6 +410,11 @@ namespace MP.Data.Services
|
||||
|
||||
private Random rnd = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// Path base chiavi REDIS
|
||||
/// </summary>
|
||||
protected readonly string _redisBaseKey = "MP:IOC";
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -59,9 +59,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<DbSizeModel> result = new List<DbSizeModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:DbInfo:ALL";
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
RedisValue rawData = _redisDb.StringGet(currKey);
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
result = JsonConvert.DeserializeObject<List<DbSizeModel>>($"{rawData}");
|
||||
@@ -72,7 +72,7 @@ namespace MP.Data.Services
|
||||
result = dbController.AllDbInfo();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -95,7 +95,7 @@ namespace MP.Data.Services
|
||||
string currKey = $"{redisBaseKey}:IobList";
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
string? rawData = redisDb.StringGet(currKey);
|
||||
string? rawData = _redisDb.StringGet(currKey);
|
||||
if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2)
|
||||
{
|
||||
source = "REDIS";
|
||||
@@ -123,9 +123,9 @@ namespace MP.Data.Services
|
||||
//ordino x idxmacchina...
|
||||
dbResult = dbResult.OrderBy(x => x.IdxMacchina).ToList();
|
||||
|
||||
// serializzo in cache redisConn
|
||||
// serializzo in cache _redisConn
|
||||
rawData = JsonConvert.SerializeObject(dbResult, JSSettings);
|
||||
redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (dbResult == null)
|
||||
{
|
||||
@@ -147,9 +147,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<RemoteRebootLogModel> result = new List<RemoteRebootLogModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:RemRebLog:ALL";
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
RedisValue rawData = _redisDb.StringGet(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -161,7 +161,7 @@ namespace MP.Data.Services
|
||||
result = dbController.RemRebootLogGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -183,9 +183,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<RemoteRebootLogModel> result = new List<RemoteRebootLogModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:RemRebLog:LAST";
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
RedisValue rawData = _redisDb.StringGet(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -197,7 +197,7 @@ namespace MP.Data.Services
|
||||
result = dbController.RemRebootLogGetLast();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraFastCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
|
||||
@@ -63,9 +63,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<AnagArticoliModel>? result = new List<AnagArticoliModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Art:{azienda}:{searchVal}:{numRecord}";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
result = JsonConvert.DeserializeObject<List<AnagArticoliModel>>($"{rawData}");
|
||||
@@ -79,7 +79,7 @@ namespace MP.Data.Services
|
||||
#endif
|
||||
// serializzp e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
await redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
await _redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -100,9 +100,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<ConfigModel>? result = new List<ConfigModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Conf";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
result = JsonConvert.DeserializeObject<List<ConfigModel>>($"{rawData}");
|
||||
@@ -113,7 +113,7 @@ namespace MP.Data.Services
|
||||
result = dbController.ConfigGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, LongCache);
|
||||
_redisDb.StringSet(currKey, rawData, LongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -156,9 +156,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<LinkMenuModel>? result = new List<LinkMenuModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Menu:ALL";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
result = JsonConvert.DeserializeObject<List<LinkMenuModel>>($"{rawData}");
|
||||
@@ -169,7 +169,7 @@ namespace MP.Data.Services
|
||||
result = await Task.FromResult(dbController.ListLinkAll());
|
||||
// serializzp e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
await redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -191,9 +191,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<LinkMenuModel>? result = new List<LinkMenuModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Menu:{tipoLink}";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -205,7 +205,7 @@ namespace MP.Data.Services
|
||||
result = await Task.FromResult(dbController.ListLinkFilt(tipoLink));
|
||||
// serializzp e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
await redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -227,9 +227,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<MacchineModel>? result = new List<MacchineModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Macc:{MatrOpr}";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -241,7 +241,7 @@ namespace MP.Data.Services
|
||||
result = await Task.FromResult(dbController.MacchineByMatrOper(MatrOpr));
|
||||
// serializzp e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
await redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
await _redisDb.StringSetAsync(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -287,31 +287,31 @@ namespace MP.Data.Services
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Esegue flush memoria redisConn dato pat2Flush
|
||||
/// Esegue flush memoria _redisConn dato pat2Flush
|
||||
/// </summary>
|
||||
/// <param name="pat2Flush"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> ExecFlushRedisPattern(RedisValue pat2Flush)
|
||||
{
|
||||
bool answ = false;
|
||||
var masterEndpoint = redisConn.GetEndPoints()
|
||||
.Where(ep => redisConn.GetServer(ep).IsConnected && !redisConn.GetServer(ep).IsReplica)
|
||||
var masterEndpoint = _redisConn.GetEndPoints()
|
||||
.Where(ep => _redisConn.GetServer(ep).IsConnected && !_redisConn.GetServer(ep).IsReplica)
|
||||
.FirstOrDefault();
|
||||
|
||||
// sepattern è "*" elimino intero DB...
|
||||
if (masterEndpoint != null && (pat2Flush.Equals(new RedisValue("*")) || pat2Flush == RedisValue.Null))
|
||||
{
|
||||
redisConn.GetServer(masterEndpoint).FlushDatabase(database: redisDb.Database);
|
||||
_redisConn.GetServer(masterEndpoint).FlushDatabase(database: _redisDb.Database);
|
||||
}
|
||||
else
|
||||
{
|
||||
var server = redisConn.GetServer(masterEndpoint);
|
||||
var keys = server.Keys(database: redisDb.Database, pattern: pat2Flush, pageSize: 1000);
|
||||
var server = _redisConn.GetServer(masterEndpoint);
|
||||
var keys = server.Keys(database: _redisDb.Database, pattern: pat2Flush, pageSize: 1000);
|
||||
|
||||
var deleteTasks = new List<Task>();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
deleteTasks.Add(redisDb.KeyDeleteAsync(key));
|
||||
deleteTasks.Add(_redisDb.KeyDeleteAsync(key));
|
||||
if (deleteTasks.Count >= 1000)
|
||||
{
|
||||
await Task.WhenAll(deleteTasks);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#if false
|
||||
using Blazored.LocalStorage;
|
||||
using Blazored.SessionStorage;
|
||||
#endif
|
||||
using EgwCoreLib.Utils;
|
||||
using EgwCoreLib.Utils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MP.Data.DbModels;
|
||||
using MP.Data.DTO;
|
||||
@@ -186,6 +182,9 @@ namespace MP.Data.Services
|
||||
}
|
||||
}
|
||||
|
||||
public string PageName;
|
||||
public string PageIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Diritti utente (solo Funzione/Ruolo)
|
||||
/// </summary>
|
||||
@@ -688,7 +687,7 @@ namespace MP.Data.Services
|
||||
protected IConnectionMultiplexer redisConn = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto DB redisConn da impiegare x chiamate R/W
|
||||
/// Oggetto DB _redisConn da impiegare x chiamate R/W
|
||||
/// </summary>
|
||||
protected IDatabase redisDb = null!;
|
||||
|
||||
@@ -745,7 +744,7 @@ namespace MP.Data.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recupero HashSet redisConn come Dictionary
|
||||
/// Recupero HashSet _redisConn come Dictionary
|
||||
/// </summary>
|
||||
/// <param name="currKey"></param>
|
||||
/// <param name="dict"></param>
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<ConfigModel>? result = new List<ConfigModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Conf";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace MP.Data.Services
|
||||
result = dbController.ConfigGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, LongCache);
|
||||
_redisDb.StringSet(currKey, rawData, LongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -103,9 +103,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<ODLExpModel>? result = new List<ODLExpModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:ODL:{IdxMacchina}:{CodArt}";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -117,7 +117,7 @@ namespace MP.Data.Services
|
||||
result = await Task.FromResult(dbController.ListODLFilt(inCorso, CodArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate));
|
||||
// serializzp e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
await redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
await _redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace MP.Data.Services
|
||||
//Dictionary<string, List<EventDto>>? result = null;
|
||||
//// cerco in redis...
|
||||
//string currKey = $"{redisBaseKey}:PlannerData:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}";
|
||||
//RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
//RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
////if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
//if (rawData.HasValue && rawData.Length() > 2)
|
||||
//{
|
||||
@@ -80,7 +80,7 @@ namespace MP.Data.Services
|
||||
// result = dataSimController.PlannerGetEvents(dtStart, dtEnd);
|
||||
// // serializzo e salvo...
|
||||
// rawData = JsonConvert.SerializeObject(result);
|
||||
// await redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
// await _redisDb.StringSetAsync(currKey, rawData, LongCache);
|
||||
//}
|
||||
//activity?.SetTag("data.source", source);
|
||||
//LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms");
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<ConfigModel>? result = new List<ConfigModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Conf";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
@@ -176,7 +176,7 @@ namespace MP.Data.Services
|
||||
string source = "DB";
|
||||
sw.Start();
|
||||
List<MacchineModel>? result = new List<MacchineModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{Constants.redisMacchine}:ALL";
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
if (rawData.HasValue)
|
||||
@@ -207,7 +207,7 @@ namespace MP.Data.Services
|
||||
string source = "DB";
|
||||
sw.Start();
|
||||
List<MacchineModel>? result = new List<MacchineModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{Constants.redisMacchine}:{CodGruppo}";
|
||||
RedisValue rawData = redisDb.StringGet(currKey);
|
||||
if (rawData.HasValue)
|
||||
@@ -326,7 +326,7 @@ namespace MP.Data.Services
|
||||
string source = "DB";
|
||||
sw.Start();
|
||||
List<MappaStatoExplModel>? result = new List<MappaStatoExplModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
RedisValue rawData = redisDb.StringGet(Constants.redisMseKey);
|
||||
if (rawData.HasValue && !forceDb)
|
||||
{
|
||||
@@ -426,7 +426,7 @@ namespace MP.Data.Services
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Esegue flush memoria redisConn dato pat2Flush
|
||||
/// Esegue flush memoria _redisConn dato pat2Flush
|
||||
/// </summary>
|
||||
/// <param name="pat2Flush"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
+201
-210
File diff suppressed because it is too large
Load Diff
@@ -62,9 +62,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<ConfigModel>? result = new List<ConfigModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Conf";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace MP.Data.Services
|
||||
result = dbController.ConfigGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, LongCache);
|
||||
_redisDb.StringSet(currKey, rawData, LongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -122,9 +122,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<LingueModel>? result = new List<LingueModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Lang";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -136,7 +136,7 @@ namespace MP.Data.Services
|
||||
result = dbController.LingueGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -174,9 +174,9 @@ namespace MP.Data.Services
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
List<VocabolarioModel>? result = new List<VocabolarioModel>();
|
||||
// cerco in redisConn...
|
||||
// cerco in _redisConn...
|
||||
string currKey = $"{redisBaseKey}:Voc";
|
||||
RedisValue rawData = await redisDb.StringGetAsync(currKey);
|
||||
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
|
||||
//if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
if (rawData.HasValue)
|
||||
{
|
||||
@@ -188,7 +188,7 @@ namespace MP.Data.Services
|
||||
result = dbController.VocabolarioGetAll();
|
||||
// serializzo e salvo...
|
||||
rawData = JsonConvert.SerializeObject(result);
|
||||
redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
_redisDb.StringSet(currKey, rawData, UltraLongCache);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
@@ -254,31 +254,31 @@ namespace MP.Data.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue flush memoria redisConn dato pat2Flush
|
||||
/// Esegue flush memoria _redisConn dato pat2Flush
|
||||
/// </summary>
|
||||
/// <param name="pat2Flush"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> ExecFlushRedisPattern(RedisValue pat2Flush)
|
||||
{
|
||||
bool answ = false;
|
||||
var masterEndpoint = redisConn.GetEndPoints()
|
||||
.Where(ep => redisConn.GetServer(ep).IsConnected && !redisConn.GetServer(ep).IsReplica)
|
||||
var masterEndpoint = _redisConn.GetEndPoints()
|
||||
.Where(ep => _redisConn.GetServer(ep).IsConnected && !_redisConn.GetServer(ep).IsReplica)
|
||||
.FirstOrDefault();
|
||||
|
||||
// sepattern è "*" elimino intero DB...
|
||||
if (masterEndpoint != null && (pat2Flush.Equals(new RedisValue("*")) || pat2Flush == RedisValue.Null))
|
||||
{
|
||||
redisConn.GetServer(masterEndpoint).FlushDatabase(database: redisDb.Database);
|
||||
_redisConn.GetServer(masterEndpoint).FlushDatabase(database: _redisDb.Database);
|
||||
}
|
||||
else
|
||||
{
|
||||
var server = redisConn.GetServer(masterEndpoint);
|
||||
var keys = server.Keys(database: redisDb.Database, pattern: pat2Flush, pageSize: 1000);
|
||||
var server = _redisConn.GetServer(masterEndpoint);
|
||||
var keys = server.Keys(database: _redisDb.Database, pattern: pat2Flush, pageSize: 1000);
|
||||
|
||||
var deleteTasks = new List<Task>();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
deleteTasks.Add(redisDb.KeyDeleteAsync(key));
|
||||
deleteTasks.Add(_redisDb.KeyDeleteAsync(key));
|
||||
if (deleteTasks.Count >= 1000)
|
||||
{
|
||||
await Task.WhenAll(deleteTasks);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Services.Utils
|
||||
{
|
||||
public interface IStatsAggrService
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Recupera l'elenco delle statistiche aggregate per un periodo specificato.
|
||||
/// Utilizza la cache automaticamente.
|
||||
/// </summary>
|
||||
/// <param name="dtStart">Data inizio periodo</param>
|
||||
/// <param name="dtEnd">Data fine periodo</param>
|
||||
Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd);
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce un dizionario di elaborazioni di tipo Pareto su orizzonte settimanale
|
||||
/// Ogni elaborazione contiene Dictionary in forma pareto per una data statistica
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, List<StatDataDTO>>> GetParetoStatsWeekAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Recupera il range di periodi valido per le chiamate aggregate.
|
||||
/// Utilizza la cache automaticamente.
|
||||
/// </summary>
|
||||
Task<DtUtils.Periodo> GetRangeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Inserisce o aggiorna in batch le statistiche aggregate nel database.
|
||||
/// Opzionalmente elimina i record precedenti nel periodo specificato.
|
||||
/// </summary>
|
||||
/// <param name="listRecords">Elenco dei record da inserire/aggiornare</param>
|
||||
/// <param name="removeOld">Se true elimina preventivamente i record nel periodo richiesto</param>
|
||||
Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld);
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using MP.Data.DTO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Services.Utils
|
||||
{
|
||||
public interface IStatsDetailService
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Recupera le statistiche di dettaglio per un periodo specificato, con filtri opzionali su ambiente e tipo.
|
||||
/// Utilizza la cache automaticamente.
|
||||
/// </summary>
|
||||
/// <param name="dtStart">Data inizio periodo</param>
|
||||
/// <param name="dtEnd">Data fine periodo</param>
|
||||
/// <param name="sDest">Filtro opzionale per destinazione (default: vuoto = tutti)</param>
|
||||
/// <param name="sType">Filtro opzionale per tipo (default: vuoto = tutti)</param>
|
||||
Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sDest = "", string sType = "");
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce un Pareto degli eventi (in forma originale) più frequenti dato periodo ed eventuale filtro Destinazione/Tipo
|
||||
/// </summary>
|
||||
/// <param name="dtStart">Data inizio periodo</param>
|
||||
/// <param name="dtEnd">Data fine periodo</param>
|
||||
/// <param name="maxRec"></param>
|
||||
/// <param name="sDest">Filtro opzionale per destinazione (default: vuoto = tutti)</param>
|
||||
/// <param name="sType">Filtro opzionale per tipo (default: vuoto = tutti)</param>
|
||||
/// <returns></returns>
|
||||
Task<List<StatsDetailModel>> GetParetoAsync(DateTime dtStart, DateTime dtEnd, int maxRec, string sDest = "", string sType = "");
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce un dizionario di elaborazioni di tipo Pareto
|
||||
/// Ogni elaborazione contiene Dictionary in forma pareto per una data statistica
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, List<StatDataDTO>>> GetParetoStatsDayAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Recupera il range di periodi valido per le chiamate di dettaglio.
|
||||
/// Utilizza la cache automaticamente.
|
||||
/// </summary>
|
||||
/// <param name="sEnvir">Ambiente filtrato</param>
|
||||
/// <param name="sType">Tipo filtrato</param>
|
||||
Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType);
|
||||
|
||||
/// <summary>
|
||||
/// Helper conversione dati dettaglio in statistiche da inviare a ChartJS
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
/// <returns></returns>
|
||||
List<ChartSeriesDto> GetTimeSeriesData(List<StatsDetailModel> rawData);
|
||||
|
||||
/// <summary>
|
||||
/// Inserisce o aggiorna in batch le statistiche di dettaglio nel database.
|
||||
/// Opzionalmente elimina i record precedenti nel periodo specificato.
|
||||
/// </summary>
|
||||
/// <param name="listRecords">Elenco dei record da inserire/aggiornare</param>
|
||||
/// <param name="removeOld">Se true elimina preventivamente i record nel periodo richiesto</param>
|
||||
Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld);
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using MP.Data.Repository.Utils;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Services.Utils
|
||||
{
|
||||
public class StatsAggrService : BaseServ, IStatsAggrService
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
public StatsAggrService(
|
||||
IConfiguration config,
|
||||
IConnectionMultiplexer redis,
|
||||
IStatsAggrRepository repo) : base(config, redis)
|
||||
{
|
||||
_className = "StatsAggr";
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd)
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetFilt", async (activity) =>
|
||||
{
|
||||
return await GetOrSetCacheAsync(
|
||||
$"{_redisBaseKey}:{_className}:DT:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}",
|
||||
async () => await _repo.GetFiltAsync(dtStart, dtEnd),
|
||||
UltraLongCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<string, List<StatDataDTO>>> GetParetoStatsWeekAsync()
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetDailyParetoStats", async (activity) =>
|
||||
{
|
||||
return await GetOrSetCacheAsync(
|
||||
$"{_redisBaseKey}:{_className}:ParetoWeek",
|
||||
async () => await GetParetoDataAsync(),
|
||||
LongCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DtUtils.Periodo> GetRangeAsync()
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetRange", async (activity) =>
|
||||
{
|
||||
return await GetOrSetCacheAsync(
|
||||
$"{_redisBaseKey}:{_className}:Range",
|
||||
async () => await _repo.GetRangeAsync(),
|
||||
UltraFastCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld)
|
||||
{
|
||||
return await TraceAsync($"{_className}.UpsertMany", async (activity) =>
|
||||
{
|
||||
string operation = "UpsertMany";
|
||||
var success = await _repo.UpsertManyAsync(listRecords, removeOld);
|
||||
|
||||
activity?.SetTag("db.operation", operation);
|
||||
|
||||
if (success > 0)
|
||||
{
|
||||
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// metodo locale per recupero e trasformazione dati da includere con processo generare di tracking & cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task<Dictionary<string, List<StatDataDTO>>> GetParetoDataAsync()
|
||||
{
|
||||
Dictionary<string, List<StatDataDTO>> result = new();
|
||||
DateTime oggi = DateTime.Today;
|
||||
var rawData = await GetFiltAsync(oggi.AddDays(-7), oggi);
|
||||
// calcolo le varie statistiche...
|
||||
var pDestRequest = rawData.GroupBy(x => x.Destination)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Dest.Request (#)", pDestRequest);
|
||||
|
||||
var pDestDuration = rawData.GroupBy(x => x.Destination)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount * x.AvgDuration)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Dest.Duration (ms)", pDestDuration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly string _className;
|
||||
private readonly IStatsAggrRepository _repo;
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using EgwCoreLib.Utils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using MP.Data.DTO;
|
||||
using MP.Data.Repository.Utils;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Data.Services.Utils
|
||||
{
|
||||
public class StatsDetailService : BaseServ, IStatsDetailService
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
public StatsDetailService(
|
||||
IConfiguration config,
|
||||
IConnectionMultiplexer redis,
|
||||
IStatsDetailRepository repo) : base(config, redis)
|
||||
{
|
||||
_className = "StatsDetail";
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sDest = "", string sType = "")
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetFilt", async (activity) =>
|
||||
{
|
||||
string cacheKey = $"{_redisBaseKey}:{_className}:DT:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}";
|
||||
if (!string.IsNullOrEmpty(sDest))
|
||||
{
|
||||
cacheKey += $":{sDest}";
|
||||
}
|
||||
if (!string.IsNullOrEmpty(sType))
|
||||
{
|
||||
cacheKey += $":{sType}";
|
||||
}
|
||||
return await GetOrSetCacheAsync(
|
||||
cacheKey,
|
||||
async () => await _repo.GetFiltAsync(dtStart, dtEnd, sDest, sType),
|
||||
LongCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<StatsDetailModel>> GetParetoAsync(DateTime dtStart, DateTime dtEnd, int maxRec, string sDest = "", string sType = "")
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetFilt", async (activity) =>
|
||||
{
|
||||
string cacheKey = $"{_redisBaseKey}:{_className}:PARETO:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}";
|
||||
if (!string.IsNullOrEmpty(sDest))
|
||||
{
|
||||
cacheKey += $":{sDest}";
|
||||
}
|
||||
if (!string.IsNullOrEmpty(sType))
|
||||
{
|
||||
cacheKey += $":{sType}";
|
||||
}
|
||||
return await GetOrSetCacheAsync(
|
||||
cacheKey,
|
||||
async () => await _repo.GetParetoAsync(dtStart, dtEnd, maxRec, sDest, sType),
|
||||
LongCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<string, List<StatDataDTO>>> GetParetoStatsDayAsync()
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetDailyParetoStats", async (activity) =>
|
||||
{
|
||||
return await GetOrSetCacheAsync(
|
||||
$"{_redisBaseKey}:{_className}:ParetoWeek",
|
||||
async () => await GetParetoDataAsync(),
|
||||
LongCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType)
|
||||
{
|
||||
return await TraceAsync($"{_className}.GetRange", async (activity) =>
|
||||
{
|
||||
return await GetOrSetCacheAsync(
|
||||
$"{_redisBaseKey}:{_className}:Range:{sEnvir}:{sType}",
|
||||
async () => await _repo.GetRangeAsync(sEnvir, sType),
|
||||
UltraFastCache
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<ChartSeriesDto> GetTimeSeriesData(List<StatsDetailModel> rawData)
|
||||
{
|
||||
var series = rawData
|
||||
.GroupBy(s => new { s.Destination, s.Type }) // Raggruppiamo per la chiave composta
|
||||
.Select(group => new ChartSeriesDto
|
||||
{
|
||||
// Creiamo un nome leggibile per la legenda del grafico
|
||||
SeriesName = $"{group.Key.Destination}|{group.Key.Type}",
|
||||
|
||||
// Per ogni gruppo, creiamo la lista dei punti temporali
|
||||
DataPoints = group
|
||||
.OrderBy(p => p.Hour) // Fondamentale: l'asse X deve essere cronologico
|
||||
.Select(p => new chartJsData.chartJsTSerie
|
||||
{
|
||||
x = p.Hour,
|
||||
y = p.AvgDuration // La metrica richiesta
|
||||
})
|
||||
.ToList()
|
||||
})
|
||||
.OrderBy(s => s.SeriesName) // Opzionale: ordina le serie alfabeticamente
|
||||
.ToList();
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld)
|
||||
{
|
||||
return await TraceAsync($"{_className}.UpsertMany", async (activity) =>
|
||||
{
|
||||
string operation = "UpsertMany";
|
||||
var success = await _repo.UpsertManyAsync(listRecords, removeOld);
|
||||
|
||||
activity?.SetTag("db.operation", operation);
|
||||
|
||||
if (success > 0)
|
||||
{
|
||||
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// metodo locale per recupero e trasformazione dati da includere con processo generare di tracking & cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task<Dictionary<string, List<StatDataDTO>>> GetParetoDataAsync()
|
||||
{
|
||||
Dictionary<string, List<StatDataDTO>> result = new();
|
||||
DateTime adesso = DateTime.Now;
|
||||
var rawData = await GetFiltAsync(adesso.AddDays(-1), adesso);
|
||||
// calcolo le varie statistiche...
|
||||
var pDestRequest = rawData.GroupBy(x => x.Destination)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Dest.Request (#)", pDestRequest);
|
||||
var pDestDuration = rawData.GroupBy(x => x.Destination)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount * x.AvgDuration)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Dest.Duration (ms)", pDestDuration);
|
||||
|
||||
var pTypeRequest = rawData.GroupBy(x => x.Type)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Type.Request (#)", pTypeRequest);
|
||||
var pTypeDuration = rawData.GroupBy(x => x.Type)
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = g.Key,
|
||||
Value = g.Sum(x => x.RequestCount * x.AvgDuration)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("Type.Duration (ms)", pTypeDuration);
|
||||
|
||||
// calcolo le varie statistiche COMPOSTE...
|
||||
var pDestTypeRequest = rawData.GroupBy(x => new { x.Destination, x.Type })
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = $"{g.Key.Destination}.{g.Key.Type}",
|
||||
Value = g.Sum(x => x.RequestCount)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("DestType.Request (#)", pDestTypeRequest);
|
||||
var pDestTypeDuration = rawData.GroupBy(x => new { x.Destination, x.Type })
|
||||
.Select(g => new StatDataDTO
|
||||
{
|
||||
Label = $"{g.Key.Destination}.{g.Key.Type}",
|
||||
Value = g.Sum(x => x.RequestCount * x.AvgDuration)
|
||||
})
|
||||
.OrderByDescending(x => x.Value)
|
||||
.ToList();
|
||||
result.Add("DestType.Duration (ms)", pDestTypeDuration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly string _className;
|
||||
private readonly IStatsDetailRepository _repo;
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@* <base href="/" /> *@
|
||||
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="lib/font-awesome/css/all.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="MP.IOC.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
@* <HeadOutlet /> *@
|
||||
<HeadOutlet @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@* <Routes /> *@
|
||||
<Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script type="text/javascript" src="lib/bootstrap/js/bootstrap.min.js"></script>
|
||||
@* <script src="lib/Chart.js/chart.js"></script> *@
|
||||
<script src="lib/Chart.js/chart.umd.js"></script>
|
||||
<script src="lib/luxon/luxon.js"></script>
|
||||
<script src="lib/chartjs-adapter-luxon/chartjs-adapter-luxon.umd.js"></script>
|
||||
<script src="lib/chartBoot.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,133 @@
|
||||
@using MP.Data
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyHist";
|
||||
|
||||
[Parameter]
|
||||
public string Legenda { get; set; } = "Legenda";
|
||||
|
||||
[Parameter]
|
||||
public bool Horizontal { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public List<double> Data { get; set; } = new List<double>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> Labels { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public double AspRatio { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public List<string> lineColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> backColor { get; set; } = new List<string>();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task renderChart()
|
||||
{
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js, tipo verticale
|
||||
var config = new
|
||||
{
|
||||
type = "bar",
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
yAxes = new
|
||||
{
|
||||
suggestedMin = 0,
|
||||
display = true,
|
||||
ticks = new
|
||||
{
|
||||
beginAtZero = true,
|
||||
maxTicksLimit = 10
|
||||
}
|
||||
}
|
||||
},
|
||||
Animation = false,
|
||||
AspectRatio = AspRatio == 0 ? "auto" : $"{AspRatio}"
|
||||
},
|
||||
data = new
|
||||
{
|
||||
datasets = new[] {
|
||||
new {
|
||||
data = Data,
|
||||
borderColor = lineColor,
|
||||
backgroundColor = backColor,
|
||||
borderWidth = 1,
|
||||
label = Legenda
|
||||
}
|
||||
},
|
||||
labels = Labels
|
||||
}
|
||||
};
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js, tipo verticale
|
||||
var configHor = new
|
||||
{
|
||||
type = "bar",
|
||||
options = new
|
||||
{
|
||||
indexAxis = "y",
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
yAxes = new
|
||||
{
|
||||
suggestedMin = 0,
|
||||
display = true,
|
||||
ticks = new
|
||||
{
|
||||
beginAtZero = true,
|
||||
maxTicksLimit = 10
|
||||
}
|
||||
}
|
||||
},
|
||||
Animation = false,
|
||||
AspectRatio = AspRatio == 0 ? "auto" : $"{AspRatio}"
|
||||
},
|
||||
data = new
|
||||
{
|
||||
datasets = new[] {
|
||||
new {
|
||||
data = Data,
|
||||
borderColor = lineColor,
|
||||
backgroundColor = backColor,
|
||||
borderWidth = 1,
|
||||
label = Legenda
|
||||
}
|
||||
},
|
||||
labels = Labels
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
if (Horizontal)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, configHor);
|
||||
}
|
||||
else
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
public enum ChartType
|
||||
{
|
||||
Pie,
|
||||
Bar
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyChart";
|
||||
|
||||
[Parameter]
|
||||
public ChartType Type { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string[]? Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string[]? BackgroundColor { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string[]? Labels { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await InitDefault();
|
||||
}
|
||||
|
||||
protected async Task InitDefault()
|
||||
{
|
||||
// Here we create an anonymous type with all the options
|
||||
// that need to be sent to Chart.js
|
||||
var config = new
|
||||
{
|
||||
type = Type.ToString().ToLower(),
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
yAxes = new
|
||||
{
|
||||
suggestedMin = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
data = new
|
||||
{
|
||||
datasets = new[]
|
||||
{
|
||||
new { data = Data, backgroundColor = BackgroundColor}
|
||||
},
|
||||
labels = Labels
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
@using MP.Data
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyTs";
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "Demo Line";
|
||||
|
||||
[Parameter]
|
||||
public List<chartJsData.chartJsTSerie> DataTS { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public List<string> Labels { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> lineColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> backColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public double AspRatio { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public bool Stepped { get; set; } = false;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string MinValue { get; set; } = "0";
|
||||
|
||||
[Parameter]
|
||||
public string MaxValue { get; set; } = "0";
|
||||
|
||||
[Parameter]
|
||||
public int lTens { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// await Task.Delay(100);
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task renderChart()
|
||||
{
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js
|
||||
var config = new
|
||||
{
|
||||
type = "line",
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
yAxes = new
|
||||
{
|
||||
type = "linear",
|
||||
// type = "logarithmic",
|
||||
display = true,
|
||||
ticks = new
|
||||
{
|
||||
maxTicksLimit = 10
|
||||
},
|
||||
suggestedMin = MinValue != MaxValue ? MinValue : "auto",
|
||||
suggestedMax = MinValue != MaxValue ? MaxValue : "auto"
|
||||
},
|
||||
xAxes = new
|
||||
{
|
||||
type = "time",
|
||||
distribution = "linear",
|
||||
}
|
||||
},
|
||||
Animation = false,
|
||||
AspectRatio = AspRatio == 0 ? "auto" : $"{AspRatio}"
|
||||
},
|
||||
data = new
|
||||
{
|
||||
labels = Labels,
|
||||
datasets = new[]
|
||||
{
|
||||
new {
|
||||
data = DataTS,
|
||||
borderColor = lineColor,
|
||||
backgroundColor = backColor,
|
||||
lineTension = lTens,
|
||||
stepped = Stepped,
|
||||
label = Title
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
@using MP.Data
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyTs";
|
||||
|
||||
[Parameter]
|
||||
public List<string> Titles { get; set; } = new List<string>() { "Demo Line" };
|
||||
|
||||
[Parameter]
|
||||
public List<List<chartJsData.chartJsTSerie>> DataTSList { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public List<string> Labels { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> lineColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> backColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public double AspRatio { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public bool Stepped { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public bool Stacked { get; set; } = false;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string MinValue { get; set; } = "0";
|
||||
|
||||
[Parameter]
|
||||
public string MaxValue { get; set; } = "0";
|
||||
|
||||
[Parameter]
|
||||
public int lTens { get; set; } = 0;
|
||||
|
||||
[Parameter]
|
||||
public string yScale { get; set; } = "linear";
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task renderChart()
|
||||
{
|
||||
int i = 0;
|
||||
// preparo lista di dati...
|
||||
var dataSetsList = DataTSList.Select(dTS => new
|
||||
{
|
||||
data = dTS,
|
||||
borderColor = lineColor[i],
|
||||
backgroundColor = backColor,
|
||||
lineTension = lTens,
|
||||
stepped = Stepped,
|
||||
label = Titles[i++]
|
||||
}).ToList();
|
||||
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js
|
||||
var config = new
|
||||
{
|
||||
type = "line",
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
y = new
|
||||
{
|
||||
stacked = Stacked,
|
||||
type = yScale,
|
||||
// type = "logarithmic",
|
||||
display = true,
|
||||
ticks = new
|
||||
{
|
||||
maxTicksLimit = 10
|
||||
},
|
||||
suggestedMin = MinValue != MaxValue ? MinValue : "auto",
|
||||
suggestedMax = MinValue != MaxValue ? MaxValue : "auto"
|
||||
},
|
||||
x = new
|
||||
{
|
||||
type = "time",
|
||||
time = new
|
||||
{
|
||||
unit = "hour" // Forza l'unità temporale se i dati sono orari
|
||||
},
|
||||
distribution = "linear",
|
||||
}
|
||||
},
|
||||
animation = false,
|
||||
aspectRatio = AspRatio == 0 ? (object)null : AspRatio
|
||||
},
|
||||
data = new
|
||||
{
|
||||
labels = Labels,
|
||||
datasets = dataSetsList
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
@using MP.Data
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyTs";
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "DEMO Pie Chart";
|
||||
|
||||
[Parameter]
|
||||
public string LegendPos { get; set; } = "right";
|
||||
|
||||
[Parameter]
|
||||
public List<double> Data { get; set; } = new List<double>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> Labels { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> lineColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> backColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public double AspRatio { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task renderChart()
|
||||
{
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js
|
||||
var config = new
|
||||
{
|
||||
type = "pie",
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
plugins = new
|
||||
{
|
||||
legend = new
|
||||
{
|
||||
position = LegendPos
|
||||
},
|
||||
title = new
|
||||
{
|
||||
display = true,
|
||||
text = Title
|
||||
}
|
||||
},
|
||||
Animation = false,
|
||||
AspectRatio = AspRatio == 0 ? "auto" : $"{AspRatio}",
|
||||
HoverBorderWidth = 3
|
||||
},
|
||||
data = new
|
||||
{
|
||||
labels = Labels.ToArray(),
|
||||
datasets = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
data = Data,
|
||||
borderColor= lineColor,
|
||||
backgroundColor= backColor
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
@using MP.Data
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<canvas id="@Id"></canvas>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Id { get; set; } = "MyTs";
|
||||
|
||||
[Parameter]
|
||||
public List<chartJsData.chartJsTSerie> DataTS { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public List<string> lineColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public List<string> backColor { get; set; } = new List<string>();
|
||||
|
||||
[Parameter]
|
||||
public double AspRatio { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await renderChart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializzazione rendering componente
|
||||
///
|
||||
/// partendo da qui:
|
||||
/// https://www.williamleme.com/posts/2020/003-chartjs-blazor/
|
||||
/// https://www.puresourcecode.com/dotnet/blazor/using-chart-js-with-blazor/
|
||||
/// https://www.tutorialsteacher.com/csharp/csharp-anonymous-type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected async Task renderChart()
|
||||
{
|
||||
// creazione di un oggetto anonymous type con tutte le opzioni da passare a chart.js
|
||||
var config = new
|
||||
{
|
||||
type = "line",
|
||||
options = new
|
||||
{
|
||||
responsive = true,
|
||||
scales = new
|
||||
{
|
||||
yAxes = new
|
||||
{
|
||||
display = true,
|
||||
ticks = new
|
||||
{
|
||||
maxTicksLimit = 10
|
||||
}
|
||||
},
|
||||
xAxes = new
|
||||
{
|
||||
type = "timeseries",
|
||||
distribution = "linear",
|
||||
}
|
||||
},
|
||||
Animation = false,
|
||||
AspectRatio = AspRatio == 0 ? "auto" : $"{AspRatio}"
|
||||
},
|
||||
data = new
|
||||
{
|
||||
datasets = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
data = DataTS,
|
||||
borderColor= lineColor,
|
||||
backgroundColor= backColor,
|
||||
lineTension= 0,
|
||||
stepped= true,
|
||||
label= "Temperatura Rilevata"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await Task.Delay(50);
|
||||
await JSRuntime.InvokeVoidAsync("setup", Id, config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="d-flex justify-content-between text-light w-100">
|
||||
<div class="px-1 text-start">
|
||||
MP-IOC <span class="small">v.@version</span>
|
||||
</div>
|
||||
<div class="px-1 text-end">
|
||||
<span class="small">@adesso</span>
|
||||
<a class="text-light" href="https://www.egalware.com/" target="_blank">Egalware <img class="img-fluid" width="16" src="images/LogoEgw.png" /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
protected DateTime adesso = DateTime.Now;
|
||||
|
||||
Version version = typeof(Program).Assembly.GetName().Version;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
<div class="row pt-2">
|
||||
<div class="col-7 col-md-6 col-lg-4 col-xl-3">
|
||||
<i class="fas fa-user-alt"></i> <b>@userName</b>
|
||||
</div>
|
||||
<div class="col-12 col-lg-4 col-xl-6 d-none d-lg-block text-center h4 text-truncate">
|
||||
<span><i class="@PageIcon" aria-hidden="true"></i> @PageName</span>
|
||||
</div>
|
||||
<div class="col-5 col-md-6 col-lg-4 col-xl-3 text-end">
|
||||
@if (ShowSearch)
|
||||
{
|
||||
@* <SearchMod></SearchMod> *@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace MP.IOC.Components.Compo
|
||||
{
|
||||
public partial class CmpTop : IDisposable
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
[Inject]
|
||||
protected AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!;
|
||||
|
||||
#endregion Protected Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await forceReload();
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private string userName = "";
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private string PageIcon { get; set; }
|
||||
|
||||
private string PageName { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "ShowSearch")]
|
||||
private bool ShowSearch { get; set; }
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task forceReload()
|
||||
{
|
||||
userName = "N.A.";
|
||||
await Task.Delay(1); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
var user = authState.User;
|
||||
if (user.Identity != null && user.Identity.IsAuthenticated)
|
||||
{
|
||||
userName = $"{user.Identity.Name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
userName = "N.A.";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="px-0 fw-bold">@Title</div>
|
||||
<div class="px-0">
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoReset()">
|
||||
<i class="fa-solid fa-arrow-rotate-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<ul class="list-group">
|
||||
@foreach (var itemDet in ListPaged)
|
||||
{
|
||||
<li class="list-group-item @CheckSelect(itemDet.Label) d-flex justify-content-between align-items-start small py-1">
|
||||
<div class="fs-6 align-items-center">
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoSelect(itemDet.Label)">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</button>
|
||||
@itemDet.Label
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<b>@($"{itemDet.Value:N0}")</b>
|
||||
<div class="small">@($"({valPerc(itemDet.Value):P2})")</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer py-0 px-1 small">
|
||||
<EgwCoreLib.Razor.DataPager currPage="@pageNum" PageSize="@numRecPage" totalCount="@totalCount" numPageChanged="SavePage" numRecordChanged="SaveNumRec" DisplSize="DataPager.ObjSize.small"></EgwCoreLib.Razor.DataPager>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MP.Core.DTO;
|
||||
|
||||
namespace MP.IOC.Components.Compo
|
||||
{
|
||||
public partial class ParetoDetail
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> EC_Selected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<StatDataDTO> ParetoList { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
//base.OnParametersSet();
|
||||
totalCount = ParetoList.Count();
|
||||
grandTotal = ParetoList.Sum(x => x.Value);
|
||||
if (grandTotal == 0)
|
||||
{
|
||||
grandTotal = 1;
|
||||
}
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
protected double valPerc(double valore)
|
||||
{
|
||||
return valore / grandTotal;
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private string currSelect = "";
|
||||
private double grandTotal = 1;
|
||||
private List<StatDataDTO> ListPaged = new();
|
||||
|
||||
private int numRecPage = 10;
|
||||
|
||||
private int pageNum = 1;
|
||||
|
||||
private int totalCount = 0;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private string CheckSelect(string curKey)
|
||||
{
|
||||
return !string.IsNullOrEmpty(currSelect) && currSelect == curKey ? "bg-dark text-light bg-gradient" : "";
|
||||
}
|
||||
|
||||
private async Task DoReset()
|
||||
{
|
||||
currSelect = "";
|
||||
await EC_Selected.InvokeAsync("");
|
||||
}
|
||||
|
||||
private async Task DoSelect(string reqKey)
|
||||
{
|
||||
if (ParetoList.Any(x => x.Label == reqKey))
|
||||
{
|
||||
currSelect = reqKey;
|
||||
await EC_Selected.InvokeAsync(reqKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveNumRec(int newNum)
|
||||
{
|
||||
numRecPage = newNum;
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
private void SavePage(int newNum)
|
||||
{
|
||||
pageNum = newNum;
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
private void UpdateTable()
|
||||
{
|
||||
// esegue paginazione
|
||||
if (totalCount > numRecPage)
|
||||
{
|
||||
ListPaged = ParetoList.Skip((pageNum - 1) * numRecPage).Take(numRecPage).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
ListPaged = ParetoList;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<PageTitle>MP-IOC</PageTitle>
|
||||
|
||||
<div class="page">
|
||||
<div class="@sideClass d-print-none">
|
||||
<NavMenu EC_compressUpdated="UpdateNavDisplay" />
|
||||
</div>
|
||||
|
||||
<div class="main pe-1">
|
||||
<div class="top-row d-print-none">
|
||||
<CmpTop></CmpTop>
|
||||
</div>
|
||||
<div class="content pt-1 pt-lg-2 mb-5 px-3">
|
||||
@Body
|
||||
</div>
|
||||
<div class="fixed-bottom bottom-row">
|
||||
<CmpFooter></CmpFooter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace MP.IOC.Components.Layout
|
||||
{
|
||||
public partial class MainLayout : IDisposable
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
public void OnHideSearch()
|
||||
{
|
||||
ShowSearch = false;
|
||||
}
|
||||
|
||||
public void OnShowSearch()
|
||||
{
|
||||
ShowSearch = true;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
protected bool annualAuthOk { get; set; } = false;
|
||||
|
||||
protected string cssRow { get; set; } = "bottom-row bg-primary";
|
||||
|
||||
protected bool navLarge { get; set; } = true;
|
||||
|
||||
protected string sideClass { get; set; } = "sidebar";
|
||||
|
||||
#endregion Protected Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected void UpdateNavDisplay()
|
||||
{
|
||||
navLarge = !navLarge;
|
||||
sideClass = navLarge ? "sidebar" : "sidebarSmall";
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private bool ShowSearch { get; set; } = false;
|
||||
|
||||
#endregion Private Properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
}
|
||||
.sidebar,
|
||||
.sidebarSmall {
|
||||
background-image: linear-gradient(180deg, #052767 20%, #3aa6ff 90%);
|
||||
}
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
height: 3.5rem;
|
||||
align-items: center;
|
||||
/*justify-content: space-evenly;
|
||||
display: flex;*/
|
||||
}
|
||||
.top-row ::deep a,
|
||||
.top-row .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
.top-row a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.bottom-row {
|
||||
color: #dedede;
|
||||
background-color: #000000;
|
||||
height: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
.sidebarSmall {
|
||||
width: 80px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.bottom-row {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.main > div {
|
||||
padding-left: 0.5rem !important;
|
||||
padding-right: 0.5rem !important;
|
||||
/*padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar,
|
||||
.sidebarSmall {
|
||||
background-image: -webkit-linear-gradient(180deg, rgb(5, 39, 103) 20%, #3aa6ff 90%);
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 20%, #3aa6ff 90%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
height: 3.5rem;
|
||||
align-items: center;
|
||||
/*justify-content: space-evenly;
|
||||
display: flex;*/
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.top-row a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
color: #dedede;
|
||||
background-color: #000000;
|
||||
height: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 13.5rem;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.sidebarSmall {
|
||||
width: 4.5rem;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.main > div {
|
||||
padding-left: 0.5rem !important;
|
||||
padding-right: 0.5rem !important;
|
||||
/*padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.page{position:relative;display:flex;flex-direction:column;}.main{flex:1;}.sidebar,.sidebarSmall{background-image:linear-gradient(180deg,#052767 20%,#3aa6ff 90%);}.top-row{background-color:#f7f7f7;border-bottom:1px solid #d6d5d5;height:3.5rem;align-items:center;}.top-row ::deep a,.top-row .btn-link{white-space:nowrap;margin-left:1.5rem;}.top-row a:first-child{overflow:hidden;text-overflow:ellipsis;}.bottom-row{color:#dedede;background-color:#000;height:2rem;align-items:center;}@media(min-width:992px){.page{flex-direction:row;}.sidebar{width:250px;height:100vh;position:sticky;top:0;}.sidebarSmall{width:80px;height:100vh;position:sticky;top:0;}.top-row{position:sticky;top:0;z-index:1;}.bottom-row{position:fixed;bottom:0;z-index:1;}.main>div{padding-left:.5rem!important;padding-right:.5rem!important;}}
|
||||
@@ -0,0 +1,83 @@
|
||||
<div class="top-row ps-2 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
@if (!showText)
|
||||
{
|
||||
<a class="navbar-brand p-0" @onclick="() => ToggleCompress()">MP <i class="fas fa-caret-square-right"></i></a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="navbar-brand" @onclick="() => ToggleCompress()">MP.IOC <i class="fas fa-caret-square-left"></i></a>
|
||||
}
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
|
||||
<nav class="flex-column">
|
||||
@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="Index" Match="NavLinkMatch.All">
|
||||
<span class="fas fa-home fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">Home</span>
|
||||
}
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-2">
|
||||
<NavLink class="nav-link py-0 px-2 mb-0" href="callstats">
|
||||
<span class="fas fa-chart-pie fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">Call Stats</span>
|
||||
}
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-2">
|
||||
<NavLink class="nav-link py-0 px-2 mb-0" href="RouteConf">
|
||||
<span class="fas fa-computer fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">Route Conf</span>
|
||||
}
|
||||
</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 fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">About</span>
|
||||
}
|
||||
</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 fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">Contacts</span>
|
||||
}
|
||||
</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 fs-4 pe-2" aria-hidden="true"></span>
|
||||
@if (showText)
|
||||
{
|
||||
<span class="@hideText">Refresh Data</span>
|
||||
}
|
||||
</NavLink>
|
||||
</div> *@
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using MP.AppAuth.Models;
|
||||
|
||||
namespace MP.IOC.Components.Layout
|
||||
{
|
||||
public partial class NavMenu
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<bool> EC_compressUpdated { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
[Inject]
|
||||
protected AuthenticationStateProvider AuthStateProvider { get; set; } = null!;
|
||||
|
||||
protected string hideText { get => showText ? "" : "invisible"; }
|
||||
|
||||
[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 bool showText { get; set; } = true;
|
||||
|
||||
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 override async Task OnInitializedAsync()
|
||||
{
|
||||
await ReloadData();
|
||||
}
|
||||
|
||||
protected void ToggleCompress()
|
||||
{
|
||||
showText = !showText;
|
||||
EC_compressUpdated.InvokeAsync(showText);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private bool onlyIcon = false;
|
||||
private string SafePages = "";
|
||||
private string userName = "";
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
[Inject]
|
||||
private IConfiguration ConfMan { get; set; } = null!;
|
||||
|
||||
private bool isLoading { get; set; } = false;
|
||||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private string? TextCss => onlyIcon ? "d-none" : "";
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task ReloadData()
|
||||
{
|
||||
isLoading = true;
|
||||
// sistemo elenco pagine safe...
|
||||
SafePages = ConfMan.GetValue<string>("ServerConf: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.";
|
||||
}
|
||||
isLoading = false;
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.oi {
|
||||
width: 2rem;
|
||||
font-size: 1.1rem;
|
||||
vertical-align: text-top;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.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 a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.25);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@page "/about"
|
||||
<h3>About</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
@page "/callstats"
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h3>Current Call Stats (24h)</h3>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<div class="row mb-2">
|
||||
<div class="col-3">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="px-0 fw-bold">Avail Data</div>
|
||||
<div class="px-0">
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoReset()">
|
||||
<i class="fa-solid fa-arrow-rotate-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<ul class="list-group shadow">
|
||||
@foreach (var item in ParetoDay)
|
||||
{
|
||||
<li class="list-group-item @CheckSelect(item.Key) d-flex justify-content-between align-items-start small py-1">
|
||||
<span class="align-items-center">@item.Key</span>
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoSelect(item.Key)">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (string.IsNullOrEmpty(currHistId))
|
||||
{
|
||||
<div class="col-9">
|
||||
<div class="alert alert-info">selezionare la metrica</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-6">
|
||||
<BarPlot Id="@currHistId" AspRatio="2" Data="@DatiPareto" Labels="@LabelPareto" Legenda="@currTitle" lineColor="@lineColors" backColor="@bgColors"></BarPlot>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<PieChart Id="@currPieId" AspRatio="1" LegendPos="none" Data="@DatiPareto" Labels="@LabelPareto" lineColor="@lineColors" backColor="@bgColors" Title="@currTitle"></PieChart>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(currHistId))
|
||||
{
|
||||
<div class="row mb-2">
|
||||
<div class="col-3">
|
||||
<ParetoDetail Title="@currHistId" ParetoList="@currData" EC_Selected="ShowDetail"></ParetoDetail>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
@if (string.IsNullOrEmpty(currDetail))
|
||||
{
|
||||
<div class="alert alert-info">← selezionare valore</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<b>@currDetail</b>
|
||||
if (tsDataDetail.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info">← No data found</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MultiLine Id="@currTsId" AspRatio="4" DataTSList="@TSDataMulti" Labels="@LabelPlot" lineColor="@lineColorsMLine" backColor="@bgColorsMLine" lTens="0" Titles="@lineTitles" Stepped="false" yScale="logarithmic"></MultiLine>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,269 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using MP.Data.DTO;
|
||||
using MP.Data.Services.Utils;
|
||||
|
||||
namespace MP.IOC.Components.Pages
|
||||
{
|
||||
public partial class CallStats
|
||||
{
|
||||
#region Protected Methods
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await ReloadData();
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private List<StatDataDTO> currData = new();
|
||||
|
||||
private string currDetail = "";
|
||||
|
||||
private string currHistId = "";
|
||||
|
||||
private string currId = "";
|
||||
|
||||
private string currPieId = "";
|
||||
|
||||
private string currTitle = "";
|
||||
|
||||
private string currTsId = "";
|
||||
|
||||
private List<string> lineTitles = new List<string>();
|
||||
|
||||
private Dictionary<string, List<StatDataDTO>> ParetoDay = new();
|
||||
|
||||
private List<chartJsData.chartJsTSerie> tsData = new List<chartJsData.chartJsTSerie>();
|
||||
|
||||
private List<ChartSeriesDto> tsDataDetail = new();
|
||||
|
||||
private List<List<chartJsData.chartJsTSerie>> TSDataMulti = new();
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<string> bgColors
|
||||
{
|
||||
get => GetSemaforicColors(currData.Count, "0.3");
|
||||
}
|
||||
|
||||
private List<string> bgColorsMLine
|
||||
{
|
||||
get => GetDistinctColors(tsDataDetail.Count, "0.3");
|
||||
}
|
||||
|
||||
private List<double> DatiPareto
|
||||
{
|
||||
get => currData.Select(x => x.Value).ToList();
|
||||
}
|
||||
|
||||
private List<string> LabelPareto
|
||||
{
|
||||
get => currData.Select(x => x.Label).ToList();
|
||||
}
|
||||
|
||||
private List<string> LabelPlot
|
||||
{
|
||||
get => tsData.Select(r => $"{r.x:yyyy-MM-dd}").ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<string> lineColors
|
||||
{
|
||||
get => GetSemaforicColors(currData.Count, "1");
|
||||
}
|
||||
|
||||
private List<string> lineColorsMLine
|
||||
{
|
||||
get => GetDistinctColors(tsDataDetail.Count, "1");
|
||||
}
|
||||
|
||||
[Inject]
|
||||
private IStatsDetailService SDetService { get; set; } = null!;
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private string CheckSelect(string curKey)
|
||||
{
|
||||
return !string.IsNullOrEmpty(currId) && currId == curKey ? "active" : "";
|
||||
}
|
||||
|
||||
private void DoReset()
|
||||
{
|
||||
currId = "";
|
||||
currHistId = "";
|
||||
currPieId = "";
|
||||
currTsId = "";
|
||||
currData = new();
|
||||
}
|
||||
|
||||
private void DoSelect(string reqKey)
|
||||
{
|
||||
if (ParetoDay.ContainsKey(reqKey))
|
||||
{
|
||||
currId = reqKey;
|
||||
currHistId = $"Bar_{reqKey}";
|
||||
currPieId = $"Pie_{reqKey}";
|
||||
currTsId = $"TS_{reqKey}";
|
||||
currTitle = $"Pareto | {reqKey}";
|
||||
currData = ParetoDay[reqKey];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReloadData()
|
||||
{
|
||||
ParetoDay = await SDetService.GetParetoStatsDayAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Genera colori sfondo 33% rosso / arancione / giallo
|
||||
/// </summary>
|
||||
/// <param name="numRecords"></param>
|
||||
/// <param name="alpha"></param>
|
||||
/// <returns></returns>
|
||||
private List<string> semaphColors(int numRecords, string alpha)
|
||||
{
|
||||
List<string> answ = new List<string>();
|
||||
// verde...
|
||||
for (int i = 0; i < numRecords / 3; i++)
|
||||
{
|
||||
answ.Add($"rgba(54, 235, 82, {alpha})");
|
||||
}
|
||||
// arancione
|
||||
for (int i = 0; i < numRecords / 3; i++)
|
||||
{
|
||||
answ.Add($"rgba(255, 206, 86, {alpha})");
|
||||
}
|
||||
while (answ.Count < numRecords)
|
||||
{
|
||||
answ.Add($"rgba(255, 99, 132, {alpha}");
|
||||
}
|
||||
|
||||
return answ;
|
||||
}
|
||||
|
||||
private List<string> GetSemaforicColors(int numRecords, string alpha)
|
||||
{
|
||||
List<string> colors = new List<string>();
|
||||
if (numRecords <= 0) return colors;
|
||||
if (numRecords == 1) { colors.Add($"rgba(54, 235, 82, {alpha})"); return colors; }
|
||||
|
||||
// Definiamo i punti chiave (R, G, B)
|
||||
(int r, int g, int b) green = (54, 235, 82);
|
||||
(int r, int g, int b) yellow = (255, 206, 86);
|
||||
(int r, int g, int b) red = (255, 99, 132);
|
||||
|
||||
for (int i = 0; i < numRecords; i++)
|
||||
{
|
||||
// t va da 0.0 (primo record) a 1.0 (ultimo record)
|
||||
double t = (double)i / (numRecords - 1);
|
||||
int r, g, b;
|
||||
|
||||
if (t < 0.5)
|
||||
{
|
||||
// Da Verde a Giallo (mappiamo 0->0.5 su 0->1)
|
||||
double localT = t * 2;
|
||||
r = (int)(green.r + (yellow.r - green.r) * localT);
|
||||
g = (int)(green.g + (yellow.g - green.g) * localT);
|
||||
b = (int)(green.b + (yellow.b - green.b) * localT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Da Giallo a Rosso (mappiamo 0.5->1 su 0->1)
|
||||
double localT = (t - 0.5) * 2;
|
||||
r = (int)(yellow.r + (red.r - yellow.r) * localT);
|
||||
g = (int)(yellow.g + (red.g - yellow.g) * localT);
|
||||
b = (int)(yellow.b + (red.b - yellow.b) * localT);
|
||||
}
|
||||
|
||||
colors.Add($"rgba({r}, {g}, {b}, {alpha})");
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
//private List<string> GetDistinctColors(int numRecords, string alpha)
|
||||
//{
|
||||
// List<string> colors = new List<string>();
|
||||
|
||||
// for (int i = 0; i < numRecords; i++)
|
||||
// {
|
||||
// // Distribuiamo la tonalità (Hue) uniformemente sui 360 gradi
|
||||
// double hue = (double)i * 360 / numRecords;
|
||||
|
||||
// // Usiamo il formato CSS hsla() che è più semplice da generare direttamente
|
||||
// // Saturazione 70% e Luminosità 50-60% di solito danno colori vivaci e distinti
|
||||
// colors.Add($"hsla({hue:0.##}, 70%, 50%, {alpha})");
|
||||
// }
|
||||
|
||||
// return colors;
|
||||
//}
|
||||
private List<string> GetDistinctColors(int numRecords, string alpha)
|
||||
{
|
||||
List<string> colors = new List<string>();
|
||||
if (numRecords <= 0) return colors;
|
||||
|
||||
// Partiamo dal Blu (240°) invece che dal Rosso (0°)
|
||||
double startHue = 240.0;
|
||||
|
||||
for (int i = 0; i < numRecords; i++)
|
||||
{
|
||||
// Distribuiamo la tonalità aggiungendo l'offset iniziale
|
||||
// L'operatore % 360 assicura di rimanere nel cerchio se superiamo il rosso
|
||||
double hue = (startHue + ((double)i * 360 / numRecords)) % 360;
|
||||
|
||||
// Strategia di distinzione (attiva sempre, ma più efficace sopra i 5 colori)
|
||||
// Alterniamo i valori per i record pari/dispari
|
||||
string saturation = "70%";
|
||||
string lightness = "50%";
|
||||
|
||||
if (numRecords > 5)
|
||||
{
|
||||
// Se i colori sono molti, alterniamo luminosità e saturazione
|
||||
// I record dispari saranno più chiari e saturi, i pari più scuri e tenui
|
||||
lightness = (i % 2 == 0) ? "45%" : "65%";
|
||||
saturation = (i % 2 == 0) ? "80%" : "60%";
|
||||
}
|
||||
|
||||
colors.Add($"hsla({hue:0.##}, {saturation}, {lightness}, {alpha})");
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// abilita visualizzaione grafico dettagli x metodo indicato
|
||||
/// </summary>
|
||||
/// <param name="selDetail"></param>
|
||||
private async Task ShowDetail(string selDetail)
|
||||
{
|
||||
currDetail = selDetail;
|
||||
// recupero dettaglio 7gg...
|
||||
DateTime adesso = DateTime.Now;
|
||||
List<StatsDetailModel> rawData = await SDetService.GetFiltAsync(adesso.AddDays(-5), adesso, "", selDetail);
|
||||
// conversione con grouping
|
||||
tsData = rawData.Select(r => new chartJsData.chartJsTSerie() { x = r.Hour, y = (double)r.AvgDuration })
|
||||
.OrderBy(o => o.x)
|
||||
.ToList();
|
||||
tsDataDetail = SDetService.GetTimeSeriesData(rawData);
|
||||
lineTitles = tsDataDetail.Select(x => x.SeriesName).ToList();
|
||||
TSDataMulti = tsDataDetail.Select(x => x.DataPoints).ToList();
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@page "/contacts"
|
||||
|
||||
<h3>Contacts</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
@page "/"
|
||||
@page "/Home"
|
||||
@page "/Index"
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-dark text-light">
|
||||
<div class="row">
|
||||
<div class="col-3 p-2">
|
||||
<img src="images/LogoMapo.png" class="image-fluid" height="64" />
|
||||
</div>
|
||||
<div class="col-6 p-2 text-center align-content-center">
|
||||
<div class="fs-1 fw-bold">MAPO MES</div>
|
||||
<div class="fs-6 text-secondary">Rest API target</div>
|
||||
</div>
|
||||
<div class="col-3 text-end p-2">
|
||||
<img src="images/LogoEgw.png" class="image-fluid" height="64" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>Alive</b>
|
||||
</div>
|
||||
<div class="text-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>IOB-PI</b> I/O
|
||||
</div>
|
||||
<div class="text-danger">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>IOB-WIN</b> I/O
|
||||
</div>
|
||||
<div class="text-danger">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>File</b> upload
|
||||
</div>
|
||||
<div class="text-danger">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6 text-center">
|
||||
<h1>MP-IOC</h1>
|
||||
<h4>.net Core 8.0</h4>
|
||||
</div>
|
||||
<div class="col-3 text-end">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>Bench</b> test
|
||||
</div>
|
||||
<div class="text-warning">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>Parameters</b> management
|
||||
</div>
|
||||
<div class="text-danger">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<b>Recipe</b> management
|
||||
</div>
|
||||
<div class="text-success">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-2">
|
||||
<div class="col-3">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active d-flex justify-content-between align-items-start">
|
||||
<b>Top Hourly Call</b>
|
||||
</li>
|
||||
@foreach (var item in ListParetoCall)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start small py-1">
|
||||
<div>
|
||||
@item.Destination | <b>@item.Type</b>
|
||||
</div>
|
||||
<div class="">
|
||||
<b>@($"{item.RequestCount:N0}")</b>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6 text-center">
|
||||
<div class="fs-3">IOC Conversion rate</div>
|
||||
<div class="row">
|
||||
@foreach (var item in paretoWeek)
|
||||
{
|
||||
<div class="col-6">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active d-flex justify-content-between align-items-start">
|
||||
<b>@item.Key</b>
|
||||
</li>
|
||||
@foreach (var itemDet in item.Value)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start small py-1">
|
||||
<div>
|
||||
<b>@itemDet.Label</b>
|
||||
</div>
|
||||
<div class="">
|
||||
<b>@($"{itemDet.Value:N0}")</b>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active d-flex justify-content-between align-items-start">
|
||||
<b>Call Processed</b>
|
||||
</li>
|
||||
@foreach (var item in ListGlobalCall)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start small py-1">
|
||||
<div>
|
||||
@($"{item.Hour:yyyy-MM-dd}") | <b>@item.Destination</b>
|
||||
</div>
|
||||
<div class="">
|
||||
<b>@($"{item.RequestCount:N0}")</b>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MP.Core.DTO;
|
||||
using MP.Data.DbModels.Utils;
|
||||
using MP.Data.Services.Utils;
|
||||
|
||||
namespace MP.IOC.Components.Pages
|
||||
{
|
||||
public partial class Index
|
||||
{
|
||||
#region Protected Methods
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await ReloadData();
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private List<StatsAggregatedModel> ListGlobalCall = new();
|
||||
|
||||
private List<StatsDetailModel> ListParetoCall = new();
|
||||
|
||||
private Dictionary<string, List<StatDataDTO>> paretoWeek = new();
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
[Inject]
|
||||
private IStatsAggrService StatsAggrService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IStatsDetailService StatsDetService { get; set; } = null!;
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task ReloadData()
|
||||
{
|
||||
DateTime oggi = DateTime.Today;
|
||||
DateTime adesso = DateTime.Now;
|
||||
var rawData = await StatsAggrService.GetFiltAsync(oggi.AddDays(-5), oggi);
|
||||
ListGlobalCall = rawData.OrderByDescending(x => x.Hour).ToList();
|
||||
ListParetoCall = await StatsDetService.GetParetoAsync(adesso.AddHours(-1), adesso, 10);
|
||||
paretoWeek = await StatsAggrService.GetParetoStatsWeekAsync();
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@page "/RefreshData"
|
||||
|
||||
<h3>RefreshData</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
@page "/RouteConf"
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="px-0">
|
||||
<h3>Route Configuration (IO/IOC)</h3>
|
||||
</div>
|
||||
<div class="px-0">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-magnifying-glass"></i></span>
|
||||
<input type="text" class="form-control" placeholder="Ricerca: alt-/" accesskey="/" @bind="SearchVal" />
|
||||
<button class="btn btn-sm @CssReset" @onclick="ResetSearch"><i class="fa-solid fa-rotate-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<button class="btn btn-sm btn-primary" title="Reset selezione" @onclick="DoReset"><i class="fa-solid fa-arrow-rotate-right"></i></button>
|
||||
</th>
|
||||
<th>Metodo</th>
|
||||
<th class="text-start">IO %</th>
|
||||
<th class="text-center">Balance</th>
|
||||
<th class="text-end">IOC %</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var record in ListPaged)
|
||||
{
|
||||
<tr class="@CheckSelect(record)">
|
||||
<td>
|
||||
@if (SelRecord == null)
|
||||
{
|
||||
<button class="btn btn-sm btn-info" @onclick="() => DoEdit(record)"><i class="fa-solid fa-pencil"></i></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SelRecord.Method == record.Method)
|
||||
{
|
||||
<button class="btn btn-sm btn-success" @onclick="() => DoSave(record)"><i class="fa-solid fa-floppy-disk"></i></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-sm btn-secondary disabled"><i class="fa-solid fa-pencil"></i></button>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td>@record.Method</td>
|
||||
<td class="text-start">
|
||||
<button class="btn btn-sm btn-warning" @onclick="() => SetNewWeight(record, 0)" title="Set 100% IO"><i class="fa-solid fa-scale-unbalanced"></i></button>
|
||||
@record.OldWeight
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-warning" @onclick="() => SetNewWeight(record, record.NewWeight - 10)" title="Seto 50%">+10%</button>
|
||||
<button class="btn btn-sm btn-success" @onclick="() => SetNewWeight(record, 50)" title="Seto 50%"><i class="fa-solid fa-scale-balanced"></i></button>
|
||||
<button class="btn btn-sm btn-info" @onclick="() => SetNewWeight(record, record.NewWeight + 10)" title="Seto 50%">+10%</button>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@record.NewWeight
|
||||
<button class="btn btn-sm btn-info" @onclick="() => SetNewWeight(record, 100)" title="Set 100% IOC"><i class="fa-solid fa-scale-unbalanced-flip"></i></button>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@if (SelRecord != null && SelRecord.Method == record.Method)
|
||||
{
|
||||
<button class="btn btn-sm btn-warning" @onclick="DoReset"><i class="fa-solid fa-ban"></i></button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="10">
|
||||
<EgwCoreLib.Razor.DataPager currPage="@pageNum" PageSize="@numRecPage" totalCount="@totalCount" numPageChanged="SavePage" numRecordChanged="SaveNumRec" DisplSize="DataPager.ObjSize.small"></EgwCoreLib.Razor.DataPager>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,166 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MP.Core.DTO;
|
||||
using MP.IOC.Services;
|
||||
|
||||
namespace MP.IOC.Components.Pages
|
||||
{
|
||||
public partial class RouteConf
|
||||
{
|
||||
#region Protected Properties
|
||||
|
||||
protected string CssReset
|
||||
{
|
||||
get => string.IsNullOrEmpty(SearchVal) ? "btn-outline-secondary" : "btn-primary";
|
||||
}
|
||||
|
||||
protected string SearchVal
|
||||
{
|
||||
get => _searchVal;
|
||||
set
|
||||
{
|
||||
if (_searchVal != value)
|
||||
{
|
||||
_searchVal = value;
|
||||
UpdateTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Protected Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected string CheckSelect(WeightDTO currRec)
|
||||
{
|
||||
return SelRecord != null && SelRecord.Method == currRec.Method ? "table-info" : "";
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
//return base.OnInitializedAsync();
|
||||
await ReloadData();
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
protected void ResetSearch()
|
||||
{
|
||||
SearchVal = "";
|
||||
//UpdateTable();
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private string _searchVal = "";
|
||||
|
||||
private List<WeightDTO> ListComplete = new();
|
||||
|
||||
private List<WeightDTO> ListPaged = new();
|
||||
|
||||
private List<WeightDTO> ListSearch = new();
|
||||
|
||||
private int numRecPage = 10;
|
||||
|
||||
private int pageNum = 1;
|
||||
|
||||
private WeightDTO? SelRecord = null;
|
||||
|
||||
private int totalCount = 0;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
[Inject]
|
||||
private IWeightProvider WService { get; set; } = null!;
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Edit record selezionato
|
||||
/// </summary>
|
||||
/// <param name="curRec"></param>
|
||||
private void DoEdit(WeightDTO curRec)
|
||||
{
|
||||
SelRecord = curRec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imposta il peso del metodo "New" come richiesto, con eventuale check sui limiti 0-100
|
||||
/// </summary>
|
||||
/// <param name="curRec"></param>
|
||||
/// <param name="newWeight"></param>
|
||||
private async Task SetNewWeight(WeightDTO curRec, int newWeight)
|
||||
{
|
||||
newWeight = Math.Clamp(newWeight, 0, 100);
|
||||
curRec.NewWeight = newWeight;
|
||||
curRec.OldWeight = 100 - newWeight;
|
||||
// salvo!
|
||||
WService.UpsertWeight(curRec);
|
||||
// rileggo!
|
||||
await ReloadData();
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void DoReset()
|
||||
{
|
||||
SelRecord = null;
|
||||
}
|
||||
|
||||
private void DoSave(WeightDTO updRec)
|
||||
{
|
||||
// salvo e resetto...
|
||||
|
||||
SelRecord = null;
|
||||
}
|
||||
|
||||
private async Task ReloadData()
|
||||
{
|
||||
ListComplete = await WService.GetAllWeightsAsync();
|
||||
}
|
||||
|
||||
private void SaveNumRec(int newNum)
|
||||
{
|
||||
numRecPage = newNum;
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
private void SavePage(int newNum)
|
||||
{
|
||||
pageNum = newNum;
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
private void UpdateTable()
|
||||
{
|
||||
// effettuo eventuale ricerca + conteggio...
|
||||
if (string.IsNullOrEmpty(SearchVal))
|
||||
{
|
||||
ListSearch = ListComplete;
|
||||
}
|
||||
else
|
||||
{
|
||||
ListSearch = ListComplete
|
||||
.Where(x => x.Method.Contains(SearchVal, StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
totalCount = ListSearch.Count();
|
||||
// esegue paginazione
|
||||
if (totalCount > numRecPage)
|
||||
{
|
||||
ListPaged = ListSearch.Skip((pageNum - 1) * numRecPage).Take(numRecPage).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
ListPaged = ListSearch;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,16 @@
|
||||
@using EgwCoreLib.Razor
|
||||
@using EgwCoreLib.Utils
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using MP.Data
|
||||
@using MP.Data.Repository
|
||||
@using MP.Data.Services
|
||||
@using MP.IOC.Components
|
||||
@using MP.IOC.Components.Compo
|
||||
@using MP.IOC.Components.ChartJS
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@@ -3,6 +3,7 @@ using MP.Data;
|
||||
using MP.IOC.Data;
|
||||
using NLog;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MP.IOC.Controllers
|
||||
{
|
||||
@@ -29,7 +30,9 @@ namespace MP.IOC.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("CountKeys")]
|
||||
[HttpGet]
|
||||
[Route("CountKeys")]
|
||||
[Route("CNTKEY")]
|
||||
public string CountKeys(string? id)
|
||||
{
|
||||
string answ = "ND";
|
||||
@@ -74,7 +77,8 @@ namespace MP.IOC.Controllers
|
||||
answ = "";
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> valori = DService.ResetDatiMacchina(id);
|
||||
Dictionary<string, string> valori = DService.mDatiMacchine(id);
|
||||
//Dictionary<string, string> valori = DService.ResetDatiMacchina(id);
|
||||
foreach (var item in valori)
|
||||
{
|
||||
answ += $"{item.Key}|{item.Value}{Environment.NewLine}";
|
||||
@@ -117,7 +121,7 @@ namespace MP.IOC.Controllers
|
||||
answ = "";
|
||||
try
|
||||
{
|
||||
var fiHASH = Utils.hSMI(idxFamIn);
|
||||
var fiHASH = Utils.GetHashSMI(idxFamIn);
|
||||
string outVal = "";
|
||||
bool trovato = DService.RedisHashPresent(fiHASH);
|
||||
if (!trovato)
|
||||
@@ -356,6 +360,17 @@ namespace MP.IOC.Controllers
|
||||
return answ;
|
||||
}
|
||||
|
||||
[HttpGet("version")]
|
||||
public string version()
|
||||
{
|
||||
var version = Assembly
|
||||
.GetExecutingAssembly()
|
||||
.GetName()
|
||||
.Version?
|
||||
.ToString() ?? "unknown";
|
||||
|
||||
return version;
|
||||
}
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
+1119
-2171
File diff suppressed because it is too large
Load Diff
+2210
-123
File diff suppressed because it is too large
Load Diff
+15
-1
@@ -1,11 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>6.16.2604.1517</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="compilerconfig.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IIS01.pubxml" />
|
||||
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IIS03.pubxml" />
|
||||
@@ -14,6 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="compilerconfig.json" />
|
||||
<None Include="Properties\PublishProfiles\IIS01.pubxml.user" />
|
||||
<None Include="Properties\PublishProfiles\IIS03.pubxml.user" />
|
||||
<None Include="Properties\PublishProfiles\IIS04.pubxml.user" />
|
||||
@@ -22,9 +28,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EgwCoreLib.Razor" Version="1.5.2511.312" />
|
||||
<PackageReference Include="EgwCoreLib.Utils" Version="1.5.2511.312" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="8.0.25" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -40,6 +50,10 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Components\Layout\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File $(ProjectDir)\post-build.ps1 -ProjectDir $(ProjectDir) -ProjectPath $(ProjectPath)" />
|
||||
</Target>
|
||||
|
||||
+159
-12
@@ -1,40 +1,179 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using MP.Data;
|
||||
using MP.Data.Repository.Utils;
|
||||
using MP.Data.Services.Utils;
|
||||
using MP.IOC.Components;
|
||||
using MP.IOC.Data;
|
||||
using MP.IOC.Services;
|
||||
using NLog;
|
||||
using NLog.Web;
|
||||
using StackExchange.Redis;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// recupero env corrente
|
||||
var env = builder.Environment;
|
||||
var logger = LogManager.Setup()
|
||||
.LoadConfigurationFromAppSettings()
|
||||
.GetCurrentClassLogger();
|
||||
logger.Info("Program.cs: startup");
|
||||
|
||||
// Add services to the container.
|
||||
var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
|
||||
logger.Info($"MP.IOC | Program.cs: startup | v.{assemblyVersion}");
|
||||
logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}");
|
||||
|
||||
// Config setup
|
||||
ConfigurationManager configuration = builder.Configuration;
|
||||
// REDIS setup
|
||||
logger.Info("Config OK");
|
||||
string connStringRedis = configuration.GetConnectionString("Redis");
|
||||
string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":"));
|
||||
logger.Info("Setup REDIS OK");
|
||||
|
||||
|
||||
// YARP base config
|
||||
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||||
builder.Services.AddHttpForwarder();
|
||||
|
||||
// HttpMessageInvoker (SocketsHttpHandler)
|
||||
builder.Services.AddSingleton(sp =>
|
||||
{
|
||||
var handler = new SocketsHttpHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
UseCookies = false,
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
|
||||
};
|
||||
|
||||
// Accetta certificati non validi (dev only)
|
||||
handler.SslOptions = new System.Net.Security.SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
|
||||
};
|
||||
|
||||
return new HttpMessageInvoker(handler);
|
||||
});
|
||||
logger.Info("YARP reverse proxy configured");
|
||||
|
||||
// base services
|
||||
builder.Services.AddSingleton<PreserveBodyTransformer>();
|
||||
builder.Services.AddSingleton<RouteStatsManager>();
|
||||
builder.Services.AddHostedService<MetricsCalcService>();
|
||||
builder.Services.AddHostedService<MetricsDbFlushService>();
|
||||
|
||||
// Registra i servizi per Blazor
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
// Aggiungi Health Checks
|
||||
//builder.Services.AddHealthChecks()
|
||||
// .AddSqlServer(builder.Configuration.GetConnectionString("CoreDb"));
|
||||
|
||||
// MP.Data DbContext for Stats repositories
|
||||
string utilsConnString = builder.Configuration.GetConnectionString("MP.Utils") ?? "Server=localhost;Database=MoonPro_Utils; integrated security=True; MultipleActiveResultSets=True; App=MP.IOC;";
|
||||
builder.Services.AddDbContextFactory<MoonPro_UtilsContext>(options =>
|
||||
options.UseSqlServer(utilsConnString));
|
||||
|
||||
// MP.Data Services Utils - Statistiche DB
|
||||
builder.Services.AddScoped<IStatsAggrRepository, StatsAggrRepository>();
|
||||
builder.Services.AddScoped<IStatsDetailRepository, StatsDetailRepository>();
|
||||
builder.Services.AddScoped<IStatsAggrService, StatsAggrService>();
|
||||
builder.Services.AddScoped<IStatsDetailService, StatsDetailService>();
|
||||
|
||||
// generic controller
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
//builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "EgaWare's Mapo Rest API",
|
||||
Version = $"v1 | {assemblyVersion}",
|
||||
Description = $"API documentation for v1 | AssemblyVersion {assemblyVersion}"
|
||||
});
|
||||
});
|
||||
|
||||
ConfigurationManager configuration = builder.Configuration;
|
||||
// REDIS setup
|
||||
logger.Info("Setup REDIS");
|
||||
string connStringRedis = configuration.GetConnectionString("Redis");
|
||||
string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":"));
|
||||
// avvio oggetto shared x redis...
|
||||
var redisMultiplexer = ConnectionMultiplexer.Connect(connStringRedis);
|
||||
builder.Services.AddSingleton<IConnectionMultiplexer>(redisMultiplexer);
|
||||
var redisMux = ConnectionMultiplexer.Connect(connStringRedis);
|
||||
builder.Services.AddSingleton<IConnectionMultiplexer>(redisMux);
|
||||
|
||||
// oggetto principale accesso dati
|
||||
builder.Services.AddSingleton<MpDataService>();
|
||||
|
||||
logger.Info("Standard service configured");
|
||||
|
||||
// WeightProvider: Redis/Memory da config
|
||||
var weightOnRedis = builder.Configuration.GetValue<bool>("ServerConf:RedisWeight", false);
|
||||
if (weightOnRedis)
|
||||
{
|
||||
builder.Services.AddSingleton<IConnectionMultiplexer>(redisMux);
|
||||
builder.Services.AddSingleton<IWeightProvider, RedisWeightProvider>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Services.AddSingleton<IWeightProvider, InMemoryWeightProvider>();
|
||||
}
|
||||
logger.Info($"Weight service configured | use Redis: {weightOnRedis}");
|
||||
|
||||
// RouteManager registration (singleton)
|
||||
builder.Services.AddSingleton<RouteManager>();
|
||||
logger.Info("Singleton Route Manager registered");
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// aggiunt base URL x routing corretto
|
||||
string baseUrl = configuration.GetValue<string>("ServerConf:BaseUrl") ?? "";
|
||||
app.UsePathBase(baseUrl);
|
||||
string routePath = configuration.GetValue<string>("ServerConf:RoutePath") ?? "/api/RIOB";
|
||||
string fullPath = $"{baseUrl}{routePath}".Replace("//", "/");
|
||||
logger.Info($"BaseUrl: {baseUrl}");
|
||||
|
||||
app.Use(async (ctx, next) =>
|
||||
{
|
||||
logger.Debug($"Incoming request PathBase='{ctx.Request.PathBase}' Path='{ctx.Request.Path}' RawTarget='{ctx.Request.GetEncodedUrl()}'");
|
||||
await next();
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Map route to RouteManager
|
||||
//app.Map("/MP/IOC/api/RIOB/{**catchAll}", async (HttpContext ctx) =>
|
||||
//{
|
||||
// var routeManager = ctx.RequestServices.GetRequiredService<RouteManager>();
|
||||
// await routeManager.HandleAsync(ctx);
|
||||
//});
|
||||
|
||||
// parametrizzare?!?!?
|
||||
app.MapWhen(ctx =>
|
||||
ctx.Request.Path.StartsWithSegments(fullPath, StringComparison.OrdinalIgnoreCase) ||
|
||||
ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase) ||
|
||||
(ctx.Request.PathBase.HasValue && ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase)),
|
||||
builder =>
|
||||
{
|
||||
builder.Run(async ctx =>
|
||||
{
|
||||
var routeManager = ctx.RequestServices.GetRequiredService<RouteManager>();
|
||||
await routeManager.HandleAsync(ctx);
|
||||
});
|
||||
});
|
||||
|
||||
logger.Info("App: route Mapped");
|
||||
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment() || app.Environment.IsStaging())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
//app.UseSwaggerUI();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint($"{baseUrl}swagger/v1/swagger.json", "EgalWare's MAPO API v1");
|
||||
});
|
||||
}
|
||||
logger.Info("Added swagger");
|
||||
|
||||
@@ -45,9 +184,17 @@ app.UseAuthorization();
|
||||
// aggiunta x index.html
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
// Mappatura delle API
|
||||
app.MapControllers();
|
||||
|
||||
// Mappatura della Dashboard Blazor
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
//app.MapHealthChecks("/health");
|
||||
|
||||
logger.Info("Run App");
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -22,7 +22,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<UserName>jenkins</UserName>
|
||||
<_SavePWD>true</_SavePWD>
|
||||
<_TargetId>IISWebDeploy</_TargetId>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PropertyGroup>
|
||||
<TimeStampOfAssociatedLegacyPublishXmlFile />
|
||||
<EncryptedPassword>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAdCoESbxryUqXde3FfEOCTwAAAAACAAAAAAADZgAAwAAAABAAAAAFrH1vByj45Qn06hO/OH6tAAAAAASAAACgAAAAEAAAAIUK5NiEpc4lc11Op6/CLx8YAAAAMecN12fzIN9e3E8R/nu0ATe2PAsMy7M8FAAAAKbUyki2vkSFehjbpB8wCVVVa055</EncryptedPassword>
|
||||
<History>True|2024-11-04T07:56:17.3071781Z||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||;</History>
|
||||
<History>True|2026-04-03T10:55:15.0251473Z||;True|2026-04-03T10:03:02.5833820+02:00||;True|2026-04-03T09:55:55.5274684+02:00||;True|2026-04-03T09:52:44.9063312+02:00||;False|2026-04-03T09:45:49.5943015+02:00||;True|2024-11-04T08:56:17.3071781+01:00||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -22,7 +22,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<UserName>jenkins</UserName>
|
||||
<_SavePWD>true</_SavePWD>
|
||||
<_TargetId>IISWebDeploy</_TargetId>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -22,7 +22,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<UserName>jenkins</UserName>
|
||||
<_SavePWD>true</_SavePWD>
|
||||
<_TargetId>IISWebDeploy</_TargetId>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -15,7 +15,7 @@ by editing this MSBuild file. In order to learn more about this please visit htt
|
||||
<DesktopBuildPackageLocation>bin\publish\MP.IOC.zip</DesktopBuildPackageLocation>
|
||||
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||
<DeployIisAppPath>Default Web Site/MP/IOC</DeployIisAppPath>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<SelfContained>false</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>Modulo MP-IOC </i>
|
||||
<h4>Versione: 6.16.2602.2510</h4>
|
||||
<h4>Versione: 6.16.2604.1517</h4>
|
||||
<br /> Note di rilascio:
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.16.2602.2510
|
||||
6.16.2604.1517
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>6.16.2602.2510</version>
|
||||
<version>6.16.2604.1517</version>
|
||||
<url>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip</url>
|
||||
<changelog>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
using MP.Data;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using StackExchange.Redis;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MP.IOC.Services
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Classe base per i servizi che fornisce funzionalità comuni come
|
||||
/// - connessione a Redis
|
||||
/// - configurazione
|
||||
/// - gestione dei messaggi
|
||||
/// - strategie di caching.
|
||||
///
|
||||
/// Questa classe agisce come modello per altri servizi derivati.
|
||||
/// </summary>
|
||||
public class BaseServ
|
||||
{
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Inizializza una nuova istanza della classe BaseServ.
|
||||
/// Configura la connessione Redis, carica le impostazioni di configurazione e inizializza il serializzatore JSON.
|
||||
/// </summary>
|
||||
/// <param name="Configuration">Oggetto di configurazione per recuperare le impostazioni dell'applicazione.</param>
|
||||
/// <param name="RedisConn">Multiplexer di connessione Redis per operazioni sul database.</param>
|
||||
public BaseServ(IConfiguration Configuration, IConnectionMultiplexer RedisConn)
|
||||
{
|
||||
_config = Configuration;
|
||||
_redisConn = RedisConn;
|
||||
_redisDb = _redisConn.GetDatabase();
|
||||
// configuro la base key x la cache Redis, con verifica contenga Cache finale
|
||||
_redisBaseKey = _config.GetValue<string>("ServerConf:RedisBaseKey") ?? "Lux:Cache";
|
||||
// aggiungo cache se non finisse per ":cache"
|
||||
if (!_redisBaseKey.EndsWith(":Cache"))
|
||||
{
|
||||
_redisBaseKey += ":Cache";
|
||||
}
|
||||
|
||||
// Configurazione serializzatore JSON per risolvere errore di loop circolare
|
||||
JSSettings = new JsonSerializerSettings()
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per la comunicazione riguardo update calcolo BOM
|
||||
/// </summary>
|
||||
public MessagePipe PipeBom { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno HwList da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelHwList.
|
||||
/// </summary>
|
||||
public MessagePipe PipeHwList { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno HwOptions calcolate da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelHwOpt.
|
||||
/// </summary>
|
||||
public MessagePipe PipeHwOpt { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno PNG calcolati da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelPng.
|
||||
/// </summary>
|
||||
public MessagePipe PipePng { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Canale informazioni relativo ad attività relative alla gesitone PROD:
|
||||
/// - carico macchine
|
||||
/// - scheduling
|
||||
/// </summary>
|
||||
public MessagePipe PipeProd { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno info elementi del profile
|
||||
/// </summary>
|
||||
public MessagePipe PipeProfElement { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno ProfileListAsync calcolate da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelProfList.
|
||||
/// </summary>
|
||||
public MessagePipe PipeProfList { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno Shape calcolate da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelShape.
|
||||
/// </summary>
|
||||
public MessagePipe PipeShape { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per ritorno SVG calcolati da Engine di calcolo verso interfaccia utente.
|
||||
/// I messaggi vengono inviati sul canale Redis definito da ChannelSvg.
|
||||
/// </summary>
|
||||
public MessagePipe PipeSvg { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Pipe dei messaggi per la comunicazione riguardo update generico UI
|
||||
/// </summary>
|
||||
public MessagePipe PipeUpdate { get; set; } = null!;
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto per collezione dati Activity (span in Uptrace)
|
||||
/// </summary>
|
||||
protected static readonly ActivitySource ActivitySource = new ActivitySource("Lux.DATA");
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto logger utilizzato per registrare eventi e errori a livello di classe.
|
||||
/// Utile per il monitoraggio del comportamento dell'applicazione e la risoluzione di problemi.
|
||||
/// </summary>
|
||||
protected static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto di configurazione statico per accedere alle impostazioni dell'applicazione (es. stringhe di connessione).
|
||||
/// Condiviso tra tutte le istanze di BaseServ.
|
||||
/// </summary>
|
||||
protected readonly IConfiguration _config = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Path base chiavi REDIS
|
||||
/// </summary>
|
||||
protected readonly string _redisBaseKey = "Lux:Cache";
|
||||
|
||||
/// <summary>
|
||||
/// Oggetto per la connessione a Redis utilizzato per operazioni di lettura/scrittura.
|
||||
/// </summary>
|
||||
protected readonly IConnectionMultiplexer _redisConn = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Database Redis utilizzato per le operazioni di lettura/scrittura
|
||||
/// nb: ottenuto tramite _redisConn.GetDatabase()
|
||||
/// </summary>
|
||||
protected readonly IDatabase _redisDb = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Abilitazione operazioni tracing generiche
|
||||
/// </summary>
|
||||
protected readonly bool _traceEnabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// Impostazioni del serializzatore JSON utilizzato per gestire oggetti con riferimenti circolari
|
||||
/// (es. oggetti che si fanno riferimento reciprocamente).
|
||||
/// </summary>
|
||||
protected JsonSerializerSettings? JSSettings;
|
||||
|
||||
#endregion Protected Fields
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache breve (circa 1 minuto + variazione del +/-10%)
|
||||
/// </summary>
|
||||
protected TimeSpan FastCache
|
||||
{
|
||||
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache lunga (+ variazione del +/-10%)
|
||||
/// </summary>
|
||||
protected TimeSpan LongCache
|
||||
{
|
||||
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache molto breve (circa 10 secondi + variazione del +/-10%)
|
||||
/// </summary>
|
||||
protected TimeSpan UltraFastCache
|
||||
{
|
||||
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache molto lunga (+ variazione del +/-10%)
|
||||
/// </summary>
|
||||
protected TimeSpan UltraLongCache
|
||||
{
|
||||
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
|
||||
}
|
||||
|
||||
#endregion Protected Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Helper avvio attività per la funzione tracciata
|
||||
/// </summary>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
protected static Activity? StartActivity([CallerMemberName] string? methodName = null)
|
||||
{
|
||||
var activity = ActivitySource.StartActivity(methodName ?? "UNDEF");
|
||||
activity?.SetTag("host.name", Environment.MachineName);
|
||||
return activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalida una o più chiavi/pattern in Redis
|
||||
/// </summary>
|
||||
protected async Task ClearCacheAsync(params string[] patterns)
|
||||
{
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
// Chiamata al tuo metodo esistente
|
||||
await ExecFlushRedisPatternAsync((RedisValue)pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metodo di flush dati cache Redis
|
||||
/// </summary>
|
||||
/// <param name="pattern"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task ExecFlushRedisPatternAsync(RedisValue pattern)
|
||||
{
|
||||
// Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan)
|
||||
// Esempio rapido via server scan:
|
||||
var endpoints = _redisConn.GetEndPoints();
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
var server = _redisConn.GetServer(endpoint);
|
||||
await foreach (var key in server.KeysAsync(_redisDb.Database, pattern))
|
||||
{
|
||||
await _redisDb.KeyDeleteAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="expiration"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task<T> GetOrSetCacheAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null)
|
||||
{
|
||||
using var activity = StartActivity();
|
||||
string source = "DB";
|
||||
|
||||
// 1. Provo Redis
|
||||
var cached = await _redisDb.StringGetAsync(key);
|
||||
if (cached.HasValue)
|
||||
{
|
||||
source = "REDIS";
|
||||
var cachedResult = JsonConvert.DeserializeObject<T>(cached!)!;
|
||||
|
||||
activity?.SetTag("data.source", source);
|
||||
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller);
|
||||
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
// 2. Chiamo il factory (DB)
|
||||
T result = await factory();
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
// 3. Salva in Redis per la prossima volta
|
||||
var serialized = JsonConvert.SerializeObject(result, JSSettings);
|
||||
await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache);
|
||||
}
|
||||
|
||||
// sistemo activity tracking data
|
||||
activity?.SetTag("data.source", source);
|
||||
activity?.Stop();
|
||||
|
||||
// log in console
|
||||
LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller);
|
||||
|
||||
return result!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper trace messaggio log (SE abilitato)
|
||||
/// </summary>
|
||||
/// <param name="traceMsg"></param>
|
||||
/// <param name="reqLevel"></param>
|
||||
/// <param name="methodName"></param>
|
||||
protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null)
|
||||
{
|
||||
if (!_traceEnabled)
|
||||
return;
|
||||
|
||||
reqLevel ??= NLog.LogLevel.Debug;
|
||||
|
||||
// Loggo!
|
||||
Log.Log(reqLevel, $"{methodName} | {traceMsg}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper generale per la telemetria e gestione eccezioni
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
protected async Task<T> TraceAsync<T>(string name, Func<Activity?, Task<T>> body, object? parameters = null)
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity(name);
|
||||
try
|
||||
{
|
||||
if (parameters != null)
|
||||
{
|
||||
activity?.SetTag("params", JsonConvert.SerializeObject(parameters));
|
||||
}
|
||||
var result = await body(activity);
|
||||
activity?.SetStatus(ActivityStatusCode.Ok);
|
||||
activity?.Stop();
|
||||
LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
|
||||
//Log.Error(ex, "Errore in {MethodName}", name);
|
||||
LogTrace($"Errore in {name}", NLog.LogLevel.Error, name);
|
||||
throw; // Riesponi l'eccezione per il tracking globale
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache lunga in secondi (predefinito: 5 minuti)
|
||||
/// Utilizzato nella proprietà LongCache per definire quanto a lungo i dati devono essere memorizzati in cache.
|
||||
/// </summary>
|
||||
private int cacheTtlLong = 60 * 5;
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache breve in secondi (predefinito: 1 minuto)
|
||||
/// Utilizzato nelle proprietà FastCache e UltraFastCache per definire la durata della cache breve.
|
||||
/// </summary>
|
||||
private int cacheTtlShort = 60 * 1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generatore di numeri casuali utilizzato per introdurre variabilità dinamica nelle durate della cache
|
||||
/// (simula variazioni reali nella freschezza dei dati e nei tempi di scadenza).
|
||||
/// </summary>
|
||||
private Random rnd = new Random();
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Methods
|
||||
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user