From e4fd85c9cb32fde092f57682bde9010e217cabc4 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 23 Oct 2024 19:21:11 +0200 Subject: [PATCH] LAND - import scheduler da STATS - ok compilazione (da completare con esecuzione REST call) --- MP-LAND.sln | 8 + MP.Data/Controllers/MpLandController.cs | 421 ++++++++++++++++++++++++ MP.Data/MoonProContext.cs | 9 + MP.Data/Services/TaskService.cs | 324 ++++++++++++++++++ MP.Land/Components/TLResult.razor | 7 + MP.Land/Components/TLResult.razor.cs | 56 ++++ MP.Land/Components/TaskEdit.razor | 101 ++++++ MP.Land/Components/TaskEdit.razor.cs | 47 +++ MP.Land/Components/TaskExeList.razor | 85 +++++ MP.Land/Components/TaskExeList.razor.cs | 113 +++++++ MP.Land/MP.Land.csproj | 3 +- MP.Land/Pages/TaskScheduler.razor | 166 ++++++++++ MP.Land/Pages/TaskScheduler.razor.cs | 355 ++++++++++++++++++++ MP.Land/Resources/ChangeLog.html | 2 +- MP.Land/Resources/VersNum.txt | 2 +- MP.Land/Resources/manifest.xml | 2 +- MP.Land/Shared/NavMenu.razor | 8 + MP.Land/Startup.cs | 4 +- MP.Land/appsettings.json | 2 + MP.Stats/Components/TLResult.razor | 2 +- MP.Stats/Components/TaskExeList.razor | 25 +- MP.Stats/MP.Stats.csproj | 4 +- MP.Stats/Pages/TaskScheduler.razor | 2 +- MP.Stats/Resources/ChangeLog.html | 2 +- MP.Stats/Resources/VersNum.txt | 2 +- MP.Stats/Resources/manifest.xml | 2 +- 26 files changed, 1732 insertions(+), 22 deletions(-) create mode 100644 MP.Data/Controllers/MpLandController.cs create mode 100644 MP.Data/Services/TaskService.cs create mode 100644 MP.Land/Components/TLResult.razor create mode 100644 MP.Land/Components/TLResult.razor.cs create mode 100644 MP.Land/Components/TaskEdit.razor create mode 100644 MP.Land/Components/TaskEdit.razor.cs create mode 100644 MP.Land/Components/TaskExeList.razor create mode 100644 MP.Land/Components/TaskExeList.razor.cs create mode 100644 MP.Land/Pages/TaskScheduler.razor create mode 100644 MP.Land/Pages/TaskScheduler.razor.cs diff --git a/MP-LAND.sln b/MP-LAND.sln index 7c54065e..85bcdda1 100644 --- a/MP-LAND.sln +++ b/MP-LAND.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.AppAuth", "MP.AppAuth\MP EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Egw.Core", "Egw.Core\Egw.Core.csproj", "{D3D348EF-1313-43DF-94FB-28CD38B68212}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.Data", "MP.Data\MP.Data.csproj", "{EE871AE5-9B5E-493E-8E59-F77234979AD7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug_LiManDebug|Any CPU = Debug_LiManDebug|Any CPU @@ -34,6 +36,12 @@ Global {D3D348EF-1313-43DF-94FB-28CD38B68212}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3D348EF-1313-43DF-94FB-28CD38B68212}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3D348EF-1313-43DF-94FB-28CD38B68212}.Release|Any CPU.Build.0 = Release|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Debug_LiManDebug|Any CPU.ActiveCfg = Debug|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Debug_LiManDebug|Any CPU.Build.0 = Debug|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE871AE5-9B5E-493E-8E59-F77234979AD7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MP.Data/Controllers/MpLandController.cs b/MP.Data/Controllers/MpLandController.cs new file mode 100644 index 00000000..10112783 --- /dev/null +++ b/MP.Data/Controllers/MpLandController.cs @@ -0,0 +1,421 @@ +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using MP.Data.DatabaseModels; +using MP.Data.Objects; +using NLog; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using static MP.Data.Objects.Enums; + +namespace MP.Data.Controllers +{ + public class MpLandController : IDisposable + { + #region Public Constructors + + public MpLandController(IConfiguration configuration) + { + _configuration = configuration; + Log.Info("Avviato MpLandController"); + } + + #endregion Public Constructors + + #region Public Methods + + public DateTime CalcNextExe(TaskListModel taskRec) + { + DateTime dtNext = DateTime.Today; + try + { + // calcolo next exec da tipo... + switch (taskRec.Freq) + { + case TaskFreqType.ND: + dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad); + break; + + case TaskFreqType.Sec: + dtNext = taskRec.DtLastExec.AddSeconds(taskRec.Cad); + break; + + case TaskFreqType.Min: + dtNext = taskRec.DtLastExec.AddMinutes(taskRec.Cad); + break; + + case TaskFreqType.Hour: + dtNext = taskRec.DtLastExec.AddHours(taskRec.Cad); + break; + + case TaskFreqType.Day: + dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad); + break; + + case TaskFreqType.Week: + dtNext = taskRec.DtLastExec.AddDays(7 * taskRec.Cad); + break; + + case TaskFreqType.Month: + dtNext = taskRec.DtLastExec.AddMonths(taskRec.Cad); + break; + + case TaskFreqType.Year: + dtNext = taskRec.DtLastExec.AddYears(taskRec.Cad); + break; + + default: + dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad); + break; + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in CalcNextExe{Environment.NewLine}{exc}"); + } + return dtNext; + } + + /// + /// Elenco da tabella Config + /// + /// + public List ConfigGetAll() + { + List dbResult = new List(); + using (var dbCtx = new MoonProContext(_configuration)) + { + dbResult = dbCtx + .DbSetConfig + .AsNoTracking() + .OrderBy(x => x.Chiave) + .ToList(); + } + return dbResult; + } + + public void Dispose() + { + _configuration = null; + } + + /// + /// Elenco operatori + /// + /// + public List ElencoOperatori() + { + List dbResult = new List(); + using (var dbCtx = new MoonProContext(_configuration)) + { + dbResult = dbCtx + .DbOperatori + .Where(s => s.MatrOpr > 0) + .AsNoTracking() + .OrderBy(x => x.MatrOpr) + .ToList(); + } + return dbResult; + } + + /// + /// Chiamata esecuzione di un singolo task programmato + /// + /// + /// Se true rischedula successiva chiamata + /// + public TaskResultModel ExecuteTask(int TaskId, bool SchedNext) + { + TaskResultModel callRes = new TaskResultModel(); + using (var dbCtx = new MoonPro_STATSContext(_configuration)) + { + // imposto timeout a 5 min + //var currTimeout = dbCtx.Database.GetCommandTimeout(); + dbCtx.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); + try + { + DateTime dtStart = DateTime.Now; + // recupero i dati da richiamare... + var currRec = dbCtx + .DbSetTaskList + .Where(x => x.TaskId == TaskId) + .FirstOrDefault(); + if (currRec != null) + { + // recupero comando + string sqlCommand = currRec.Command; + string rawParams = currRec.Args; + callRes = dbCtx + .DbSetTaskResult + .FromSqlRaw($"EXEC {sqlCommand} {rawParams}") + .AsNoTracking() + .AsEnumerable() + .FirstOrDefault(); + DateTime dtEnd = DateTime.Now; + + // preparo record esecuzione... + TaskExecModel resRec = new TaskExecModel() + { + TaskId = TaskId, + DtStart = dtStart, + DtEnd = dtEnd, + IsError = callRes.ExecResult < 0, + Result = callRes.TextResult + }; + dbCtx + .DbSetTaskExe + .Add(resRec); + + // aggiorno record chiamata... + currRec.DtLastExec = dtStart; + currRec.LastResult = resRec.Result; + currRec.LastIsError = resRec.IsError; + currRec.LastDuration = dtEnd.Subtract(dtStart).TotalSeconds; + // solo se richiesto rischedulazione ricalcola chiamata + if (SchedNext) + { + // calcolo prossima esecuzione... + currRec.DtNextExec = CalcNextExe(currRec); + } + // segno modificato + dbCtx.Entry(currRec).State = EntityState.Modified; + + // salvo modifiche! + dbCtx.SaveChanges(); + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in ExecuteSqlCommand{Environment.NewLine}{exc}"); + } + } + return callRes; + } + + /// + /// Annulla modifiche su una specifica entity (cancel update) + /// + /// + /// + public bool RollBackEntity(object item) + { + bool answ = false; + using (var dbCtx = new MoonPro_STATSContext(_configuration)) + { + try + { + if (dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Deleted || dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Modified) + { + dbCtx.Entry(item).Reload(); + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in rollBackEntity{Environment.NewLine}{exc}"); + } + } + return answ; + } + + /// + /// Ricerca task dato tipo + num max (desc) + /// + /// TaskId da cui deriva + /// + public List TaskExecGetFilt(int TaskId, int maxRec) + { + List dbResult = new List(); + using (var dbCtx = new MoonProContext(_configuration)) + { + dbResult = dbCtx + .DbSetTaskExe + .Include(x => x.TaskListNav) + .Where(x => (x.TaskId == TaskId)) + .OrderByDescending(x => x.DtStart) + .Take(maxRec) + .ToList(); + } + return dbResult; + } + + /// + /// Upsert record TaskExec + /// + /// Record da aggiornare/inserire + /// + public bool TaskExecUpsert(TaskExecModel rec2upd) + { + bool done = false; + using (var dbCtx = new MoonProContext(_configuration)) + { + try + { + var currData = dbCtx + .DbSetTaskExe + .Where(x => x.TaskExecId == rec2upd.TaskExecId) + .FirstOrDefault(); + if (currData != null) + { + currData.TaskId = rec2upd.TaskId; + currData.DtStart = rec2upd.DtStart; + currData.DtEnd = rec2upd.DtEnd; + currData.IsError = rec2upd.IsError; + currData.Result = rec2upd.Result; + dbCtx.Entry(currData).State = EntityState.Modified; + } + else + { + dbCtx + .DbSetTaskExe + .Add(rec2upd); + } + dbCtx.SaveChanges(); + done = true; + } + catch (Exception exc) + { + Log.Error($"Eccezione in TaskExecUpsert{Environment.NewLine}{exc}"); + } + } + return done; + } + + /// + /// Ricerca task dato tipo e + /// + /// + /// + public List TaskListGetAll(Task2ExeType TType) + { + List dbResult = new List(); + using (var dbCtx = new MoonProContext(_configuration)) + { + dbResult = dbCtx + .DbSetTaskList + .Where(x => (TType == Task2ExeType.ND || x.TType == TType)) + .OrderBy(x => x.Ordinal) + .ToList(); + } + return dbResult; + } + + /// + /// Update ordinamento task + /// + /// Record da spostare x priorità + /// + public bool TaskListMove(TaskListModel rec2upd, bool moveUp) + { + bool done = false; + using (var dbCtx = new MoonProContext(_configuration)) + { + try + { + var currData = dbCtx + .DbSetTaskList + .Where(x => x.TaskId == rec2upd.TaskId) + .FirstOrDefault(); + if (currData != null) + { + int actOrdinal = currData.Ordinal; + TaskListModel? otherRec = null; + // cerco, secondo richiesta, precedente o successivo + if (moveUp) + { + otherRec = dbCtx + .DbSetTaskList + .Where(x => x.Ordinal < currData.Ordinal) + .OrderByDescending(x => x.Ordinal) + .FirstOrDefault(); + } + else + { + otherRec = dbCtx + .DbSetTaskList + .Where(x => x.Ordinal > currData.Ordinal) + .OrderBy(x => x.Ordinal) + .FirstOrDefault(); + } + // inverto ordinale SE ho record + if (otherRec != null) + { + currData.Ordinal = otherRec.Ordinal; + otherRec.Ordinal = actOrdinal; + dbCtx.Entry(currData).State = EntityState.Modified; + dbCtx.Entry(otherRec).State = EntityState.Modified; + } + } + //salvo + dbCtx.SaveChanges(); + done = true; + } + catch (Exception exc) + { + Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}"); + } + } + return done; + } + + /// + /// Upsert record TaskList + /// + /// Record da aggiornare/inserire + /// + public bool TaskListUpsert(TaskListModel rec2upd) + { + bool done = false; + using (var dbCtx = new MoonProContext(_configuration)) + { + try + { + var currData = dbCtx + .DbSetTaskList + .Where(x => x.TaskId == rec2upd.TaskId) + .FirstOrDefault(); + if (currData != null) + { + currData.Ordinal = rec2upd.Ordinal; + currData.Name = rec2upd.Name; + currData.Descript = rec2upd.Descript; + currData.Command = rec2upd.Command; + currData.Args = rec2upd.Args; + currData.Freq = rec2upd.Freq; + currData.Cad = rec2upd.Cad; + currData.DtLastExec = rec2upd.DtLastExec; + currData.DtNextExec = rec2upd.DtNextExec; + currData.LastDuration = rec2upd.LastDuration; + currData.LastResult = rec2upd.LastResult; + dbCtx.Entry(currData).State = EntityState.Modified; + } + else + { + dbCtx + .DbSetTaskList + .Add(rec2upd); + } + dbCtx.SaveChanges(); + done = true; + } + catch (Exception exc) + { + Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}"); + } + } + return done; + } + + #endregion Public Methods + + #region Private Fields + + private static IConfiguration _configuration; + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/MP.Data/MoonProContext.cs b/MP.Data/MoonProContext.cs index e8a2bb3c..99e1199b 100644 --- a/MP.Data/MoonProContext.cs +++ b/MP.Data/MoonProContext.cs @@ -99,6 +99,11 @@ namespace MP.Data public virtual DbSet DbSetParetoFluxLog { get; set; } + public virtual DbSet DbSetTaskList { get; set; } + public virtual DbSet DbSetTaskExe { get; set; } + public virtual DbSet DbSetTaskResult { get; set; } + + #endregion Public Properties #region Private Methods @@ -122,6 +127,10 @@ namespace MP.Data { connString = _configuration.GetConnectionString("MP.STATS"); } + if (string.IsNullOrEmpty(connString)) + { + connString = _configuration.GetConnectionString("MP.Land"); + } optionsBuilder.UseSqlServer(connString); //optionsBuilder.UseSqlServer("Server=SQL2016DEV;Database=MoonPro;Trusted_Connection=True;"); diff --git a/MP.Data/Services/TaskService.cs b/MP.Data/Services/TaskService.cs new file mode 100644 index 00000000..f11cf152 --- /dev/null +++ b/MP.Data/Services/TaskService.cs @@ -0,0 +1,324 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using MP.Data.Controllers; +using MP.Data.DatabaseModels; +using Newtonsoft.Json; +using NLog; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static MP.Data.Objects.Enums; + +namespace MP.Data.Services +{ + public class TaskService : BaseServ, IDisposable + { + #region Public Constructors + + /// + /// Init servizio TAB + /// + /// + public TaskService(IConfiguration configuration) + { + _configuration = configuration; + + // setup compoenti REDIS + redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); + redisDb = redisConn.GetDatabase(); + + // conf DB + ConnStr = _configuration.GetConnectionString("MP.All"); + if (string.IsNullOrEmpty(ConnStr)) + { + Log.Error("ConnString empty!"); + } + else + { + StringBuilder sb = new StringBuilder(); + MLController = new MpLandController(configuration); + sb.AppendLine($"TaskService | MpLandController OK"); + Log.Info(sb.ToString()); + // sistemo i parametri x redHas... + CodModulo = _configuration.GetValue("ServerConf:CodModulo"); + var cstringArray = ConnStr.Split(";"); + foreach (var item in cstringArray) + { + var cData = item.Trim().Split("="); + if (cData.Length == 2) + { + if (!connStrParams.ContainsKey(cData[0])) + { + connStrParams.Add(cData[0], cData[1]); + } + } + } + // sistemo + DataSource = connStrParams["Server"]; + DataBase = connStrParams["Database"]; + } + } + + #endregion Public Constructors + + #region Public Events + + /// + /// Evento richiesta rilettura dati pagina (x refresh pagine aperte) + /// + public event EventHandler ReloadRequest = delegate { }; + + #endregion Public Events + + #region Public Methods + + public void Dispose() + { + // Clear database controller + MLController.Dispose(); + // redis dispose + redisConn = null; + redisDb = null; + } + + /// + /// Chiamata esecuzione di un singolo task programmato + /// + /// + /// Se true rischedula successiva chiamata + /// + public async Task ExecuteTask(int TaskId, bool SchedNext) + { + TaskResultModel dbResult = MLController.ExecuteTask(TaskId, SchedNext); + // svuoto cache! + await FlushCache("Task"); + return dbResult; + } + + /// + /// Pulizia cache Redis (tutta) + /// + /// + public async Task FlushCache() + { + RedisValue pattern = new RedisValue($"{redisBaseKey}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + return answ; + } + + /// + /// Pulizia cache Redis per chiave specifica (da redisBaseKey...) + /// + /// + public async Task FlushCache(string KeyReq) + { + RedisValue pattern = new RedisValue($"{redisBaseKey}:{KeyReq}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + return answ; + } + + /// + /// Invio notifica rilettura (con parametro) + /// + /// + public void NotifyReloadRequest(string message) + { + if (ReloadRequest != null) + { + // messaggio + ReloadEventArgs rea = new ReloadEventArgs(message); + ReloadRequest.Invoke(this, rea); + } + } + + public void rollBackEdit(object item) + { + MLController.RollBackEntity(item); + } + + /// + /// Ricerca task dato tipo + num max (desc) + /// + /// TaskId da cui deriva + /// + public async Task> TaskExecGetFilt(int TaskId, int maxRec, string searchVal) + { + // setup parametri costanti + string source = "DB"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List result = new List(); + // cerco in redis... + DateTime adesso = DateTime.Now; + string currKey = $"{redisBaseKey}:Task:ExecList:{TaskId}:{adesso:yyMMdd}:{adesso:HHmm}:{maxRec}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + source = "REDIS"; + } + else + { + result = MLController.TaskExecGetFilt(TaskId, maxRec); + // serializzp e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, FastCache); + } + if (result == null) + { + result = new List(); + } + sw.Stop(); + Log.Debug($"TaskExecGetFilt | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco TaskList gestiti + /// + /// + /// + /// + public async Task> TaskListAll(Task2ExeType TType, string searchVal = "") + { + // setup parametri costanti + string source = "DB"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List result = new List(); + // cerco in redis... + DateTime adesso = DateTime.Now; + string currKey = $"{redisBaseKey}:Task:List:{TType}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + source = "REDIS"; + } + else + { + result = MLController.TaskListGetAll(TType); + // serializzp e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, FastCache); + } + if (result == null) + { + result = new List(); + } + // se necessario filtro.. + if (!string.IsNullOrEmpty(searchVal)) + { + result = result + .Where(x => x.Name.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase) + || x.Descript.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); + } + sw.Stop(); + Log.Debug($"TaskListAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Update ordinamento task + /// + /// Record da spostare x priorità + /// + public async Task TaskListMove(TaskListModel rec2upd, bool moveUp) + { + bool dbResult = MLController.TaskListMove(rec2upd, moveUp); + // svuoto cache! + await FlushCache("Task"); + return await Task.FromResult(dbResult); + } + + /// + /// Update/Insert record TaskList + /// + /// + /// + public async Task TaskListUpsert(TaskListModel rec2upd) + { + bool dbResult = MLController.TaskListUpsert(rec2upd); + // svuoto cache! + await FlushCache("Task"); + return await Task.FromResult(dbResult); + } + + #endregion Public Methods + + #region Protected Fields + + /// + /// Oggetto per connessione a REDIS + /// + protected ConnectionMultiplexer redisConn = null!; + + /// + /// Oggetto DB redis da impiegare x chiamate R/W + /// + protected IDatabase redisDb = null!; + + #endregion Protected Fields + + #region Private Fields + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private string CodModulo = ""; + + private string ConnStr = ""; + + private Dictionary connStrParams = new Dictionary(); + + private string DataBase = ""; + + private string DataSource = ""; + + private string redisBaseKey = "MP:TASK"; + + #endregion Private Fields + + #region Private Properties + + private static MpLandController MLController { get; set; } = null!; + + #endregion Private Properties + + #region Private Methods + + /// + /// Esegue flush memoria redis dato pattern + /// + /// + /// + private async Task ExecFlushRedisPattern(RedisValue pattern) + { + bool answ = false; + var listEndpoints = redisConn.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + //var server = redisConnAdmin.GetServer(listEndpoints[0]); + var server = redisConn.GetServer(endPoint); + if (server != null) + { + var keyList = server.Keys(redisDb.Database, pattern); + foreach (var item in keyList) + { + await redisDb.KeyDeleteAsync(item); + } + answ = true; + } + } + // notifico update ai client in ascolto x reset cache + NotifyReloadRequest($"FlushRedisCache | {pattern}"); + return answ; + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/MP.Land/Components/TLResult.razor b/MP.Land/Components/TLResult.razor new file mode 100644 index 00000000..fe4d9b04 --- /dev/null +++ b/MP.Land/Components/TLResult.razor @@ -0,0 +1,7 @@ +
+ @($"{CurrRecord.LastDuration:N3}") sec +
+@if (showDetail) +{ +
@CurrRecord.LastResult
+} diff --git a/MP.Land/Components/TLResult.razor.cs b/MP.Land/Components/TLResult.razor.cs new file mode 100644 index 00000000..466a9950 --- /dev/null +++ b/MP.Land/Components/TLResult.razor.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Components; +using MP.Data.DatabaseModels; +using System.Threading.Tasks; + +namespace MP.Land.Components +{ + public partial class TLResult + { + #region Public Properties + + [Parameter] + public TaskListModel CurrRecord { get; set; } = null!; + + #endregion Public Properties + + #region Protected Properties + + protected string alCss + { + get => CurrRecord.LastIsError ? "alert-danger" : "alert-success"; + } + + protected string btnCss + { + get + { + string answ = showDetail ? "btn-" : "btn-outline-"; + answ += CurrRecord.LastIsError ? "danger" : "success"; + return answ; + } + } + + protected string iconCss + { + get => CurrRecord.LastIsError ? "fa-thumbs-down" : "fa-thumbs-up"; + } + + #endregion Protected Properties + + #region Protected Methods + + protected async Task toggleDetail() + { + showDetail = !showDetail; + await InvokeAsync(StateHasChanged); + } + + #endregion Protected Methods + + #region Private Properties + + private bool showDetail { get; set; } = false; + + #endregion Private Properties + } +} \ No newline at end of file diff --git a/MP.Land/Components/TaskEdit.razor b/MP.Land/Components/TaskEdit.razor new file mode 100644 index 00000000..69d6674a --- /dev/null +++ b/MP.Land/Components/TaskEdit.razor @@ -0,0 +1,101 @@ +@if (CurrRecord != null) +{ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ @*
+
+ + +
+
+
+
+ + +
+
*@ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+} + diff --git a/MP.Land/Components/TaskEdit.razor.cs b/MP.Land/Components/TaskEdit.razor.cs new file mode 100644 index 00000000..da8867a9 --- /dev/null +++ b/MP.Land/Components/TaskEdit.razor.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Components; +using MP.Data.DatabaseModels; +using MP.Data.Services; +using System.Threading.Tasks; + +namespace MP.Land.Components +{ + public partial class TaskEdit + { + #region Public Properties + + [Parameter] + public TaskListModel? CurrRecord { get; set; } = null; + + [Parameter] + public EventCallback EC_update { get; set; } + + #endregion Public Properties + + #region Protected Properties + + [Inject] + protected TaskService TService { get; set; } + + #endregion Protected Properties + + #region Protected Methods + + protected async Task doCancel() + { + await EC_update.InvokeAsync(false); + } + + protected async Task doSave() + { + bool fatto = false; + await Task.Delay(1); + if (CurrRecord != null) + { + fatto = await TService.TaskListUpsert(CurrRecord); + } + await EC_update.InvokeAsync(fatto); + } + + #endregion Protected Methods + } +} \ No newline at end of file diff --git a/MP.Land/Components/TaskExeList.razor b/MP.Land/Components/TaskExeList.razor new file mode 100644 index 00000000..b74c56a3 --- /dev/null +++ b/MP.Land/Components/TaskExeList.razor @@ -0,0 +1,85 @@ + +
+
+
+
+ History +
+
+
+ +
+
+
+
+
+ @if (ListRecords == null) + { + + } + else if (totalCount == 0) + { +
Nessun record trovato
+ } + else + { +
+
+ + + + + + + + + + + @foreach (var record in ListRecords) + { + + + + + + + } + +
#InizioFineEsito
+ @record.TaskExecId + + @($"{record.DtStart:HH:mm:ss.fff}") +
@($"{record.DtStart:yyyy-MM.dd ddd}")
+
+ @($"{record.DtEnd:HH:mm:ss.fff}") +
@($"{record.DtEnd:yyyy-MM.dd ddd}")
+
+
+
+ @if (@record.IsError) + { + + } + else + { + + } +
+
+ @($"{record.Duration:N3}") sec +
+
+
@record.Result
+
+
+
+ } +
+ +
\ No newline at end of file diff --git a/MP.Land/Components/TaskExeList.razor.cs b/MP.Land/Components/TaskExeList.razor.cs new file mode 100644 index 00000000..9eb8c672 --- /dev/null +++ b/MP.Land/Components/TaskExeList.razor.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Components; +using MP.Data.DatabaseModels; +using MP.Data.Services; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MP.Land.Components +{ + public partial class TaskExeList + { + #region Public Properties + + [Parameter] + public TaskListModel? CurrRecord { get; set; } = null; + + #endregion Public Properties + + #region Protected Fields + + protected bool isLoading = false; + + #endregion Protected Fields + + #region Protected Properties + + [Inject] + protected NavigationManager NavManager { get; set; } + + /// + /// Show error mode: 0 = tutti 1 = solo errori 2 = solo ok + /// + protected int ShowErrorMode + { + get => showErrorMode; + set + { + if (showErrorMode != value) + { + showErrorMode = value; + var pUpd = Task.Run(async () => await ReloadData()); + pUpd.Wait(); + } + } + } + + protected int totalCount { get; set; } = 0; + + [Inject] + protected TaskService TService { get; set; } + + #endregion Protected Properties + + #region Protected Methods + + protected async Task ForceReload(int newNum) + { + numRecord = newNum; + await ReloadData(); + } + + protected async Task ForceReloadPage(int newNum) + { + currPage = newNum; + await ReloadData(); + } + + protected override async Task OnInitializedAsync() + { + await ReloadData(); + } + + #endregion Protected Methods + + #region Private Fields + + private List ListRecords; + private List SearchRecords; + + #endregion Private Fields + + #region Private Properties + + private int currPage { get; set; } = 1; + private int numRecord { get; set; } = 10; + private int showErrorMode { get; set; } = 0; + + #endregion Private Properties + + #region Private Methods + + private async Task ReloadData() + { + SearchRecords = await TService.TaskExecGetFilt(CurrRecord.TaskId, 1000, ""); + // se non tutti filtro... + if (ShowErrorMode != 0) + { + if (ShowErrorMode == 1) + { + SearchRecords = SearchRecords.FindAll(x => x.IsError); + } + else if (ShowErrorMode == 2) + { + SearchRecords = SearchRecords.FindAll(x => !x.IsError); + } + } + totalCount = SearchRecords.Count; + ListRecords = SearchRecords.Skip(numRecord * (currPage - 1)).Take(numRecord).ToList(); + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/MP.Land/MP.Land.csproj b/MP.Land/MP.Land.csproj index 3efa1241..3879efba 100644 --- a/MP.Land/MP.Land.csproj +++ b/MP.Land/MP.Land.csproj @@ -3,7 +3,7 @@ net6.0 MP.Land - 6.16.2410.2215 + 6.16.2410.2319 Debug;Release;Debug_LiManDebug @@ -62,6 +62,7 @@ + diff --git a/MP.Land/Pages/TaskScheduler.razor b/MP.Land/Pages/TaskScheduler.razor new file mode 100644 index 00000000..14f307a3 --- /dev/null +++ b/MP.Land/Pages/TaskScheduler.razor @@ -0,0 +1,166 @@ +@page "/TaskScheduler" + +
+
+
+
+
+
+ TaskList +
+
+
+ @if (currRecord == null) + { + + } + else + { + + } + +
+
+
+ +
+
+ @if (isLoading) + { + + + } + else if (ListRecords == null) + { + + } + else if (totalCount == 0) + { +
Nessun record trovato
+ } + else + { +
+
+ + + + + + + + + + @if (detRecord == null) + { + + + + + } + + + + @foreach (var record in ListRecords) + { + + + + + + + + @if (detRecord == null) + { + + + + + } + + } + +
+ + OrdTaskTipoCommandSched.LastNextResult + +
+ + @if (detRecord == null) + { + @if (currRecord == null) + { + + + } + else + { + + } + } + + @if (detRecord == null) + { + @if (record.Ordinal == minOrdinal) + { + + } + else + { + + } + } + @record.Ordinal + @if (detRecord == null) + { + @if (record.Ordinal == maxOrdinal) + { + + } + else + { + + } + } + +
@record.Name
+
@record.Descript
+
+ @record.TType + +
@record.Command
+
@record.Args
+
@record.Freq × @record.Cad +
@($"{record.DtLastExec:yyyy-MM-dd}")
+
@($"{record.DtLastExec:ddd HH:mm:ss}")
+
+
@($"{record.DtNextExec:yyyy-MM-dd}")
+
@($"{record.DtNextExec:ddd HH:mm:ss}")
+
+ + + +
+
+
+ } +
+ +
+
+ @if (detRecord != null && !isLoading) + { +
+ +
+ } +
\ No newline at end of file diff --git a/MP.Land/Pages/TaskScheduler.razor.cs b/MP.Land/Pages/TaskScheduler.razor.cs new file mode 100644 index 00000000..b4e9f61c --- /dev/null +++ b/MP.Land/Pages/TaskScheduler.razor.cs @@ -0,0 +1,355 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using MP.Data.DatabaseModels; +using MP.Land.Data; +using static MP.Data.Objects.Enums; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System; +using System.Linq; +using MP.Data.Services; + +namespace MP.Land.Pages +{ + public partial class TaskScheduler : ComponentBase, IDisposable + { + #region Public Methods + + public string checkSelect(int TaskId) + { + string answ = ""; + if (currRecord != null) + { + try + { + answ = (currRecord.TaskId == TaskId) ? "table-info" : ""; + } + catch + { } + } + else if (detRecord != null) + { + answ = (detRecord.TaskId == TaskId) ? "table-info" : ""; + } + return answ; + } + + public void Dispose() + { + MessageService.EA_SearchUpdated -= OnSeachUpdated; + } + + public async void OnSeachUpdated() + { + await InvokeAsync(() => + { + Task task = ReloadData(); + StateHasChanged(); + }); + } + + #endregion Public Methods + + #region Protected Fields + + protected string fileName = "TaskList.csv"; + + #endregion Protected Fields + + #region Protected Properties + + [Inject] + protected IJSRuntime JSRuntime { get; set; } + + protected string mainCss + { + get => detRecord == null ? "col-12" : "col-6"; + } + + protected int maxOrdinal { get; set; } = 999; + + [Inject] + protected Data.MessageService MessageService { get; set; } + + protected int minOrdinal { get; set; } = 0; + + [Inject] + protected NavigationManager NavManager { get; set; } + + protected int totalCount { get; set; } = 0; + + [Inject] + protected TaskService TService { get; set; } + + protected Task2ExeType TypeSel + { + get => typeSel; + set + { + if (typeSel != value) + { + typeSel = value; + var pUpd = Task.Run(async () => + { + await ReloadData(); + }); + pUpd.Wait(); + } + } + } + + #endregion Protected Properties + + #region Protected Methods + + protected async Task addNew() + { + currRecord = new TaskListModel() { Name = "Nuovo Task", Descript = "Descrizione", DtLastExec = DateTime.Today, DtNextExec = DateTime.Today.AddDays(1) }; + await ReloadData(); + } + + /// + /// Gestione display avanzamento step + /// + /// + protected async Task advStep(int currStep) + { + currVal = currStep; + nextVal = currVal + 1; + await InvokeAsync(StateHasChanged); + } + + protected async Task doCancel() + { + currRecord = null; + detRecord = null; + await ReloadData(); + } + + protected async Task doClone(TaskListModel selRec) + { + if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler duplicare il record selezionato?")) + return; + currRecord = new TaskListModel() + { + Args = selRec.Args, + Name = $"Copia di {selRec.Name}", + Cad = selRec.Cad, + Command = selRec.Command, + Descript = $"Copia di {selRec.Descript}", + DtNextExec = DateTime.Today.AddDays(1), + DtLastExec = DateTime.MinValue, + Freq = selRec.Freq, + LastDuration = 0, + LastIsError = false, + LastResult = "", + TType = selRec.TType, + Ordinal = SearchRecords.Count + 1, + }; + await ReloadData(); + } + + protected async Task doEdit(TaskListModel selRec) + { + currRecord = selRec; + await ReloadData(); + } + + protected async Task doMove(TaskListModel currRec, bool goUp) + { + await TService.TaskListMove(currRec, goUp); + detRecord = null; + currRecord = null; + await ReloadData(); + } + + protected async Task doReset() + { + detRecord = null; + currRecord = null; + await TService.FlushCache(); + await ReloadData(); + } + + protected async Task doRun(TaskListModel selRec) + { + // SE non è ancora scaduto chiedo conferma + if (selRec.DtNextExec > DateTime.Now) + { + if (!await JSRuntime.InvokeAsync("confirm", $"Confermi esecuzione forzata task non scaduto?{Environment.NewLine}[{selRec.TaskId}]: {selRec.Name} - {selRec.Descript}{Environment.NewLine}Prossima schedulazione: {selRec.DtNextExec:yyyy-MM-dd HH:mm:ss}")) + return; + } + + // imposto tempo atteso esecuzione da ultimo... + isLoading = true; + MaxVal = 4; + int currStep = 0; + await advStep(currStep); + expTimeMsec = (int)(1000 * selRec.LastDuration) / 4; + detRecord = null; + await advStep(currStep++); + await Task.Delay(100); + await advStep(currStep++); + // chiama esecuzione task + var result = await TService.ExecuteTask(selRec.TaskId, false); + await advStep(currStep++); + isLoading = false; + await Task.Delay(100); + await advStep(currStep++); + await ReloadData(); + } + + protected async Task doSelect(TaskListModel selRec) + { + detRecord = null; + currRecord = null; + isLoading = true; + detRecord = selRec; + await ReloadData(); + isLoading = false; + } + + protected async Task forceAll() + { + if (!await JSRuntime.InvokeAsync("confirm", $"Confermi esecuzione forzata di tutti i task?")) + return; + + isLoading = true; + detRecord = null; + await Task.Delay(100); + foreach (var taskRec in SearchRecords) + { + var result = await TService.ExecuteTask(taskRec.TaskId, false); + } + isLoading = false; + await Task.Delay(100); + await ReloadData(); + } + + protected async Task ForceReload(int newNum) + { + numRecord = newNum; + await ReloadData(); + } + + protected async Task ForceReloadPage(int newNum) + { + currPage = newNum; + await ReloadData(); + } + + protected async Task forceUpdate(bool doForce) + { + currRecord = null; + await ReloadData(); + } + + protected override async Task OnInitializedAsync() + { + clearFile(); + numRecord = 10; + MessageService.ShowSearch = false; + MessageService.PageName = "Task Scheduler"; + MessageService.PageIcon = "oi oi-clock"; + MessageService.EA_SearchUpdated += OnSeachUpdated; + await ReloadData(); + } + + protected void ResetData() + { + clearFile(); + TService.rollBackEdit(currRecord); + currRecord = null; + } + + protected async Task ResetFilter(SelectData newFilter) + { + clearFile(); + detRecord = null; + currRecord = null; + SearchRecords = null; + ListRecords = null; + await ReloadData(); + } + + protected double righDiv(double num, double den) + { + if (den == 0) + { + den = 1; + } + double answ = num / den; + return answ; + } + + #endregion Protected Methods + + #region Private Fields + + private double currVal = 0; + private List ListRecords; + private int MaxVal = 10; + private double nextVal = 0; + private List SearchRecords; + + #endregion Private Fields + + #region Private Properties + + private int currPage { get; set; } = 1; + + private TaskListModel currRecord { get; set; } = null; + + private TaskListModel detRecord { get; set; } = null; + + private int expTimeMsec { get; set; } = 30000; + + private string fullPath + { + get => $"{Directory.GetCurrentDirectory()}\\temp\\{fileName}"; + } + + private bool isLoading { get; set; } = false; + private int numRecord { get; set; } = 10; + + private Task2ExeType typeSel { get; set; } = Task2ExeType.ND; + + #endregion Private Properties + + #region Private Methods + + private string btnRunCss(DateTime dtNextExe) + { + DateTime adesso = DateTime.Now; + string answ = dtNextExe < adesso ? "btn-success" : "btn-warning"; + return answ; + } + + private async void clearFile() + { + await Task.Run(() => File.Delete(fullPath)); + } + + private async Task ExportCsv() + { + isLoading = true; + // salvo davvero! + await MP.Data.Utils.SaveToCsv(SearchRecords, fullPath, ';'); + isLoading = false; + } + + private async Task ReloadData() + { + SearchRecords = await TService.TaskListAll(TypeSel, ""); + totalCount = SearchRecords.Count; + var firstRec = SearchRecords.OrderBy(x => x.Ordinal).FirstOrDefault(); + minOrdinal = firstRec != null ? firstRec.Ordinal : 0; + var lastRec = SearchRecords.OrderByDescending(x => x.Ordinal).FirstOrDefault(); + maxOrdinal = lastRec != null ? lastRec.Ordinal : 9999; + ListRecords = SearchRecords.Skip(numRecord * (currPage - 1)).Take(numRecord).ToList(); + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/MP.Land/Resources/ChangeLog.html b/MP.Land/Resources/ChangeLog.html index 1755be06..89c96808 100644 --- a/MP.Land/Resources/ChangeLog.html +++ b/MP.Land/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo Tablet MAPO - DotNet6 -

Versione: 6.16.2410.2215

+

Versione: 6.16.2410.2319


Note di rilascio:
    diff --git a/MP.Land/Resources/VersNum.txt b/MP.Land/Resources/VersNum.txt index 892d8f51..90be5917 100644 --- a/MP.Land/Resources/VersNum.txt +++ b/MP.Land/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2410.2215 +6.16.2410.2319 diff --git a/MP.Land/Resources/manifest.xml b/MP.Land/Resources/manifest.xml index cd262318..98bea03f 100644 --- a/MP.Land/Resources/manifest.xml +++ b/MP.Land/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2410.2215 + 6.16.2410.2319 https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html false diff --git a/MP.Land/Shared/NavMenu.razor b/MP.Land/Shared/NavMenu.razor index 547e2abb..58b2fa2d 100644 --- a/MP.Land/Shared/NavMenu.razor +++ b/MP.Land/Shared/NavMenu.razor @@ -53,6 +53,14 @@ System Info + @if (IsSuperAdmin) + { + + }