From 3f2b66d002b8db7814c96ababd21d8d0ac06a2ba Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 12 Dec 2025 18:51:53 +0100 Subject: [PATCH] Continuo integrazione metodi tracciamento RUID --- .../Services/CalcRuidService.cs | 312 ++++++++++++++++++ .../Services/CleanupService.cs | 60 ++++ .../Services/ReqIndexService.cs | 225 ------------- Lux.API/Controllers/GenericController.cs | 46 ++- Lux.API/Controllers/WindowController.cs | 101 +++--- Lux.API/Lux.API.csproj | 2 +- Lux.API/Program.cs | 12 + Lux.API/appsettings.json | 5 +- .../Components/Compo/Stats/PeriodStats.razor | 26 ++ .../Compo/Stats/RelatimeStats.razor | 26 ++ Lux.UI/Lux.UI.csproj | 2 +- Lux.UI/Program.cs | 12 + Lux.UI/appsettings.json | 5 +- Resources/ChangeLog.html | 2 +- Resources/VersNum.txt | 2 +- Resources/manifest.xml | 2 +- 16 files changed, 548 insertions(+), 292 deletions(-) create mode 100644 EgwCoreLib.Lux.Data/Services/CalcRuidService.cs create mode 100644 EgwCoreLib.Lux.Data/Services/CleanupService.cs delete mode 100644 EgwCoreLib.Lux.Data/Services/ReqIndexService.cs create mode 100644 Lux.UI/Components/Compo/Stats/PeriodStats.razor create mode 100644 Lux.UI/Components/Compo/Stats/RelatimeStats.razor diff --git a/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs new file mode 100644 index 00000000..797d7bc2 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs @@ -0,0 +1,312 @@ +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EgwCoreLib.Lux.Data.Services +{ + /// + /// Gestione servizio indice richieste + /// + public class CalcRuidService + { + #region Public Constructors + + public CalcRuidService(IConnectionMultiplexer redis, TimeSpan retention, string redisBaseKey) + { + _db = redis.GetDatabase(); + _retention = retention; + _base = redisBaseKey.TrimEnd(':'); + //_base = redisBaseKey.EndsWith(":") ? redisBaseKey : redisBaseKey + ":"; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Metodo Creazione nuova richiesta + /// + /// Environment calcolo + /// Tipologia richiesta + /// UID di riferimento + /// restituisce il valore del RUID (ID univoco richiesta) + public async Task AddRequestAsync(string envir, string tipo, string uid) + { + var ruid = GenerateRuid(); + var processStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + var hashKey = GetRequestKey(ruid); + var setKey = GetSortedSetKey(envir, tipo); + var uidKey = GetUidSetKey(uid); + var combKey = GetCombinationsKey(); + + var batch = _db.CreateBatch(); + + // Hash + await batch.HashSetAsync(hashKey, new HashEntry[] + { + new HashEntry("processStart", processStart), + new HashEntry("UID", uid), + new HashEntry("tipo", tipo), + new HashEntry("envir", envir) + }); + + // SortedSet + await batch.SortedSetAddAsync(setKey, ruid, processStart); + + // UID index + await batch.SetAddAsync(uidKey, ruid); + + // Registra combinazione envir|tipo + string comb = $"{envir}|{tipo}"; + await batch.SetAddAsync(combKey, comb); + + // Statistiche richieste + RedisKey minuteKey = Key($"{_base}:stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}"); + RedisKey hourKey = Key($"{_base}:stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}"); + + await batch.StringIncrementAsync(minuteKey); + await batch.StringIncrementAsync(hourKey); + + batch.Execute(); + + return ruid; + } + + /// + /// Metodo di Cleanup periodico + /// + /// Environment calcolo + /// Tipologia richiesta + /// + public async Task CleanupOldRequestsAsync(string environment, string tipo) + { + var cutoff = DateTimeOffset.UtcNow.Add(-_retention).ToUnixTimeMilliseconds(); + var setKey = GetSortedSetKey(environment, tipo); + + var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); + if (oldIds.Length == 0) return; + + var batch = _db.CreateBatch(); + + foreach (var id in oldIds) + { + var idStr = id.ToString(); + var hashKey = GetRequestKey(idStr); + + var uid = await _db.HashGetAsync(hashKey, "UID"); + if (!uid.IsNull) + { + var uidKey = GetUidSetKey(uid); + await batch.SetRemoveAsync(uidKey, idStr); + + await batch.SetLengthAsync(uidKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(uidKey); + }); + } + + await batch.KeyDeleteAsync(hashKey); + } + + await batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff); + + await batch.SortedSetLengthAsync(setKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(setKey); + }); + + batch.Execute(); + } + + /// + /// Metodo di Aggiornamento richiesta esistente + /// + /// RUID richiesta + /// + public async Task CompleteRequestAsync(string ruid) + { + var hashKey = GetRequestKey(ruid); + var processEnd = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + var processStartValue = await _db.HashGetAsync(hashKey, "processStart"); + if (processStartValue.IsNull) return; + + var processStart = (long)processStartValue; + var elapsed = (double)(processEnd - processStart) / 1000; // sono ms... divido per 1000 + + var batch = _db.CreateBatch(); + + await batch.HashSetAsync(hashKey, new HashEntry[] + { + new HashEntry("processEnd", processEnd), + new HashEntry("processElapsed", elapsed) + }); + + // Statistiche processing + RedisKey hourKeySum = Key($"{_base}:stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}"); + RedisKey hourKeyMax = Key($"{_base}:stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}"); + + await batch.StringIncrementAsync(hourKeySum, elapsed); + + // Max non atomico, ma sufficiente per statistiche + var currentMax = await _db.StringGetAsync(hourKeyMax); + if (currentMax.IsNull || long.Parse(currentMax) < elapsed) + await batch.StringSetAsync(hourKeyMax, elapsed); + + batch.Execute(); + } + + /// + /// Metodo Recupero combinazioni envir/tipo + /// + /// + public async Task> GetCombinationsAsync() + { + var members = await _db.SetMembersAsync(GetCombinationsKey()); + return members + .Select(x => x.ToString().Split('|')) + .Select(a => (a[0], a[1])); + } + + /// + /// Metodo Recupero richieste per UID + /// + /// + /// + public async Task> GetRequestsByUidAsync(string uid) + { + var uidKey = GetUidSetKey(uid); + var members = await _db.SetMembersAsync(uidKey); + return members.Select(x => x.ToString()); + } + + /// + /// Metodo Statistiche aggregate + /// + /// + public async Task GetStatsAsync() + { + RedisKey minuteKey = Key($"{_base}:stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}"); + RedisKey hourKey = Key($"{_base}:stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}"); + RedisKey hourSumKey = Key($"{_base}:stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}"); + RedisKey hourMaxKey = Key($"{_base}:stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}"); + + var minuteCount = await _db.StringGetAsync(minuteKey); + var hourCount = await _db.StringGetAsync(hourKey); + var sum = await _db.StringGetAsync(hourSumKey); + var max = await _db.StringGetAsync(hourMaxKey); + + long minCnt = minuteCount.IsNull ? 0 : (long)minuteCount; + long hourCnt = hourCount.IsNull ? 0 : (long)hourCount; + long sumVal = sum.IsNull ? 0 : (long)sum; + long maxVal = max.IsNull ? 0 : (long)max; + + double avg = hourCnt > 0 ? (double)sumVal / hourCnt : 0; + + return new + { + RequestsLastMinute = minCnt, + RequestsLastHour = hourCnt, + ProcessingAvgLastHour = avg, + ProcessingMaxLastHour = maxVal + }; + } + + /// + /// Restituisce le statistiche per range date + /// + /// + /// + /// + public async Task GetStatsRangeAsync(DateTime from, DateTime to) + { + var hourKeys = new List(); + var sumKeys = new List(); + var maxKeys = new List(); + + var cursor = from; + + while (cursor <= to) + { + RedisKey hourKey = Key($"{_base}:stats:requests:count:{cursor:yyyyMMddHH}"); + RedisKey sumKey = Key($"{_base}:stats:processing:sum:{cursor:yyyyMMddHH}"); + RedisKey maxKey = Key($"{_base}:stats:processing:max:{cursor:yyyyMMddHH}"); + + hourKeys.Add(hourKey); + sumKeys.Add(sumKey); + maxKeys.Add(maxKey); + + cursor = cursor.AddHours(1); + } + + var batch = _db.CreateBatch(); + + var hourTasks = hourKeys.Select(k => batch.StringGetAsync(k)).ToArray(); + var sumTasks = sumKeys.Select(k => batch.StringGetAsync(k)).ToArray(); + var maxTasks = maxKeys.Select(k => batch.StringGetAsync(k)).ToArray(); + + batch.Execute(); + + await Task.WhenAll(hourTasks); + await Task.WhenAll(sumTasks); + await Task.WhenAll(maxTasks); + + long totalRequests = hourTasks.Sum(t => t.Result.IsNull ? 0 : (long)t.Result); + long totalSum = sumTasks.Sum(t => t.Result.IsNull ? 0 : (long)t.Result); + long maxProcessing = maxTasks.Max(t => t.Result.IsNull ? 0 : (long)t.Result); + + double avgProcessing = totalRequests > 0 ? (double)totalSum / totalRequests : 0; + + return new + { + From = from, + To = to, + TotalRequests = totalRequests, + AvgProcessing = avgProcessing, + MaxProcessing = maxProcessing + }; + } + + #endregion Public Methods + + #region Private Fields + + private readonly string _base; + private readonly IDatabase _db; + private readonly TimeSpan _retention; + private readonly Random _rnd = new Random(); + + #endregion Private Fields + + #region Private Methods + + /// + /// Generatore RUID: + /// ID incrementale = timestamp ms + random 4 chars HEX + /// + /// + private string GenerateRuid() + { + long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string rand = Convert.ToString(_rnd.Next(0x1000, 0xFFFF), 16).ToUpper(); + return $"{ts}-{rand}"; + } + + private RedisKey GetCombinationsKey() => Key($"{_base}:requests:combinations"); + + private RedisKey GetRequestKey(string id) => Key($"{_base}:requests:{id}"); + + private RedisKey GetSortedSetKey(string env, string tipo) => Key($"{_base}:requests:{env}:{tipo}"); + + private RedisKey GetUidSetKey(string uid) => Key($"{_base}:uid:{uid}:requests"); + + private RedisKey Key(string suffix) => (RedisKey)(_base + suffix); + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/CleanupService.cs b/EgwCoreLib.Lux.Data/Services/CleanupService.cs new file mode 100644 index 00000000..4d0b4f42 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/CleanupService.cs @@ -0,0 +1,60 @@ +using EgwCoreLib.Lux.Data.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace EgwCoreLib.Lux.Data.Services +{ + public class CleanupService : BackgroundService + { + #region Public Constructors + + /// + /// Init classe con il servizio di gestione RUID + /// + /// + public CleanupService(IConfiguration config, CalcRuidService crService) + { + _config = config; + try + { + reqPeriodMinute = _config.GetValue("ServerConf:CleanupPeriodMinutes"); + } + catch + { + reqPeriodMinute = 60; + } + _calcRuidService = crService; + } + + #endregion Public Constructors + + #region Protected Methods + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var combos = await _calcRuidService.GetCombinationsAsync(); + + foreach (var (env, tipo) in combos) + await _calcRuidService.CleanupOldRequestsAsync(env, tipo); + + // attesa tra le esecuzioni + await Task.Delay(TimeSpan.FromMinutes(reqPeriodMinute), stoppingToken); + } + } + + #endregion Protected Methods + + #region Private Fields + + private readonly CalcRuidService _calcRuidService; + private IConfiguration _config; + private int reqPeriodMinute = 60; + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/ReqIndexService.cs b/EgwCoreLib.Lux.Data/Services/ReqIndexService.cs deleted file mode 100644 index 06ad7f2a..00000000 --- a/EgwCoreLib.Lux.Data/Services/ReqIndexService.cs +++ /dev/null @@ -1,225 +0,0 @@ -using StackExchange.Redis; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace EgwCoreLib.Lux.Data.Services -{ - - /// - /// Gestione servizio indice richieste - /// - public class ReqIndexService - { - private readonly IDatabase _db; - private readonly TimeSpan _retention; - private readonly Random _rnd = new Random(); - private readonly string _base; - - public ReqIndexService(IConnectionMultiplexer redis, TimeSpan retention, string redisBaseKey) - { - _db = redis.GetDatabase(); - _retention = retention; - _base = redisBaseKey.EndsWith(":") ? redisBaseKey : redisBaseKey + ":"; - } - - private string Key(string suffix) => _base + suffix; - - private string GetRequestKey(string id) => Key($"request:{id}"); - private string GetSortedSetKey(string env, string tipo) => Key($"requests:{env}:{tipo}"); - private string GetUidSetKey(string uid) => Key($"uid:{uid}:requests"); - private string GetCombinationsKey() => Key("req:combinations"); - - // --------------------------------------------------------- - // ✅ ID incrementale: timestamp ms + random 4-6 chars - // --------------------------------------------------------- - private string GenerateId() - { - long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string rand = Convert.ToString(_rnd.Next(0x1000, 0xFFFF), 16).ToUpper(); - return $"{ts}-{rand}"; - } - - // --------------------------------------------------------- - // ✅ 1. Creazione nuova richiesta - // --------------------------------------------------------- - public async Task AddRequestAsync(string environment, string uid, string tipo) - { - var id = GenerateId(); - var processStart = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - - var hashKey = GetRequestKey(id); - var setKey = GetSortedSetKey(environment, tipo); - var uidKey = GetUidSetKey(uid); - var combKey = GetCombinationsKey(); - - var batch = _db.CreateBatch(); - - // Hash - batch.HashSetAsync(hashKey, new HashEntry[] - { - new HashEntry("processStart", processStart), - new HashEntry("UID", uid), - new HashEntry("tipo", tipo ?? ""), - new HashEntry("environment", environment) - }); - - // SortedSet - batch.SortedSetAddAsync(setKey, id, processStart); - - // UID index - batch.SetAddAsync(uidKey, id); - - // Registra combinazione environment|tipo - string comb = $"{environment}|{tipo}"; - batch.SetAddAsync(combKey, comb); - - // Statistiche richieste - string minuteKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}"); - string hourKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}"); - - batch.StringIncrementAsync(minuteKey); - batch.StringIncrementAsync(hourKey); - - batch.Execute(); - - return id; - } - - // --------------------------------------------------------- - // ✅ 2. Aggiornamento richiesta esistente - // --------------------------------------------------------- - public async Task CompleteRequestAsync(string id) - { - var hashKey = GetRequestKey(id); - var processEnd = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - - var processStartValue = await _db.HashGetAsync(hashKey, "processStart"); - if (processStartValue.IsNull) return; - - var processStart = (long)processStartValue; - var elapsed = processEnd - processStart; - - var batch = _db.CreateBatch(); - - batch.HashSetAsync(hashKey, new HashEntry[] - { - new HashEntry("processEnd", processEnd), - new HashEntry("processElapsed", elapsed) - }); - - // Statistiche processing - string hourKeySum = Key($"stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}"); - string hourKeyMax = Key($"stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}"); - - batch.StringIncrementAsync(hourKeySum, elapsed); - - // Max non atomico, ma sufficiente per statistiche - var currentMax = await _db.StringGetAsync(hourKeyMax); - if (currentMax.IsNull || long.Parse(currentMax) < elapsed) - batch.StringSetAsync(hourKeyMax, elapsed); - - batch.Execute(); - } - - // --------------------------------------------------------- - // ✅ 3. Recupero richieste per UID - // --------------------------------------------------------- - public async Task> GetRequestsByUidAsync(string uid) - { - var uidKey = GetUidSetKey(uid); - var members = await _db.SetMembersAsync(uidKey); - return members.Select(x => x.ToString()); - } - - // --------------------------------------------------------- - // ✅ 4. Recupero combinazioni environment/tipo - // --------------------------------------------------------- - public async Task> GetCombinationsAsync() - { - var members = await _db.SetMembersAsync(GetCombinationsKey()); - return members - .Select(x => x.ToString().Split('|')) - .Select(a => (a[0], a[1])); - } - - // --------------------------------------------------------- - // ✅ 5. Cleanup periodico - // --------------------------------------------------------- - public async Task CleanupOldRequestsAsync(string environment, string tipo) - { - var cutoff = DateTimeOffset.UtcNow.Add(-_retention).ToUnixTimeSeconds(); - var setKey = GetSortedSetKey(environment, tipo); - - var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); - if (oldIds.Length == 0) return; - - var batch = _db.CreateBatch(); - - foreach (var id in oldIds) - { - var idStr = id.ToString(); - var hashKey = GetRequestKey(idStr); - - var uid = await _db.HashGetAsync(hashKey, "UID"); - if (!uid.IsNull) - { - var uidKey = GetUidSetKey(uid); - batch.SetRemoveAsync(uidKey, idStr); - - batch.SetLengthAsync(uidKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(uidKey); - }); - } - - batch.KeyDeleteAsync(hashKey); - } - - batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff); - - batch.SortedSetLengthAsync(setKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(setKey); - }); - - batch.Execute(); - } - - // --------------------------------------------------------- - // ✅ 6. Statistiche aggregate - // --------------------------------------------------------- - public async Task GetStatsAsync() - { - string minuteKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHHmm}"); - string hourKey = Key($"stats:requests:count:{DateTime.UtcNow:yyyyMMddHH}"); - string hourSumKey = Key($"stats:processing:sum:{DateTime.UtcNow:yyyyMMddHH}"); - string hourMaxKey = Key($"stats:processing:max:{DateTime.UtcNow:yyyyMMddHH}"); - - var minuteCount = await _db.StringGetAsync(minuteKey); - var hourCount = await _db.StringGetAsync(hourKey); - var sum = await _db.StringGetAsync(hourSumKey); - var max = await _db.StringGetAsync(hourMaxKey); - - long minCnt = minuteCount.IsNull ? 0 : (long)minuteCount; - long hourCnt = hourCount.IsNull ? 0 : (long)hourCount; - long sumVal = sum.IsNull ? 0 : (long)sum; - long maxVal = max.IsNull ? 0 : (long)max; - - double avg = hourCnt > 0 ? (double)sumVal / hourCnt : 0; - - return new - { - RequestsLastMinute = minCnt, - RequestsLastHour = hourCnt, - ProcessingAvgLastHour = avg, - ProcessingMaxLastHour = maxVal - }; - } - } - - -} \ No newline at end of file diff --git a/Lux.API/Controllers/GenericController.cs b/Lux.API/Controllers/GenericController.cs index bf605cb1..16dab33f 100644 --- a/Lux.API/Controllers/GenericController.cs +++ b/Lux.API/Controllers/GenericController.cs @@ -5,6 +5,7 @@ using EgwMultiEngineManager.Data; using Microsoft.AspNetCore.Mvc; using NLog; using System.Diagnostics; +using ZXing.QrCode.Internal; namespace Lux.API.Controllers { @@ -14,12 +15,13 @@ namespace Lux.API.Controllers { #region Public Constructors - public GenericController(IConfiguration config, IRedisService redisService, ImageCacheService imgServ) + public GenericController(IConfiguration config, IRedisService redisService, ImageCacheService imgServ, CalcRuidService crService) { _config = config; _redisService = redisService; _imgService = imgServ; chPub = _config.GetValue("ServerConf:ChannelPub") ?? ""; + _calcRuidService = crService; } #endregion Public Constructors @@ -42,7 +44,10 @@ namespace Lux.API.Controllers // ...se ricevo percorso --> leggo jwd/svg cablato if (currReq != null) { + // preparo variabili di base Dictionary DictExec = currReq.DictExec; + string envir = $"{currReq.EnvType}"; + string type = "ND"; // controllo se mancassero parametri... if (!DictExec.ContainsKey("Mode")) { @@ -58,8 +63,13 @@ namespace Lux.API.Controllers } if (!DictExec.ContainsKey("RUID")) { - // FixMe! todo! gestire con VERE richieste ID da servizio ReqIndexServicer!!!! - DictExec.Add("RUID", GenerateId()); + var mode = DictExec["Mode"]; + var sub = DictExec["SubMode"]; + type = string.IsNullOrEmpty(sub) ? mode : $"{mode}-{sub}"; + // creo registrazione richiesta... + var ruid = await _calcRuidService.AddRequestAsync(envir, type, id); + // aggiungo RUID effettivo + DictExec.Add("RUID", ruid); } int nId = 1; // da modificare con tipo richiesta... @@ -73,17 +83,6 @@ namespace Lux.API.Controllers return Ok(retVal); } - private readonly Random _rnd = new Random(); - // --------------------------------------------------------- - // ✅ ID incrementale: timestamp ms + random 4-6 chars - // --------------------------------------------------------- - private string GenerateId() - { - long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string rand = Convert.ToString(_rnd.Next(0x1000, 0xFFFF), 16).ToUpper(); - return $"{ts}-{rand}"; - } - /// /// Chiamata POST: riceve Json in formato JWD serializzato, invia richiesta calcolo modo 2 (BOM) /// PUT: api/window/bom/00000000-0000-0000-0000-000000000000 @@ -99,6 +98,7 @@ namespace Lux.API.Controllers // se messaggio vuoto --> uso default! currSer = string.IsNullOrEmpty(currSer) ? "" : currSer; + var bomEnvir = Constants.EXECENVIRONMENTS.WINDOW; // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(currSer)) @@ -109,13 +109,16 @@ namespace Lux.API.Controllers // UID cablato x ora... DictExec.Add("UID", id); - // FixMe! todo! gestire con VERE richieste ID da servizio ReqIndexServicer!!!! - DictExec.Add("RUID", GenerateId()); - + string envir = $"{bomEnvir}"; + string type = $"{Enums.QuestionModes.BOM}"; + var ruid = await _calcRuidService.AddRequestAsync(envir, type, id); + // Aggiungo RUID effettivo + DictExec.Add("RUID", ruid); + // valore serializzato x BOM DictExec.Add("SerializedData", currSer); int nId = 1; // da modificare con tipo richiesta... - QuestionDTO currArgs = new QuestionDTO(nId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW, DictExec); + QuestionDTO currArgs = new QuestionDTO(nId, bomEnvir, DictExec); await _redisService.PublishAsync(chPub, currArgs.sProcessArgs); retVal = "DONE"; @@ -130,8 +133,10 @@ namespace Lux.API.Controllers #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); + private readonly CalcRuidService _calcRuidService; private readonly IRedisService _redisService; private readonly string chPub = ""; + private IConfiguration _config; #endregion Private Fields @@ -141,5 +146,10 @@ namespace Lux.API.Controllers private ImageCacheService _imgService { get; set; } #endregion Private Properties + + #region Private Methods + + + #endregion Private Methods } } \ No newline at end of file diff --git a/Lux.API/Controllers/WindowController.cs b/Lux.API/Controllers/WindowController.cs index 7fbf3b2f..f83a9c1d 100644 --- a/Lux.API/Controllers/WindowController.cs +++ b/Lux.API/Controllers/WindowController.cs @@ -15,13 +15,14 @@ namespace Lux.API.Controllers { #region Public Constructors - public WindowController(IConfiguration config, IRedisService redisService, ImageCacheService imgServ, ConfigDataService confServ) + public WindowController(IConfiguration config, IRedisService redisService, ImageCacheService imgServ, ConfigDataService confServ, CalcRuidService crService) { _config = config; _redisService = redisService; _imgService = imgServ; _confService = confServ; chPub = _config.GetValue("ServerConf:ChannelPub") ?? ""; + _calcRuidService = crService; } #endregion Public Constructors @@ -60,12 +61,17 @@ namespace Lux.API.Controllers // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(currJwd)) { + // init vars Dictionary DictExec = new Dictionary(); - DictExec.Add("Mode", $"{(int)Egw.Window.Data.Enums.QuestionModes.BOM}"); + var cMode = Egw.Window.Data.Enums.QuestionModes.BOM; + + DictExec.Add("Mode", $"{(int)cMode}"); // UID cablato x ora... DictExec.Add("UID", id); - // FixMe! todo! gestire con VERE richieste ID da servizio ReqIndexServicer!!!! - DictExec.Add("RUID", GenerateId()); + // creo registrazione richiesta... + var ruid = await _calcRuidService.AddRequestAsync($"{cEnvir}", $"{cMode}", id); + // aggiungo RUID effettivo + DictExec.Add("RUID", ruid); DictExec.Add("SerializedData", currJwd); int nId = 1; @@ -80,17 +86,6 @@ namespace Lux.API.Controllers return Ok(svgContent); } - private readonly Random _rnd = new Random(); - // --------------------------------------------------------- - // ✅ ID incrementale: timestamp ms + random 4-6 chars - // --------------------------------------------------------- - private string GenerateId() - { - long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string rand = Convert.ToString(_rnd.Next(0x1000, 0xFFFF), 16).ToUpper(); - return $"{ts}-{rand}"; - } - /// /// Chiamata GET: invia richiesta modo 3 (HardwareModelList) che sarà poi salvata /// GET: api/window/hwlist @@ -111,27 +106,7 @@ namespace Lux.API.Controllers } /// - /// Esegue invio effettivo richiesta elenco HW list - /// - /// - private async Task sendHwReq() - { - Dictionary DictExec = new Dictionary(); - DictExec.Add("Mode", $"{(int)Egw.Window.Data.Enums.QuestionModes.HARDWARE}"); - // da rivedere? - DictExec.Add("UID", "HW.AGB"); - // FixMe! todo! gestire con VERE richieste ID da servizio ReqIndexServicer!!!! - DictExec.Add("RUID", GenerateId()); - DictExec.Add("SubMode", $"{(int)Egw.Window.Data.Enums.QuestionHwSubModes.LIST}"); - DictExec.Add("Manufacturer", $"{(int)Egw.Window.Data.Enums.HardwareManufacturers.AGB}"); - int nId = 1; - // da modificare con tipo richiesta... - QuestionDTO currArgs = new QuestionDTO(nId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW, DictExec); - await _redisService.PublishAsync(chPub, currArgs.sProcessArgs); - } - - /// - /// Chiamata GET: + /// Chiamata GET: /// - se trova in cache risponde con elenco hw già ricevuto /// - se non trova invia richiesta modo 3 (HardwareModelList) che sarà poi salvata /// GET: api/window/hwlist/nome_produttore @@ -177,7 +152,7 @@ namespace Lux.API.Controllers // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(id)) { - // bonifica nome svg da + // bonifica nome svg da svgContent = _imgService.LoadSvg(id); } sw.Stop(); @@ -254,16 +229,21 @@ namespace Lux.API.Controllers // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(currJwd)) { + // init vars Dictionary DictExec = new Dictionary(); - DictExec.Add("Mode", $"{(int)Egw.Window.Data.Enums.QuestionModes.PREVIEW}"); + var cMode = Egw.Window.Data.Enums.QuestionModes.PREVIEW; + + DictExec.Add("Mode", $"{(int)cMode}"); // UID cablato x ora... DictExec.Add("UID", id); - // FixMe! todo! gestire con VERE richieste ID da servizio ReqIndexServicer!!!! - DictExec.Add("RUID", GenerateId()); + // creo registrazione richiesta... + var ruid = await _calcRuidService.AddRequestAsync($"{cEnvir}", $"{cMode}", id); + // aggiungo RUID effettivo + DictExec.Add("RUID", ruid); DictExec.Add("SerializedData", currJwd); int nId = 1; // da modificare con tipo richiesta... - QuestionDTO currArgs = new QuestionDTO(nId, EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW, DictExec); + QuestionDTO currArgs = new QuestionDTO(nId, cEnvir, DictExec); await _redisService.PublishAsync(chPub, currArgs.sProcessArgs); svgContent = "DONE"; @@ -278,10 +258,14 @@ namespace Lux.API.Controllers #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); + private readonly CalcRuidService _calcRuidService; private readonly IRedisService _redisService; private readonly string chPub = ""; + private IConfiguration _config; + private EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS cEnvir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW; + /// /// Demorichiesta jwd x fare test richiesta calcolo /// @@ -291,9 +275,42 @@ namespace Lux.API.Controllers #region Private Properties - private ImageCacheService _imgService { get; set; } private ConfigDataService _confService { get; set; } + private ImageCacheService _imgService { get; set; } + #endregion Private Properties + + #region Private Methods + + /// + /// Esegue invio effettivo richiesta elenco HW list + /// + /// + private async Task sendHwReq() + { + // init vars + Dictionary DictExec = new Dictionary(); + var cMode = Egw.Window.Data.Enums.QuestionModes.HARDWARE; + var cSubMode = Egw.Window.Data.Enums.QuestionHwSubModes.LIST; + var cManufact = Egw.Window.Data.Enums.HardwareManufacturers.AGB; + // compongo richiesta + DictExec.Add("Mode", $"{(int)cMode}"); + // aggiungo dati ID + string uid = "HW.AGB"; + DictExec.Add("UID", uid); + // creo registrazione richiesta... + var ruid = await _calcRuidService.AddRequestAsync($"{cEnvir}", $"{cMode}-{cSubMode}", uid); + // aggiungo RUID effettivo + DictExec.Add("RUID", ruid); + DictExec.Add("SubMode", $"{(int)cSubMode}"); + DictExec.Add("Manufacturer", $"{(int)cManufact}"); + int nId = 1; + // da modificare con tipo richiesta... + QuestionDTO currArgs = new QuestionDTO(nId, cEnvir, DictExec); + await _redisService.PublishAsync(chPub, currArgs.sProcessArgs); + } + + #endregion Private Methods } } \ No newline at end of file diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index c7542124..b33dde1d 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.9.2512.1216 + 0.9.2512.1218 diff --git a/Lux.API/Program.cs b/Lux.API/Program.cs index d8e7cebf..83624824 100644 --- a/Lux.API/Program.cs +++ b/Lux.API/Program.cs @@ -53,6 +53,18 @@ builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +// init servizio gestone ReqIndex +string cleanupDayTTL = configuration.GetValue("ServerConf:CleanupDayTTL") ?? "360"; +string rBaseKey = configuration.GetValue("ServerConf:RedisBaseKey") ?? "Lux"; +int dayTTL = 360; +int.TryParse(cleanupDayTTL, out dayTTL); +builder.Services.AddSingleton(new CalcRuidService( + redisConn, + retention: TimeSpan.FromDays(dayTTL), + redisBaseKey: rBaseKey +)); + +builder.Services.AddHostedService(); var app = builder.Build(); diff --git a/Lux.API/appsettings.json b/Lux.API/appsettings.json index 75d305d9..7eba5be0 100644 --- a/Lux.API/appsettings.json +++ b/Lux.API/appsettings.json @@ -71,6 +71,9 @@ "ImageCalcTag": "svg-preview", "ImageLiveTag": "svg", "ImageFileTag": "svgfile", - "FileSharePath": "\\\\stor01\\TEAM DRIVES\\40_FileUpload\\LuxUploads" + "FileSharePath": "\\\\stor01\\TEAM DRIVES\\40_FileUpload\\LuxUploads", + "RedisBaseKey": "Lux", + "CleanupPeriodMinutes": 120, + "CleanupDayTTL": 180 } } diff --git a/Lux.UI/Components/Compo/Stats/PeriodStats.razor b/Lux.UI/Components/Compo/Stats/PeriodStats.razor new file mode 100644 index 00000000..57b5939f --- /dev/null +++ b/Lux.UI/Components/Compo/Stats/PeriodStats.razor @@ -0,0 +1,26 @@ +@using EgwCoreLib.Lux.Data.Services +@inject CalcRuidService Req + +

Statistiche Storiche

+ + + + + +@if (stats != null) +{ +

Totale richieste: @stats.TotalRequests

+

Tempo medio: @stats.AvgProcessing ms

+

Tempo max: @stats.MaxProcessing ms

+} + +@code { + private DateTime from = DateTime.UtcNow.AddHours(-24); + private DateTime to = DateTime.UtcNow; + private dynamic? stats; + + private async Task Load() + { + stats = await Req.GetStatsRangeAsync(from, to); + } +} diff --git a/Lux.UI/Components/Compo/Stats/RelatimeStats.razor b/Lux.UI/Components/Compo/Stats/RelatimeStats.razor new file mode 100644 index 00000000..d821087a --- /dev/null +++ b/Lux.UI/Components/Compo/Stats/RelatimeStats.razor @@ -0,0 +1,26 @@ +@using EgwCoreLib.Lux.Data.Services +@inject CalcRuidService Req + +

Statistiche in tempo reale

+ +
+

Richieste ultimo minuto: @stats?.RequestsLastMinute

+

Richieste ultima ora: @stats?.RequestsLastHour

+

Tempo medio: @stats?.ProcessingAvgLastHour ms

+

Tempo max: @stats?.ProcessingMaxLastHour ms

+
+ +@code { + private dynamic? stats; + + protected override async Task OnInitializedAsync() + { + var timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); + + while (await timer.WaitForNextTickAsync()) + { + stats = await Req.GetStatsAsync(); + StateHasChanged(); + } + } +} diff --git a/Lux.UI/Lux.UI.csproj b/Lux.UI/Lux.UI.csproj index f59394e2..37887ffc 100644 --- a/Lux.UI/Lux.UI.csproj +++ b/Lux.UI/Lux.UI.csproj @@ -5,7 +5,7 @@ enable enable aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50 - 0.9.2512.1216 + 0.9.2512.1218 diff --git a/Lux.UI/Program.cs b/Lux.UI/Program.cs index a10f6f2b..60c1bba7 100644 --- a/Lux.UI/Program.cs +++ b/Lux.UI/Program.cs @@ -70,6 +70,18 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +// init servizio gestone ReqIndex +string cleanupDayTTL = configuration.GetValue("ServerConf:CleanupDayTTL") ?? "360"; +string rBaseKey = configuration.GetValue("ServerConf:RedisBaseKey") ?? "Lux"; +int dayTTL = 360; +int.TryParse(cleanupDayTTL, out dayTTL); +builder.Services.AddSingleton(new CalcRuidService( + redisConn, + retention: TimeSpan.FromDays(dayTTL), + redisBaseKey: rBaseKey +)); + +builder.Services.AddHostedService(); var app = builder.Build(); diff --git a/Lux.UI/appsettings.json b/Lux.UI/appsettings.json index 49e322f2..88c752ca 100644 --- a/Lux.UI/appsettings.json +++ b/Lux.UI/appsettings.json @@ -77,6 +77,9 @@ "ImageFileTag": "cache", "ImageLiveTag": "svg", "BaseUrl": "/lux/ui/", - "FileSharePath": "\\\\stor01\\TEAM DRIVES\\40_FileUpload\\LuxUploads" + "FileSharePath": "\\\\stor01\\TEAM DRIVES\\40_FileUpload\\LuxUploads", + "RedisBaseKey": "Lux", + "CleanupPeriodMinutes": 120, + "CleanupDayTTL": 180 } } diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index 4c2ecc27..332fccec 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 0.9.2512.1216

+

Versione: 0.9.2512.1218


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index b1be78c4..e435331d 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -0.9.2512.1216 +0.9.2512.1218 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index 316df500..25ea4a5e 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 0.9.2512.1216 + 0.9.2512.1218 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false