diff --git a/Core/Enum.cs b/Core/Enum.cs new file mode 100644 index 0000000..bc27d86 --- /dev/null +++ b/Core/Enum.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core +{ + /// + /// Tipologia di ticket + /// + public enum TipologiaTicket + { + ND = 0, + Licenze, + FileUpload + } +} diff --git a/Core/SupportRequest.cs b/Core/SupportRequest.cs index cab22a3..5cdf111 100644 --- a/Core/SupportRequest.cs +++ b/Core/SupportRequest.cs @@ -18,6 +18,8 @@ namespace Core public string ContactPhone { get; set; } = ""; public int idxSubLic { get; set; } = 0; + public TipologiaTicket Tipo { get; set; } = TipologiaTicket.ND; + public bool IsValid { get => !string.IsNullOrEmpty(MasterKey) && !string.IsNullOrEmpty(ContactEmail) && !string.IsNullOrEmpty(CodInst) && !string.IsNullOrEmpty(CodApp); diff --git a/Core/UploadResult.cs b/Core/UploadResult.cs new file mode 100644 index 0000000..282af3b --- /dev/null +++ b/Core/UploadResult.cs @@ -0,0 +1,10 @@ +namespace Core +{ + public class UploadResult + { + public bool Uploaded { get; set; } + public string? FileName { get; set; } + public string? StoredFileName { get; set; } + public int ErrorCode { get; set; } + } +} diff --git a/LiMan.Api/Controllers/FilesaveController.cs b/LiMan.Api/Controllers/FilesaveController.cs new file mode 100644 index 0000000..3937abd --- /dev/null +++ b/LiMan.Api/Controllers/FilesaveController.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Core; +using LiMan.APi.Data; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace LiMan.APi.Controllers +{ + /// + /// Controller caricamento file + /// + [ApiController] + [Route("api/filesave")] + public class FilesaveController : ControllerBase + { + private readonly IWebHostEnvironment env; + private readonly ILogger logger; + + /// + /// Dataservice x accesso DB + /// + protected ApiDataService dataService { get; set; } + + /// + /// Init generico + /// + /// + public FilesaveController(ApiDataService DataService, IWebHostEnvironment env, ILogger logger) + { + dataService = DataService; + this.env = env; + this.logger = logger; + logger.LogInformation("Avviata classe FilesaveController"); + } + + + /// + /// Caricamento file effettivo via POST + /// + /// TicketId x riferimento + /// Elenco files da caricare + /// + [HttpPost("single")] + public async Task> PostSingleFile([FromForm] int ticketId, [FromForm] IFormFile file) + { + // max 20 mb + long maxFileSize = 1024 * 1024 * 20; + string ticketDir = $"T{ticketId:000000000}"; + var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/api/filesave/{ticketDir}"); + List uploadResults = new(); + string fileDir = env.ContentRootPath; + string relDir = env.EnvironmentName; + + var uploadResult = new UploadResult(); + string trustedFileNameForFileStorage; + var untrustedFileName = file.FileName; + uploadResult.FileName = untrustedFileName; + var trustedFileNameForDisplay = WebUtility.HtmlEncode(untrustedFileName); + + if (file.Length == 0) + { + logger.LogInformation("{FileName} length is 0 (Err: 1)", trustedFileNameForDisplay); + uploadResult.ErrorCode = 1; + } + else if (file.Length > maxFileSize) + { + logger.LogInformation("{FileName} of {Length} bytes is larger than the limit of {Limit} bytes (Err: 2)", trustedFileNameForDisplay, file.Length, maxFileSize); + uploadResult.ErrorCode = 2; + } + else + { + try + { + DateTime oggi = DateTime.Today; + trustedFileNameForFileStorage = Path.GetRandomFileName(); + relDir = Path.Combine(env.EnvironmentName, "unsafe_uploads", ticketDir); + fileDir = Path.Combine(env.ContentRootPath, relDir); + //string fileDir = Path.Combine(env.ContentRootPath, env.EnvironmentName, "unsafe_uploads", $"{oggi:yyyy}", $"{oggi:MM}", $"{oggi:dd}"); + if (!Directory.Exists(fileDir)) + { + Directory.CreateDirectory(fileDir); + } + var path = Path.Combine(fileDir, trustedFileNameForFileStorage); + + await using FileStream fs = new(path, FileMode.Create); + await file.CopyToAsync(fs); + + logger.LogInformation("{FileName} saved at {Path}", trustedFileNameForDisplay, path); + uploadResult.Uploaded = true; + uploadResult.StoredFileName = trustedFileNameForFileStorage; + } + catch (IOException ex) + { + logger.LogError("{FileName} error on upload (Err: 3): {Message}", trustedFileNameForDisplay, ex.Message); + uploadResult.ErrorCode = 3; + } + } + + uploadResults.Add(uploadResult); + // salvo su DB + var fatto = dataService.FileAdd(ticketId, relDir, uploadResults); + + return new CreatedResult(resourcePath, uploadResult); + } + + /// + /// Caricamento file effettivo via POST + /// + /// TicketId x riferimento + /// Elenco files da caricare + /// + [HttpPost()] + public async Task>> PostFiles([FromForm] int ticketId, [FromForm] IEnumerable files) + { + // max 3 files + var maxAllowedFiles = 3; + // max 20 mb + long maxFileSize = 1024 * 1024 * 20; + var filesProcessed = 0; + string ticketDir = $"T{ticketId:000000000}"; + var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/api/filesave/{ticketDir}"); + List uploadResults = new(); + string fileDir = env.ContentRootPath; + string relDir = env.EnvironmentName; + + foreach (var file in files) + { + var uploadResult = new UploadResult(); + string trustedFileNameForFileStorage; + var untrustedFileName = file.FileName; + uploadResult.FileName = untrustedFileName; + var trustedFileNameForDisplay = WebUtility.HtmlEncode(untrustedFileName); + + if (filesProcessed < maxAllowedFiles) + { + if (file.Length == 0) + { + logger.LogInformation("{FileName} length is 0 (Err: 1)", trustedFileNameForDisplay); + uploadResult.ErrorCode = 1; + } + else if (file.Length > maxFileSize) + { + logger.LogInformation("{FileName} of {Length} bytes is larger than the limit of {Limit} bytes (Err: 2)", trustedFileNameForDisplay, file.Length, maxFileSize); + uploadResult.ErrorCode = 2; + } + else + { + try + { + DateTime oggi = DateTime.Today; + trustedFileNameForFileStorage = Path.GetRandomFileName(); + relDir = Path.Combine(env.EnvironmentName, "unsafe_uploads", ticketDir); + fileDir = Path.Combine(env.ContentRootPath, relDir); + //string fileDir = Path.Combine(env.ContentRootPath, env.EnvironmentName, "unsafe_uploads", $"{oggi:yyyy}", $"{oggi:MM}", $"{oggi:dd}"); + if (!Directory.Exists(fileDir)) + { + Directory.CreateDirectory(fileDir); + } + var path = Path.Combine(fileDir, trustedFileNameForFileStorage); + + await using FileStream fs = new(path, FileMode.Create); + await file.CopyToAsync(fs); + + logger.LogInformation("{FileName} saved at {Path}", trustedFileNameForDisplay, path); + uploadResult.Uploaded = true; + uploadResult.StoredFileName = trustedFileNameForFileStorage; + } + catch (IOException ex) + { + logger.LogError("{FileName} error on upload (Err: 3): {Message}", trustedFileNameForDisplay, ex.Message); + uploadResult.ErrorCode = 3; + } + } + + filesProcessed++; + } + else + { + logger.LogInformation("{FileName} not uploaded because the request exceeded the allowed {Count} of files (Err: 4)", trustedFileNameForDisplay, maxAllowedFiles); + uploadResult.ErrorCode = 4; + } + + uploadResults.Add(uploadResult); + } + // salvo su DB + var fatto = dataService.FileAdd(ticketId, relDir, uploadResults); + + return new CreatedResult(resourcePath, uploadResults); + } + } +} diff --git a/LiMan.Api/Controllers/LicenzaController.cs b/LiMan.Api/Controllers/LicenzaController.cs index f86e8f6..84ce909 100644 --- a/LiMan.Api/Controllers/LicenzaController.cs +++ b/LiMan.Api/Controllers/LicenzaController.cs @@ -74,7 +74,7 @@ namespace LiMan.APi.Controllers /// Info licenza in formato LicenseCoord /// [HttpPost()] - public async Task> Get([FromBody] LicenseCoord AppInfo) + public async Task> Post([FromBody] LicenseCoord AppInfo) { var result = await dataService.LicenzeSearch(AppInfo.CodInst, AppInfo.CodApp, AppInfo.MasterKey, false); await dataService.recordCall(AppInfo.CodInst, AppInfo.CodApp, $"POST:api/licenza:{AppInfo.MasterKey}"); diff --git a/LiMan.Api/Controllers/TicketController.cs b/LiMan.Api/Controllers/TicketController.cs index 640110f..0965b08 100644 --- a/LiMan.Api/Controllers/TicketController.cs +++ b/LiMan.Api/Controllers/TicketController.cs @@ -54,7 +54,7 @@ namespace LiMan.APi.Controllers /// GET api/ticket/id /// - /// Recupera elenco applicativi dati cliente + /// Recupera elenco Ticket dato cliente / applicazione / chiave /// /// Codice cliente/Installazione /// Codice Applicazione @@ -74,9 +74,9 @@ namespace LiMan.APi.Controllers /// Obj Richiesta // POST api/ticket/sendReq [HttpPost("sendReq")] - public async Task> sendReq([FromBody] SupportRequest CurrRequest) + public async Task sendReq([FromBody] SupportRequest CurrRequest) { - List result = new List(); + TicketDTO result = new TicketDTO(); // controllo valori if (CurrRequest.IsValid) { @@ -84,7 +84,8 @@ namespace LiMan.APi.Controllers var insRes = await dataService.TicketAdd(CurrRequest); } // restituisco richieste aperte - result = await dataService.TicketByCliente(CurrRequest.CodInst, CurrRequest.CodApp, CurrRequest.MasterKey); + var rawResult= await dataService.TicketByCliente(CurrRequest.CodInst, CurrRequest.CodApp, CurrRequest.MasterKey, 1); + result = rawResult.FirstOrDefault(); await dataService.recordCall(CurrRequest.CodInst, CurrRequest.CodApp, $"POST:api/ticket/sendReq:{CurrRequest.MasterKey}"); return result; } diff --git a/LiMan.Api/Data/ApiDataService.cs b/LiMan.Api/Data/ApiDataService.cs index f85d8c7..acdb9d9 100644 --- a/LiMan.Api/Data/ApiDataService.cs +++ b/LiMan.Api/Data/ApiDataService.cs @@ -1,20 +1,19 @@ using Core; using LiMan.DB.DBModels; +using LiMan.DB.DTO; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using NLog; +using StackExchange.Redis.Extensions.Core.Abstractions; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading.Tasks; using static LiMan.DB.Enum; -using StackExchange.Redis.Extensions.Core.Abstractions; -using LiMan.DB.DTO; namespace LiMan.APi.Data { @@ -23,48 +22,24 @@ namespace LiMan.APi.Data /// public class ApiDataService : IDisposable { - #region Public Fields - - /// - /// Classe Accesso metodi DB - /// - public static LiMan.DB.Controllers.DbController dbController; - - #endregion Public Fields - - #region Protected Fields - - /// - /// TTL da 1 h x cache Redis - /// - protected const int hourTTL = 60 * 60; - - /// - /// Chiave redis x statistiche in acquisizione - /// - protected const string rKeySampleStats = "LiMan.UI:SampleStats:Curr"; - - /// - /// Chiave redis x statistiche in acquisizione - /// - protected const string rKeySampleVars = "LiMan.UI:SampleStats:Vars"; - /// - /// Chiave redis x licenze da MasterKey - /// - protected const string rKeyLicenze = "LiMan.UI:Licenze:ListByKey"; - - #endregion Protected Fields - #region Private Fields private static IConfiguration _configuration; + private static ILogger _logger; + private static NLog.Logger Log = LogManager.GetCurrentClassLogger(); + private readonly IEmailSender _emailSender; //private readonly IDistributedCache distributedCache; private readonly IRedisCacheClient _redisCacheClient; + /// + /// Elenco obj in cache + /// + private List cachedDataList = new List(); + /// /// Durata assoluta massima della cache IN SECONDI /// @@ -78,6 +53,49 @@ namespace LiMan.APi.Data #endregion Private Fields + #region Protected Fields + + /// + /// TTL da 1 h x cache Redis + /// + protected const int hourTTL = 60 * 60; + + /// + /// Chiave redis x attivazioni da IdxLic + /// + protected const string rKeyAttivByLic = "LiMan.UI:Licenze:AttByIdxLic"; + + /// + /// Chiave redis x licenze da MasterKey + /// + protected const string rKeyLicByMKey = "LiMan.UI:Licenze:ListByKey"; + + /// + /// Chiave redis x statistiche in acquisizione + /// + protected const string rKeySampleStats = "LiMan.UI:SampleStats:Curr"; + + /// + /// Chiave redis x statistiche in acquisizione + /// + protected const string rKeySampleVars = "LiMan.UI:SampleStats:Vars"; + + /// + /// TTL da 1 min x cache Redis + /// + protected const int shortTTL = 60 * 5; + + #endregion Protected Fields + + #region Public Fields + + /// + /// Classe Accesso metodi DB + /// + public static LiMan.DB.Controllers.DbController dbController; + + #endregion Public Fields + #region Public Constructors /// @@ -112,6 +130,174 @@ namespace LiMan.APi.Data #endregion Public Constructors + #region Private Methods + + /// + /// Parametri per generare opzioni cache + /// + /// Fattore di moltiplica cache (se 1 --> 2 e 5 min) + /// + private DistributedCacheEntryOptions cacheOpt(int multFact) + { + var numSecAbsExp = multFact <= 0 ? chAbsExp : chAbsExp * multFact; + var numSecSliExp = multFact <= 0 ? chSliExp : chSliExp * multFact; + return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddSeconds(numSecAbsExp)).SetSlidingExpiration(TimeSpan.FromSeconds(numSecSliExp)); + } + + #endregion Private Methods + + #region Protected Methods + + /// + /// Recupera statistiche correnti + /// + /// + protected async Task getCurrStats() + { + DateTime adesso = DateTime.Now; + SampleStats answ = new SampleStats() + { + Name = "ApiStats" + }; + // in primis check data/ora prima/ultima scrittura del set... (2 date, lista chiavi gestite) + string rawData = await getRSV(rKeySampleStats); + if (rawData != null) + { + answ = JsonConvert.DeserializeObject(rawData); + // aggiorno ultimo controllo e salvo... + answ.DtLast = adesso; + // salvo! + await setCurrStats(answ); + } + // controllo se scadute... + if (adesso.Subtract(answ.DtFirst).TotalMinutes > 60) + { + // se scaduto --> registrazione set sul DB (async), resettando i vari contatori... + bool salvato = await saveStatsToDb(answ.VList); + // inizio NUOVO set vuoto con record corrente + answ = new SampleStats() + { + Name = "ApiStats" + }; + // salvo! + await setCurrStats(answ); + } + // restituisco record! + return answ; + } + + /// + /// Recupero chiave da redis + /// + /// + /// + protected async Task getRSV(string rKey) + { + string answ = await _redisCacheClient.GetDbFromConfiguration().GetAsync(rKey); + return answ; + } + + /// + /// Recupera contatore x la chiave redis indicata... + /// + /// + protected async Task redCount(string rKey) + { + int currCount = 0; + string rawVal = await getRSV(rKey); + if (!string.IsNullOrEmpty(rawVal)) + { + int.TryParse(rawVal, out currCount); + } + return currCount; + } + + /// + /// Resetta contatore x la chiave redis indicata... + /// + /// + protected async Task redCountClear(string rKey) + { + bool answ = false; + int currCount = 0; + answ = await setRSV(rKey, currCount, 2 * hourTTL); + return answ; + } + + /// + /// Incrementa contatore x la chiave redis indicata... + /// + /// + protected async Task redCountIncr(string rKey) + { + bool answ = false; + int currCount = 0; + string rawVal = await getRSV(rKey); + if (!string.IsNullOrEmpty(rawVal)) + { + int.TryParse(rawVal, out currCount); + } + currCount++; + answ = await setRSV(rKey, currCount, 2 * hourTTL); + return answ; + } + + /// + /// Salva statistiche correnti + /// + /// + protected async Task setCurrStats(SampleStats newVal) + { + bool answ = false; + string rawData = JsonConvert.SerializeObject(newVal); + answ = await setRSV(rKeySampleStats, rawData, 24 * hourTTL); + return answ; + } + + /// + /// Salvataggio chiave in redis + /// + /// + /// + /// + /// + protected async Task setRSV(string rKey, string rVal, int ttlSec) + { + bool fatto = false; + await _redisCacheClient.GetDbFromConfiguration().AddAsync(rKey, rVal, DateTimeOffset.Now.AddSeconds(ttlSec)); + fatto = true; + return fatto; + } + + /// + /// Salvataggio chiave in redis + /// + /// + /// + /// + /// + protected async Task setRSV(string rKey, int rValInt, int ttlSec) + { + bool fatto = false; + await _redisCacheClient.GetDbFromConfiguration().AddAsync(rKey, rValInt, DateTimeOffset.Now.AddSeconds(ttlSec)); + fatto = true; + return fatto; + } + + /// + /// Registra in cache chiave se non fosse già in elenco + /// + /// + protected void trackCache(string newKey) + { + if (!cachedDataList.Contains(newKey)) + { + cachedDataList.Add(newKey); + } + } + + #endregion Protected Methods + #region Public Methods /// @@ -121,9 +307,9 @@ namespace LiMan.APi.Data /// Codice Applicazione /// Indica se nascondere i dati sensibili /// - public async Task> ApplicativiSearch(string CodInst, string CodApp, bool HideData) + public async Task> ApplicativiSearch(string CodInst, string CodApp, bool HideData) { - List dbResult = new List(); + List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); @@ -142,9 +328,9 @@ namespace LiMan.APi.Data /// Codice Impiego licenza /// Indica se nascondere i dati sensibili /// - public async Task AttivazioneSearch(string Chiave, string CodImpiego, bool HideData) + public async Task AttivazioneSearch(string Chiave, string CodImpiego, bool HideData) { - DB.DTO.AttivazioneDTO dbResult = new DB.DTO.AttivazioneDTO(); + AttivazioneDTO dbResult = new AttivazioneDTO(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); @@ -160,20 +346,30 @@ namespace LiMan.APi.Data /// /// Elenco Attivaizoni da ID Licenza master /// - /// Idx Licenza Master - /// Indica se nascondere i dati sensibili + /// Idx Licenza Master + /// Indica se nascondere i dati sensibili /// - public async Task> AttivazioniByLic(int IdxLic, bool HideData) + public async Task> AttivazioniByLic(int idxLic, bool hideData) { - List dbResult = new List(); - - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - - dbResult = dbController.GetAttivazioniByLic(IdxLic, HideData); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"Effettuata lettura da DB per AttivazioniByLic: {ts.TotalMilliseconds} ms"); + List dbResult = new List(); + string cacheKey = $"{rKeyAttivByLic}:{hideData}:{idxLic}"; + trackCache(cacheKey); + string rawData = await getRSV(cacheKey); + if (!string.IsNullOrEmpty(rawData)) + { + dbResult = JsonConvert.DeserializeObject>(rawData); + } + else + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + dbResult = dbController.GetAttivazioniByLic(idxLic, hideData); + rawData = JsonConvert.SerializeObject(dbResult); + await setRSV(cacheKey, rawData, shortTTL); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"Effettuata lettura da DB per AttivazioniByLic: {ts.TotalMilliseconds} ms"); + } return await Task.FromResult(dbResult); } @@ -184,21 +380,24 @@ namespace LiMan.APi.Data /// Licenza Master /// Indica se nascondere i dati sensibili /// - public async Task> AttivazioniByMasterKey(string MasterKey, bool HideData) + public async Task> AttivazioniByMasterKey(string MasterKey, bool HideData) { - List dbResult = new List(); + List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); - var licenza = dbController.GetLicenza(MasterKey); +#if false + LicenzaModel licenza = dbController.GetLicenza(MasterKey); +#endif + LicenzaModel licenza = await LicenzaByMasterKey(MasterKey); if (licenza != null) { - dbResult = dbController.GetAttivazioniByLic(licenza.IdxLic, HideData); + dbResult = await AttivazioniByLic(licenza.IdxLic, HideData); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"Effettuata lettura da DB per AttivazioniByLic: {ts.TotalMilliseconds} ms"); + Log.Trace($"Effettuata lettura da DB per AttivazioniByMasterKey: {ts.TotalMilliseconds} ms"); return await Task.FromResult(dbResult); } @@ -215,11 +414,11 @@ namespace LiMan.APi.Data Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); - - var licenza = dbController.GetLicenza(MasterKey); + LicenzaModel licenza = await LicenzaByMasterKey(MasterKey); if (licenza != null) { answ = dbController.AttivazioniDelete(ParamDict, MasterKey); + await InvalidateAllCache(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; @@ -239,11 +438,12 @@ namespace LiMan.APi.Data Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); + LicenzaModel licenza = await LicenzaByMasterKey(MasterKey); - var licenza = dbController.GetLicenza(MasterKey); if (licenza != null) { answ = dbController.AttivazioniResetAvail(MasterKey); + await InvalidateAllCache(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; @@ -268,6 +468,7 @@ namespace LiMan.APi.Data stopWatch.Start(); taskDone = dbController.AttivazioniTryAdd(MasterKey, ParamDict, DayVeto); + await InvalidateAllCache(); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata scrittura + rilettura da DB per AttivazioniTryAdd: {ts.TotalMilliseconds} ms"); @@ -290,6 +491,7 @@ namespace LiMan.APi.Data stopWatch.Start(); taskDone = dbController.AttivazioniTryRefresh(MasterKey, ParamDict); + await InvalidateAllCache(); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata scrittura + rilettura da DB per AttivazioniTryRefresh: {ts.TotalMilliseconds} ms"); @@ -306,6 +508,41 @@ namespace LiMan.APi.Data dbController.Dispose(); } + /// + /// Esegue aggiunta file dato ticket e list uploadResult + /// + /// Identificativo del ticket + /// Directory di salvataggio dei file + /// lista risultati della funzione di upload + /// + public async Task FileAdd(int idxTicket, string baseDir, List fileUploaded) + { + bool fatto = false; + // inserimento! + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + fatto = dbController.FileAdd(idxTicket, baseDir, fileUploaded); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"Effettuata inserimento con FileAdd: {ts.TotalMilliseconds} ms"); + + // restituisce elenco + return await Task.FromResult(fatto); + } + + /// + /// invalida tutta la cache in caso di update + /// + /// + public async Task InvalidateAllCache() + { + foreach (var item in cachedDataList) + { + await _redisCacheClient.GetDbFromConfiguration().RemoveAsync(item); + } + cachedDataList = new List(); + } + /// /// Elenco licenze dato cliente /// @@ -314,7 +551,8 @@ namespace LiMan.APi.Data public async Task LicenzaByMasterKey(string chiave) { LicenzaModel dbResult = new LicenzaModel(); - string cacheKey = $"{rKeyLicenze}:{chiave}"; + string cacheKey = $"{rKeyLicByMKey}:{chiave}"; + trackCache(cacheKey); string rawData = await getRSV(cacheKey); if (!string.IsNullOrEmpty(rawData)) { @@ -340,10 +578,11 @@ namespace LiMan.APi.Data /// /// /// - public async Task LicenzaRefreshPayload(LicenseCoord appInfo) + public async Task LicenzaRefreshPayload(LicenseCoord appInfo) { // chiamo metodo x ricalcolare payload dato enigma bool done = await dbController.LicenseUpdatePayload(appInfo.CodInst, appInfo.CodApp, appInfo.MasterKey, appInfo.Enigma); + await InvalidateAllCache(); // ora recupero i dati var licList = await LicenzeSearch(appInfo.CodInst, appInfo.CodApp, appInfo.MasterKey, false); return licList.FirstOrDefault(); @@ -376,9 +615,9 @@ namespace LiMan.APi.Data /// Chiave Licenza da validare /// Indica se nascondere i dati sensibili /// - public async Task> LicenzeSearch(string CodInst, string CodApp, string Chiave, bool HideData) + public async Task> LicenzeSearch(string CodInst, string CodApp, string Chiave, bool HideData) { - List dbResult = new List(); + List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); @@ -452,7 +691,7 @@ namespace LiMan.APi.Data { CodInst = valStr[0], CodApp = valStr[1], - TargetUrl = valStr[2], + TargetUrl = item.Replace($"{rKeySampleVars}:", ""), //valStr[2], DataRif = DateTime.Now, NumCall = currCount }; @@ -516,13 +755,13 @@ namespace LiMan.APi.Data /// /// /// - public async Task> TicketByCliente(string CodInst, string CodApp, string MasterKey, int numRec = 50) + public async Task> TicketByCliente(string CodInst, string CodApp, string MasterKey, int numRec = 10) { List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); - dbResult = dbController.TicketGetFilt(true, CodApp, CodInst, MasterKey, numRec); + dbResult = dbController.TicketGetFilt(true, TipologiaTicket.ND, CodApp, CodInst, MasterKey, numRec); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB per TicketByCliente: {ts.TotalMilliseconds} ms"); @@ -553,181 +792,5 @@ namespace LiMan.APi.Data #endregion Public Methods - #region Protected Methods - - /// - /// Recupera statistiche correnti - /// - /// - protected async Task getCurrStats() - { - DateTime adesso = DateTime.Now; - SampleStats answ = new SampleStats() - { - Name = "ApiStats" - }; - // in primis check data/ora prima/ultima scrittura del set... (2 date, lista chiavi gestite) - string rawData = await getRSV(rKeySampleStats); - if (rawData != null) - { - answ = JsonConvert.DeserializeObject(rawData); - // aggiorno ultimo controllo e salvo... - answ.DtLast = adesso; - // salvo! - await setCurrStats(answ); - } - // controllo se scadute... - if (adesso.Subtract(answ.DtFirst).TotalMinutes > 60) - { - // se scaduto --> registrazione set sul DB (async), resettando i vari contatori... - bool salvato = await saveStatsToDb(answ.VList); - // inizio NUOVO set vuoto con record corrente - answ = new SampleStats() - { - Name = "ApiStats" - }; - // salvo! - await setCurrStats(answ); - } - // restituisco record! - return answ; - } - - /// - /// Recupero chiave da redis - /// - /// - /// - protected async Task getRSV(string rKey) - { - string answ = await _redisCacheClient.GetDbFromConfiguration().GetAsync(rKey); - return answ; - } - - /// - /// Resetta contatore x la chiave redis indicata... - /// - /// - protected async Task redCountClear(string rKey) - { - bool answ = false; - int currCount = 0; - answ = await setRSV(rKey, currCount, 2 * hourTTL); - return answ; - } - /// - /// Recupera contatore x la chiave redis indicata... - /// - /// - protected async Task redCount(string rKey) - { - int currCount = 0; - string rawVal = await getRSV(rKey); - if (!string.IsNullOrEmpty(rawVal)) - { - int.TryParse(rawVal, out currCount); - } - return currCount; - } - - /// - /// Incrementa contatore x la chiave redis indicata... - /// - /// - protected async Task redCountIncr(string rKey) - { - bool answ = false; - int currCount = 0; - string rawVal = await getRSV(rKey); - if (!string.IsNullOrEmpty(rawVal)) - { - int.TryParse(rawVal, out currCount); - } - currCount++; - answ = await setRSV(rKey, currCount, 2 * hourTTL); - return answ; - } - - /// - /// Salva statistiche correnti - /// - /// - protected async Task setCurrStats(SampleStats newVal) - { - bool answ = false; - string rawData = JsonConvert.SerializeObject(newVal); - answ = await setRSV(rKeySampleStats, rawData, 24 * hourTTL); - return answ; - } - - /// - /// Salvataggio chiave in redis - /// - /// - /// - /// - /// - protected async Task setRSV(string rKey, string rVal, int ttlSec) - { - bool fatto = false; - await _redisCacheClient.GetDbFromConfiguration().AddAsync(rKey, rVal, DateTimeOffset.Now.AddSeconds(ttlSec)); - fatto = true; - return fatto; - } - - /// - /// Salvataggio chiave in redis - /// - /// - /// - /// - /// - protected async Task setRSV(string rKey, int rValInt, int ttlSec) - { - bool fatto = false; - await _redisCacheClient.GetDbFromConfiguration().AddAsync(rKey, rValInt, DateTimeOffset.Now.AddSeconds(ttlSec)); - fatto = true; - return fatto; - } - - #endregion Protected Methods - - #region Private Methods - - /// - /// Parametri per generare opzioni cache - /// - /// Fattore di moltiplica cache (se 1 --> 2 e 5 min) - /// - private DistributedCacheEntryOptions cacheOpt(int multFact) - { - var numSecAbsExp = multFact <= 0 ? chAbsExp : chAbsExp * multFact; - var numSecSliExp = multFact <= 0 ? chSliExp : chSliExp * multFact; - return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddSeconds(numSecAbsExp)).SetSlidingExpiration(TimeSpan.FromSeconds(numSecSliExp)); - } - - #endregion Private Methods - -#if false - /// - /// Elenco ticket dato licenza (limitato a maxRec) - /// - /// - /// - /// - public async Task> TicketByLic(int idxLic, int maxRec = 100) - { - List dbResult = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - - dbResult = dbController.TicketGetByLic(idxLic, maxRec); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"Effettuata lettura da DB per TicketByLic: {ts.TotalMilliseconds} ms"); - - return await Task.FromResult(dbResult); - } -#endif } } \ No newline at end of file diff --git a/LiMan.Api/Development/unsafe_uploads/.placeholder.file b/LiMan.Api/Development/unsafe_uploads/.placeholder.file new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/LiMan.Api/Development/unsafe_uploads/.placeholder.file @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LiMan.Api/LiMan.APi.csproj b/LiMan.Api/LiMan.APi.csproj index 61666c4..5b11f5c 100644 --- a/LiMan.Api/LiMan.APi.csproj +++ b/LiMan.Api/LiMan.APi.csproj @@ -31,11 +31,20 @@ + + PreserveNewest + Always Always + + PreserveNewest + + + PreserveNewest + \ No newline at end of file diff --git a/LiMan.Api/LiMan.APi.xml b/LiMan.Api/LiMan.APi.xml index 76c280d..41bbade 100644 --- a/LiMan.Api/LiMan.APi.xml +++ b/LiMan.Api/LiMan.APi.xml @@ -93,6 +93,38 @@ Codice univoco impiego licenza + + + Controller caricamento file + + + + + Dataservice x accesso DB + + + + + Init generico + + + + + + Caricamento file effettivo via POST + + TicketId x riferimento + Elenco files da caricare + + + + + Caricamento file effettivo via POST + + TicketId x riferimento + Elenco files da caricare + + Controller livello INSTALLAZIONI @@ -153,7 +185,7 @@ Chiave licenza da validare - + POST api/licenza Recupera dati Licenza Applicativa (id licenza + num utenze) dati cliente + programma + licenza ATTUALE @@ -192,7 +224,7 @@ GET api/ticket/id - Recupera elenco applicativi dati cliente + Recupera elenco Ticket dato cliente / applicazione / chiave Codice cliente/Installazione Codice Applicazione @@ -210,29 +242,9 @@ Classe astrazione accesso dati - + - Classe Accesso metodi DB - - - - - TTL da 1 h x cache Redis - - - - - Chiave redis x statistiche in acquisizione - - - - - Chiave redis x statistiche in acquisizione - - - - - Chiave redis x licenze da MasterKey + Elenco obj in cache @@ -246,6 +258,41 @@ NON estende oltre il tempo massimo di validità della cache (chAbsExp) + + + TTL da 1 h x cache Redis + + + + + Chiave redis x attivazioni da IdxLic + + + + + Chiave redis x licenze da MasterKey + + + + + Chiave redis x statistiche in acquisizione + + + + + Chiave redis x statistiche in acquisizione + + + + + TTL da 1 min x cache Redis + + + + + Classe Accesso metodi DB + + Init classe @@ -256,6 +303,74 @@ + + + Parametri per generare opzioni cache + + Fattore di moltiplica cache (se 1 --> 2 e 5 min) + + + + + Recupera statistiche correnti + + + + + + Recupero chiave da redis + + + + + + + Recupera contatore x la chiave redis indicata... + + + + + + Resetta contatore x la chiave redis indicata... + + + + + + Incrementa contatore x la chiave redis indicata... + + + + + + Salva statistiche correnti + + + + + + Salvataggio chiave in redis + + + + + + + + + Salvataggio chiave in redis + + + + + + + + + Registra in cache chiave se non fosse già in elenco + + + Elenco licenze dato cliente @@ -278,8 +393,8 @@ Elenco Attivaizoni da ID Licenza master - Idx Licenza Master - Indica se nascondere i dati sensibili + Idx Licenza Master + Indica se nascondere i dati sensibili @@ -329,6 +444,21 @@ Dispose classe + + + Esegue aggiunta file dato ticket e list uploadResult + + Identificativo del ticket + Directory di salvataggio dei file + lista risultati della funzione di upload + + + + + invalida tutta la cache in caso di update + + + Elenco licenze dato cliente @@ -416,67 +546,5 @@ - - - Recupera statistiche correnti - - - - - - Recupero chiave da redis - - - - - - - Resetta contatore x la chiave redis indicata... - - - - - - Recupera contatore x la chiave redis indicata... - - - - - - Incrementa contatore x la chiave redis indicata... - - - - - - Salva statistiche correnti - - - - - - Salvataggio chiave in redis - - - - - - - - - Salvataggio chiave in redis - - - - - - - - - Parametri per generare opzioni cache - - Fattore di moltiplica cache (se 1 --> 2 e 5 min) - - diff --git a/LiMan.Api/Production/unsafe_uploads/.placeholder.file b/LiMan.Api/Production/unsafe_uploads/.placeholder.file new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/LiMan.Api/Production/unsafe_uploads/.placeholder.file @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LiMan.Api/Staging/unsafe_uploads/.placeholder.file b/LiMan.Api/Staging/unsafe_uploads/.placeholder.file new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/LiMan.Api/Staging/unsafe_uploads/.placeholder.file @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LiMan.DB/Controllers/DbController.cs b/LiMan.DB/Controllers/DbController.cs index c315665..ca492d0 100644 --- a/LiMan.DB/Controllers/DbController.cs +++ b/LiMan.DB/Controllers/DbController.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using NLog; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -277,6 +278,74 @@ namespace LiMan.DB.Controllers //Log.Info("Dispose di GWMSController"); } + /// + /// Elenco files attach da registrare + /// + /// Identificativo del ticket + /// Directory di salvataggio dei file + /// lista risultati della funzione di upload + /// + public bool FileAdd(int idxTicket, string baseDir, List fileUploaded) + { + bool fatto = false; + if (fileUploaded == null || fileUploaded.Count == 0) + { + Log.Error("Errore FileAdd: fileUploaded è vuoto/nullo"); + } + else + { + using (LMDbContext localDbCtx = new LMDbContext(_configuration)) + { + // verifico Ticket sia esistente + var currTicket = localDbCtx + .DbSetTicket + .Where(x => x.IdxTicket == idxTicket) + .FirstOrDefault(); + + if (currTicket != null) + { + var newFiles = fileUploaded + .Select(x => new FileAttachModel() + { + IdxTicket = idxTicket, + OriginalName = x.FileName, + StorageName = x.StoredFileName, + DtEvent = DateTime.Now, + FullStoragePath = Path.Combine(baseDir, x.StoredFileName) + }).ToList(); + + localDbCtx + .DbSetFileAttach + .AddRange(newFiles); + + localDbCtx.SaveChanges(); + + fatto = true; + } + } + } + return fatto; + } + + /// + /// Elenco file registrati dato ticket id + /// + /// Identificativo del ticket + /// + public List FileGetFilt(int idxTicket) + { + List dbResult = new List(); + using (LMDbContext localDbCtx = new LMDbContext(_configuration)) + { + // recupero locenza... + dbResult = localDbCtx + .DbSetFileAttach + .Where(x => x.IdxTicket == idxTicket) + .ToList(); + } + return dbResult; + } + public List GetApplicativiFilt(bool OnlyActive, string CodApp, string CodInst, bool hideData) { List dbResult = new List(); @@ -775,7 +844,8 @@ namespace LiMan.DB.Controllers ContactPhone = currRequest.ContactPhone, ReqBody = currRequest.ReqBody, Status = Enum.StatoRichiesta.Richiesta, - Tipo = Enum.TipoLicenza.UserKey + Tipo = Enum.TipoLicenza.UserKey, + TType = currRequest.Tipo }; localDbCtx @@ -790,12 +860,12 @@ namespace LiMan.DB.Controllers return fatto; } - public List TicketGetFilt(bool onlyOpen, string CodApp, string CodInst, string MasterKey, int maxNum) + public List TicketGetFilt(bool onlyOpen, TipologiaTicket Tipo, string CodApp, string CodInst, string MasterKey, int maxNum) { List dbResult = new List(); using (LMDbContext localDbCtx = new LMDbContext(_configuration)) { - // recupero locenza... + // recupero licenza... var currLic = localDbCtx .DbSetLicenze .Where(x => x.CodApp == CodApp && x.CodInst == CodInst && x.Chiave == MasterKey) @@ -805,8 +875,8 @@ namespace LiMan.DB.Controllers { dbResult = localDbCtx .DbSetTicket - .Where(x => x.IdxLic == currLic.IdxLic) - .OrderByDescending(x => x.DtReq) + .Where(x => x.IdxLic == currLic.IdxLic && (x.Status <= StatoRichiesta.Valutazione || !onlyOpen) && (x.TType == Tipo || Tipo == TipologiaTicket.ND)) + .OrderByDescending(x => x.IdxTicket) .Take(maxNum) .Select(x => new TicketDTO { @@ -823,7 +893,7 @@ namespace LiMan.DB.Controllers SupplAnsw = x.SupplAnsw, SupplEmail = x.SupplEmail, SupplUserCode = x.SupplUserCode, - Tipo= x.Tipo + Tipo = x.Tipo }) .ToList(); } diff --git a/LiMan.DB/DBModels/FileAttachModel.cs b/LiMan.DB/DBModels/FileAttachModel.cs new file mode 100644 index 0000000..97c748e --- /dev/null +++ b/LiMan.DB/DBModels/FileAttachModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using static LiMan.DB.Enum; + +#nullable disable + +namespace LiMan.DB.DBModels +{ + // + // This is here so CodeMaid doesn't reorganize this document + // + //[Index(nameof(Installazione), nameof(Active), nameof(DiskStatus))] + [Table("FileAttach")] + public partial class FileAttachModel + { + #region Public Properties + + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int IdxFileAttach { get; set; } + + /// + /// Id del ticket cui è collegato + /// + public int IdxTicket { get; set; } + + /// + /// DataOra evento + /// + public DateTime DtEvent { get; set; } = DateTime.Now; + + /// + /// Codice univoco della sub licenza (opzionale) + /// + public string OriginalName { get; set; } = ""; + + /// + /// Nome con cui è salvato il file localmente + /// + public string StorageName { get; set; } = ""; + + /// + /// Path completo del file + /// + public string FullStoragePath { get; set; } = ""; + + [ForeignKey("IdxTicket")] + public virtual TicketModel TicketNav { get; set; } + + #endregion Public Properties + } +} \ No newline at end of file diff --git a/LiMan.DB/DBModels/TicketModel.cs b/LiMan.DB/DBModels/TicketModel.cs index ee3f55a..ad03fbf 100644 --- a/LiMan.DB/DBModels/TicketModel.cs +++ b/LiMan.DB/DBModels/TicketModel.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using Core; +using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using static LiMan.DB.Enum; @@ -11,7 +11,6 @@ namespace LiMan.DB.DBModels // // This is here so CodeMaid doesn't reorganize this document // - //[Index(nameof(Installazione), nameof(Active), nameof(DiskStatus))] [Table("TicketLog")] public partial class TicketModel { @@ -22,6 +21,11 @@ namespace LiMan.DB.DBModels public DateTime DtReq { get; set; } = DateTime.Now; + /// + /// Tipologia di ticket + /// + public TipologiaTicket TType { get; set; } = TipologiaTicket.Licenze; + /// /// Tipologia di licenza gestita /// diff --git a/LiMan.DB/Enum.cs b/LiMan.DB/Enum.cs index 7c7feec..1ed38f6 100644 --- a/LiMan.DB/Enum.cs +++ b/LiMan.DB/Enum.cs @@ -51,6 +51,8 @@ namespace LiMan.DB CheckSumKey } + + #endregion Public Enums } } \ No newline at end of file diff --git a/LiMan.DB/LMDbContext.cs b/LiMan.DB/LMDbContext.cs index 27a1305..3b2ee9d 100644 --- a/LiMan.DB/LMDbContext.cs +++ b/LiMan.DB/LMDbContext.cs @@ -49,6 +49,7 @@ namespace LiMan.DB #region Public Properties public virtual DbSet DbSetApp { get; set; } + public virtual DbSet DbSetFileAttach { get; set; } public virtual DbSet DbSetInst { get; set; } public virtual DbSet DbSetLicenze { get; set; } public virtual DbSet DbSetLogCall { get; set; } @@ -57,7 +58,7 @@ namespace LiMan.DB public virtual DbSet DbSetTicket { get; set; } #endregion Public Properties - + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); diff --git a/LiMan.DB/Migrations/20211220181913_AddFileAttach.Designer.cs b/LiMan.DB/Migrations/20211220181913_AddFileAttach.Designer.cs new file mode 100644 index 0000000..4717162 --- /dev/null +++ b/LiMan.DB/Migrations/20211220181913_AddFileAttach.Designer.cs @@ -0,0 +1,371 @@ +// +using System; +using LiMan.DB; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace LiMan.DB.Migrations +{ + [DbContext(typeof(LMDbContext))] + [Migration("20211220181913_AddFileAttach")] + partial class AddFileAttach + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.10") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("LiMan.DB.DBModels.ApplicativoModel", b => + { + b.Property("CodApp") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Descrizione") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("CodApp"); + + b.ToTable("Applicativi"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.Property("IdxFileAttach") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DtEvent") + .HasColumnType("datetime2"); + + b.Property("FullStoragePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxTicket") + .HasColumnType("int"); + + b.Property("OriginalName") + .HasColumnType("nvarchar(max)"); + + b.Property("StorageName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("IdxFileAttach"); + + b.HasIndex("IdxTicket"); + + b.ToTable("FileAttach"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.InstallazioneModel", b => + { + b.Property("CodInst") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Cliente") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Contatto") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Descrizione") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Email") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("CodInst"); + + b.ToTable("Installazioni"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.Property("IdxLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(50)"); + + b.Property("CodInst") + .HasColumnType("nvarchar(50)"); + + b.Property("DataEnigma") + .HasColumnType("datetime2"); + + b.Property("Descrizione") + .HasColumnType("nvarchar(max)"); + + b.Property("Enigma") + .HasColumnType("nvarchar(max)"); + + b.Property("Locked") + .HasColumnType("bit"); + + b.Property("NumLicenze") + .HasColumnType("int"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Scadenza") + .HasColumnType("datetime2"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxLic"); + + b.HasIndex("CodApp"); + + b.HasIndex("CodInst"); + + b.ToTable("Licenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogCallModel", b => + { + b.Property("DataRif") + .HasColumnType("datetime2"); + + b.Property("CodInst") + .HasColumnType("nvarchar(450)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(450)"); + + b.Property("TargetUrl") + .HasColumnType("nvarchar(450)"); + + b.Property("NumCall") + .HasColumnType("int"); + + b.HasKey("DataRif", "CodInst", "CodApp", "TargetUrl"); + + b.ToTable("LogCall"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogLicenzaModel", b => + { + b.Property("IdxLogLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(50)"); + + b.Property("CodInst") + .HasColumnType("nvarchar(50)"); + + b.Property("Descrizione") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("NumLicenze") + .HasColumnType("int"); + + b.Property("Scadenza") + .HasColumnType("datetime2"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxLogLic"); + + b.HasIndex("CodApp"); + + b.HasIndex("CodInst"); + + b.HasIndex("IdxLic"); + + b.ToTable("LogLicenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.SubLicenzaModel", b => + { + b.Property("IdxSubLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodImpiego") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.Property("VetoUnlock") + .HasColumnType("datetime2"); + + b.HasKey("IdxSubLic"); + + b.HasIndex("IdxLic"); + + b.ToTable("SubLicenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.TicketModel", b => + { + b.Property("IdxTicket") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CodImpiego") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactName") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactPhone") + .HasColumnType("nvarchar(max)"); + + b.Property("DtReq") + .HasColumnType("datetime2"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("IdxSubLic") + .HasColumnType("int"); + + b.Property("ReqBody") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SupplAnsw") + .HasColumnType("nvarchar(max)"); + + b.Property("SupplEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("SupplUserCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxTicket"); + + b.HasIndex("IdxLic"); + + b.ToTable("TicketLog"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.HasOne("LiMan.DB.DBModels.TicketModel", "TicketNav") + .WithMany() + .HasForeignKey("IdxTicket") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.ApplicativoModel", "ApplicativoNav") + .WithMany() + .HasForeignKey("CodApp"); + + b.HasOne("LiMan.DB.DBModels.InstallazioneModel", "InstallazioneNav") + .WithMany() + .HasForeignKey("CodInst"); + + b.Navigation("ApplicativoNav"); + + b.Navigation("InstallazioneNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogLicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.ApplicativoModel", "ApplicativoNav") + .WithMany() + .HasForeignKey("CodApp"); + + b.HasOne("LiMan.DB.DBModels.InstallazioneModel", "InstallazioneNav") + .WithMany() + .HasForeignKey("CodInst"); + + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany() + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicativoNav"); + + b.Navigation("InstallazioneNav"); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.SubLicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany("Attivazioni") + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.TicketModel", b => + { + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany("Tickets") + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.Navigation("Attivazioni"); + + b.Navigation("Tickets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LiMan.DB/Migrations/20211220181913_AddFileAttach.cs b/LiMan.DB/Migrations/20211220181913_AddFileAttach.cs new file mode 100644 index 0000000..2c547b8 --- /dev/null +++ b/LiMan.DB/Migrations/20211220181913_AddFileAttach.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace LiMan.DB.Migrations +{ + public partial class AddFileAttach : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FileAttach", + columns: table => new + { + IdxFileAttach = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + IdxTicket = table.Column(type: "int", nullable: false), + DtEvent = table.Column(type: "datetime2", nullable: false), + OriginalName = table.Column(type: "nvarchar(max)", nullable: true), + StorageName = table.Column(type: "nvarchar(max)", nullable: true), + FullStoragePath = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_FileAttach", x => x.IdxFileAttach); + table.ForeignKey( + name: "FK_FileAttach_TicketLog_IdxTicket", + column: x => x.IdxTicket, + principalTable: "TicketLog", + principalColumn: "IdxTicket", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_FileAttach_IdxTicket", + table: "FileAttach", + column: "IdxTicket"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FileAttach"); + } + } +} diff --git a/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.Designer.cs b/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.Designer.cs new file mode 100644 index 0000000..ac5070b --- /dev/null +++ b/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.Designer.cs @@ -0,0 +1,374 @@ +// +using System; +using LiMan.DB; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace LiMan.DB.Migrations +{ + [DbContext(typeof(LMDbContext))] + [Migration("20211221090447_UpdTicket_TType")] + partial class UpdTicket_TType + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.10") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("LiMan.DB.DBModels.ApplicativoModel", b => + { + b.Property("CodApp") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Descrizione") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("CodApp"); + + b.ToTable("Applicativi"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.Property("IdxFileAttach") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DtEvent") + .HasColumnType("datetime2"); + + b.Property("FullStoragePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxTicket") + .HasColumnType("int"); + + b.Property("OriginalName") + .HasColumnType("nvarchar(max)"); + + b.Property("StorageName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("IdxFileAttach"); + + b.HasIndex("IdxTicket"); + + b.ToTable("FileAttach"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.InstallazioneModel", b => + { + b.Property("CodInst") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Cliente") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Contatto") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Descrizione") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Email") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("CodInst"); + + b.ToTable("Installazioni"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.Property("IdxLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(50)"); + + b.Property("CodInst") + .HasColumnType("nvarchar(50)"); + + b.Property("DataEnigma") + .HasColumnType("datetime2"); + + b.Property("Descrizione") + .HasColumnType("nvarchar(max)"); + + b.Property("Enigma") + .HasColumnType("nvarchar(max)"); + + b.Property("Locked") + .HasColumnType("bit"); + + b.Property("NumLicenze") + .HasColumnType("int"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Scadenza") + .HasColumnType("datetime2"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxLic"); + + b.HasIndex("CodApp"); + + b.HasIndex("CodInst"); + + b.ToTable("Licenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogCallModel", b => + { + b.Property("DataRif") + .HasColumnType("datetime2"); + + b.Property("CodInst") + .HasColumnType("nvarchar(450)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(450)"); + + b.Property("TargetUrl") + .HasColumnType("nvarchar(450)"); + + b.Property("NumCall") + .HasColumnType("int"); + + b.HasKey("DataRif", "CodInst", "CodApp", "TargetUrl"); + + b.ToTable("LogCall"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogLicenzaModel", b => + { + b.Property("IdxLogLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodApp") + .HasColumnType("nvarchar(50)"); + + b.Property("CodInst") + .HasColumnType("nvarchar(50)"); + + b.Property("Descrizione") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("NumLicenze") + .HasColumnType("int"); + + b.Property("Scadenza") + .HasColumnType("datetime2"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxLogLic"); + + b.HasIndex("CodApp"); + + b.HasIndex("CodInst"); + + b.HasIndex("IdxLic"); + + b.ToTable("LogLicenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.SubLicenzaModel", b => + { + b.Property("IdxSubLic") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Chiave") + .HasColumnType("nvarchar(max)"); + + b.Property("CodImpiego") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.Property("VetoUnlock") + .HasColumnType("datetime2"); + + b.HasKey("IdxSubLic"); + + b.HasIndex("IdxLic"); + + b.ToTable("SubLicenze"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.TicketModel", b => + { + b.Property("IdxTicket") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CodImpiego") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactName") + .HasColumnType("nvarchar(max)"); + + b.Property("ContactPhone") + .HasColumnType("nvarchar(max)"); + + b.Property("DtReq") + .HasColumnType("datetime2"); + + b.Property("IdxLic") + .HasColumnType("int"); + + b.Property("IdxSubLic") + .HasColumnType("int"); + + b.Property("ReqBody") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SupplAnsw") + .HasColumnType("nvarchar(max)"); + + b.Property("SupplEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("SupplUserCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TType") + .HasColumnType("int"); + + b.Property("Tipo") + .HasColumnType("int"); + + b.HasKey("IdxTicket"); + + b.HasIndex("IdxLic"); + + b.ToTable("TicketLog"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.HasOne("LiMan.DB.DBModels.TicketModel", "TicketNav") + .WithMany() + .HasForeignKey("IdxTicket") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.ApplicativoModel", "ApplicativoNav") + .WithMany() + .HasForeignKey("CodApp"); + + b.HasOne("LiMan.DB.DBModels.InstallazioneModel", "InstallazioneNav") + .WithMany() + .HasForeignKey("CodInst"); + + b.Navigation("ApplicativoNav"); + + b.Navigation("InstallazioneNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LogLicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.ApplicativoModel", "ApplicativoNav") + .WithMany() + .HasForeignKey("CodApp"); + + b.HasOne("LiMan.DB.DBModels.InstallazioneModel", "InstallazioneNav") + .WithMany() + .HasForeignKey("CodInst"); + + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany() + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApplicativoNav"); + + b.Navigation("InstallazioneNav"); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.SubLicenzaModel", b => + { + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany("Attivazioni") + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.TicketModel", b => + { + b.HasOne("LiMan.DB.DBModels.LicenzaModel", "LicenzaNav") + .WithMany("Tickets") + .HasForeignKey("IdxLic") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LicenzaNav"); + }); + + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => + { + b.Navigation("Attivazioni"); + + b.Navigation("Tickets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.cs b/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.cs new file mode 100644 index 0000000..555f5a6 --- /dev/null +++ b/LiMan.DB/Migrations/20211221090447_UpdTicket_TType.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace LiMan.DB.Migrations +{ + public partial class UpdTicket_TType : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TType", + table: "TicketLog", + type: "int", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TType", + table: "TicketLog"); + } + } +} diff --git a/LiMan.DB/Migrations/LMDbContextModelSnapshot.cs b/LiMan.DB/Migrations/LMDbContextModelSnapshot.cs index b52d5fc..0d6859c 100644 --- a/LiMan.DB/Migrations/LMDbContextModelSnapshot.cs +++ b/LiMan.DB/Migrations/LMDbContextModelSnapshot.cs @@ -35,6 +35,35 @@ namespace LiMan.DB.Migrations b.ToTable("Applicativi"); }); + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.Property("IdxFileAttach") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DtEvent") + .HasColumnType("datetime2"); + + b.Property("FullStoragePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IdxTicket") + .HasColumnType("int"); + + b.Property("OriginalName") + .HasColumnType("nvarchar(max)"); + + b.Property("StorageName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("IdxFileAttach"); + + b.HasIndex("IdxTicket"); + + b.ToTable("FileAttach"); + }); + modelBuilder.Entity("LiMan.DB.DBModels.InstallazioneModel", b => { b.Property("CodInst") @@ -247,6 +276,9 @@ namespace LiMan.DB.Migrations b.Property("SupplUserCode") .HasColumnType("nvarchar(max)"); + b.Property("TType") + .HasColumnType("int"); + b.Property("Tipo") .HasColumnType("int"); @@ -257,6 +289,17 @@ namespace LiMan.DB.Migrations b.ToTable("TicketLog"); }); + modelBuilder.Entity("LiMan.DB.DBModels.FileAttachModel", b => + { + b.HasOne("LiMan.DB.DBModels.TicketModel", "TicketNav") + .WithMany() + .HasForeignKey("IdxTicket") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TicketNav"); + }); + modelBuilder.Entity("LiMan.DB.DBModels.LicenzaModel", b => { b.HasOne("LiMan.DB.DBModels.ApplicativoModel", "ApplicativoNav") diff --git a/LiMan.UI/Components/Tickets.razor.cs b/LiMan.UI/Components/Tickets.razor.cs index 9c867da..aaf2e3d 100644 --- a/LiMan.UI/Components/Tickets.razor.cs +++ b/LiMan.UI/Components/Tickets.razor.cs @@ -48,6 +48,11 @@ namespace LiMan.UI.Components } } + protected override async Task OnInitializedAsync() + { + await ReloadAllData(); + } + private bool isLoading { get; set; } = false; [Inject] diff --git a/LiMan.UI/LiMan.UI.csproj b/LiMan.UI/LiMan.UI.csproj index 8c5acef..d7fd238 100644 --- a/LiMan.UI/LiMan.UI.csproj +++ b/LiMan.UI/LiMan.UI.csproj @@ -2,7 +2,7 @@ net5.0 - 1.1.2111.1815 + 1.1.2201.1317 LiMan.UI LiMan.UI diff --git a/LiMan.UI/Pages/ListaTicket.razor b/LiMan.UI/Pages/ListaTicket.razor new file mode 100644 index 0000000..c45a328 --- /dev/null +++ b/LiMan.UI/Pages/ListaTicket.razor @@ -0,0 +1,17 @@ +@page "/ListaTicket" + +@using LiMan.UI.Components +@using LiMan.UI.Data + +@inject MessageService AppMService + + + +@code { + protected override void OnInitialized() + { + AppMService.ShowSearch = false; + AppMService.PageName = "Elenco Tickets"; + AppMService.PageIcon = "oi oi-list-rich"; + } +} \ No newline at end of file diff --git a/LiMan.UI/Properties/PublishProfiles/IIS02.pubxml b/LiMan.UI/Properties/PublishProfiles/IIS02.pubxml index aaca518..98761f9 100644 --- a/LiMan.UI/Properties/PublishProfiles/IIS02.pubxml +++ b/LiMan.UI/Properties/PublishProfiles/IIS02.pubxml @@ -13,7 +13,7 @@ by editing this MSBuild file. In order to learn more about this please visit htt True False 34200ca2-489c-435a-a60b-34de7b7ba04d - https://IIS02:8172/MsDeploy.axd + https://IIS02.egalware.com:8172/MsDeploy.axd Default Web Site/ELM.UI False diff --git a/LiMan.UI/Resources/ChangeLog.html b/LiMan.UI/Resources/ChangeLog.html index ab0a3aa..fa2c866 100644 --- a/LiMan.UI/Resources/ChangeLog.html +++ b/LiMan.UI/Resources/ChangeLog.html @@ -1,6 +1,6 @@ License Manager -

Versione: 1.1.2111.1815

+

Versione: 1.1.2201.1317


Note di rilascio:
    diff --git a/LiMan.UI/Resources/VersNum.txt b/LiMan.UI/Resources/VersNum.txt index c98fbef..c102023 100644 --- a/LiMan.UI/Resources/VersNum.txt +++ b/LiMan.UI/Resources/VersNum.txt @@ -1 +1 @@ -1.1.2111.1815 +1.1.2201.1317 diff --git a/LiMan.UI/Resources/manifest.xml b/LiMan.UI/Resources/manifest.xml index f0effe2..a6c0709 100644 --- a/LiMan.UI/Resources/manifest.xml +++ b/LiMan.UI/Resources/manifest.xml @@ -1,6 +1,6 @@ - 1.1.2111.1815 + 1.1.2201.1317 https://nexus.steamware.net/repository/SWS/LiMan/stable/LAST/LiMan.UI.zip https://nexus.steamware.net/repository/SWS/LiMan/stable/LAST/ChangeLog.html false diff --git a/LiMan.UI/Shared/NavMenu.razor b/LiMan.UI/Shared/NavMenu.razor index afe66be..506e8dc 100644 --- a/LiMan.UI/Shared/NavMenu.razor +++ b/LiMan.UI/Shared/NavMenu.razor @@ -29,6 +29,11 @@ +