- continuo porting (NON ok...)
This commit is contained in:
Samuele Locatelli
2024-10-25 19:24:40 +02:00
parent 9d6f692b4d
commit 880df2a180
10 changed files with 849 additions and 7 deletions
+1 -4
View File
@@ -7,12 +7,10 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="TaskEdit.razor.cs" />
<Compile Remove="TaskExeList.razor.cs" />
</ItemGroup>
<ItemGroup>
<Content Remove="TaskEdit.razor" />
<Content Remove="TaskExeList.razor" />
</ItemGroup>
@@ -23,6 +21,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.33" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="RestSharp" Version="112.0.0" />
@@ -30,8 +29,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
<Folder Include="Models\" />
<Folder Include="Migrations\" />
<Folder Include="Controllers\" />
</ItemGroup>
+63
View File
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
#nullable disable
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace MP.TaskMan.Models
{
[Table("TaskExec")]
public partial class TaskExecModel
{
#region Public Properties
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TaskExecId { get; set; } = 0;
/// <summary>
/// task Id di riferimento
/// </summary>
public int TaskId { get; set; } = 0;
/// <summary>
/// DataOra inizio
/// </summary>
public DateTime DtStart { get; set; } = DateTime.Now;
/// <summary>
/// DataOra fine
/// </summary>
public DateTime DtEnd { get; set; } = DateTime.Now.AddDays(-1);
/// <summary>
/// Durata ultima esecuzione in secondi
/// </summary>
[NotMapped]
public double Duration
{
get => DtEnd.Subtract(DtStart).TotalSeconds;
}
/// <summary>
/// Esito in Errore
/// </summary>
public bool IsError { get; set; } = false;
/// <summary>
/// Ultimo risultato registrato
/// </summary>
public string Result { get; set; } = "";
/// <summary>
/// Navigazione oggetto TaskList
/// </summary>
[ForeignKey("TaskId")]
public virtual TaskListModel TaskListNav { get; set; } = null!;
#endregion Public Properties
}
}
+87
View File
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static MP.TaskMan.Objects.Enums;
#nullable disable
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace MP.TaskMan.Models
{
[Table("TaskList")]
public partial class TaskListModel
{
#region Public Properties
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TaskId { get; set; } = 0;
/// <summary>
/// Ordinale x esecuzione
/// </summary>
public int Ordinal { get; set; } = 0;
/// <summary>
/// Nome Task
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Descrizione Task
/// </summary>
public string Descript { get; set; } = "";
/// <summary>
/// Tipo Task
/// </summary>
public Task2ExeType TType { get; set; } = Task2ExeType.ND;
/// <summary>
/// Comando da invocare
/// </summary>
public string Command { get; set; } = "";
/// <summary>
/// Elenco argomenti (json)
/// </summary>
public string Args { get; set; } = "";
/// <summary>
/// Frequenza esecuzione da enum
/// </summary>
public TaskFreqType Freq { get; set; } = TaskFreqType.ND;
/// <summary>
/// Cadenza esecuzione
/// </summary>
public int Cad { get; set; } = 1;
/// <summary>
/// DataOra ultima esecuzione
/// </summary>
public DateTime DtLastExec { get; set; } = DateTime.Today.AddYears(-10);
/// <summary>
/// DataOra ultima esecuzione
/// </summary>
public DateTime DtNextExec { get; set; } = DateTime.Today.AddYears(-9);
/// <summary>
/// Durata ultima esecuzione in secondi
/// </summary>
public double LastDuration { get; set; } = 0;
/// <summary>
/// Esito ultima esecuzione in Errore
/// </summary>
public bool LastIsError { get; set; } = false;
/// <summary>
/// Ultimo risultato registrato
/// </summary>
public string LastResult { get; set; } = "";
#endregion Public Properties
}
}
+37
View File
@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static MP.TaskMan.Objects.Enums;
#nullable disable
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace MP.TaskMan.Models
{
[NotMapped]
public partial class TaskResultModel
{
#region Public Properties
/// <summary>
/// Tipo di task eseguito
/// </summary>
public string Task { get; set; } = "";
/// <summary>
/// Risultato: >0 = successo, <0 = errore
/// </summary>
public int ExecResult { get; set; } = 0;
/// <summary>
/// Risultato esecuzione testuale
/// </summary>
public string TextResult { get; set; } = "";
#endregion Public Properties
}
}
+216
View File
@@ -0,0 +1,216 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace MP.TaskMan.Objects
{
public class Enums
{
#region Public Enums
/// <summary>
/// Intervallo dati (es per definizione quanti dati FluxLog tenere x intervallo
/// </summary>
public enum DataInterval
{
minute,
hour,
day
}
public enum DataItemCategory
{
CONDITION = 0,
EVENT = 1,
SAMPLE = 2
}
/// <summary>
/// Tipo di esito (generico)
/// </summary>
public enum esitoExec
{
undone,
ok,
error
}
//[JsonConverter(typeof(StringEnumConverter))]
public enum Task2ExeType
{
/// <summary>
/// Tipo indefinito / ALL
/// </summary>
ND,
/// <summary>
/// Chiamata exe esterno
/// </summary>
Exe,
/// <summary>
/// Chiamata a SQL Command
/// </summary>
SqlCommand,
/// <summary>
/// Chiamata a SQL Stored Procedure
/// </summary>
SqlStored,
/// <summary>
/// Chiamata REST tipo Get
/// </summary>
RestCallGet,
///// <summary>
///// Chiamata REST tipo Post
///// </summary>
//RestCallPost
}
//[JsonConverter(typeof(StringEnumConverter))]
public enum TaskFreqType
{
/// <summary>
/// Tipo indefinito / ALL
/// </summary>
ND,
/// <summary>
/// Secondi
/// </summary>
Sec,
/// <summary>
/// Minuti
/// </summary>
Min,
/// <summary>
/// Ore
/// </summary>
Hour,
/// <summary>
/// Giorni
/// </summary>
Day,
/// <summary>
/// Settimane
/// </summary>
Week,
/// <summary>
/// Mesi
/// </summary>
Month,
/// <summary>
/// Anni
/// </summary>
Year
}
/// <summary>
/// Elenco task ammessi (x IOB-WIN da eseguire...)
/// </summary>
public enum taskType
{
/// <summary>
/// Task nullo / fake
/// </summary>
nihil,
/// <summary>
/// Rimanda a PLC eventuale segnale NON in setup (MA NON RESETTA)
/// </summary>
fixStopSetup,
/// <summary>
/// Indica al PLC di forzare il reset del contapezzi
/// </summary>
forceResetPzCount,
/// <summary>
/// Indica al PLC di forzare il NUOVO valore di contapezzi (impostato come value)
/// </summary>
forceSetPzCount,
/// <summary>
/// Imposta Articolo su PLC
/// </summary>
setArt,
/// <summary>
/// Imposta Commessa su PLC
/// </summary>
setComm,
/// <summary>
/// Set di un PARAMETRO su PLC (in value avremo un JSON object)
/// </summary>
setParameter,
/// <summary>
/// Set Programma CNC su PLC
/// </summary>
setProg,
/// <summary>
/// Indica al PLC di impostare il numero di pezzi da produrre per la commessa (impostato
/// come value)
/// </summary>
setPzComm,
/// <summary>
/// Indica al PLC iniziato setup (e secondo casi ferma contapezzi /resetta)
/// </summary>
startSetup,
/// <summary>
/// Indica al PLC finito setup (e secondo casi ferma contapezzi /resetta)
/// </summary>
stopSetup,
/// <summary>
/// Richiesta invio watchdog a PLC
/// </summary>
sendWatchDogMes2Plc,
/// <summary>
/// Indica che è FINITA la produzione (e quindi cancello dati backup)
/// </summary>
endProd,
/// <summary>
/// Richiesta esecuzione di un sync dei dati DB di frontiera
/// </summary>
syncDbData,
/// <summary>
/// Imposta Fornitore (es grower x ICOEL)
/// </summary>
setSupplier,
/// <summary>
/// Effettua processing other info (es ritorno consumi x ricette FIMAT)
/// </summary>
processOtherInfo
}
/// <summary>
/// Finestra temporale di aggregazione dati VC
/// </summary>
public enum timeWindow
{
free,
hour,
day,
week,
month
}
#endregion Public Enums
}
}
+70
View File
@@ -0,0 +1,70 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MP.TaskMan.Services
{
/// <summary>
/// Classe di partenza x costruzione servizi di accesso dati + cache
/// </summary>
public class BaseServ
{
#region Protected Properties
/// <summary>
/// Durata cache breve (1 min circa + perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache MOLTO breve (10 sec circa + perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan UltraFastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
}
/// <summary>
/// Durata cache MOLTO lunga (+ perturbazione percentuale +/-10%)
/// </summary>
protected TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Protected Properties
protected static IConfiguration _configuration = null!;
#region Private Fields
/// <summary>
/// Durata cache lunga IN SECONDI
/// </summary>
private int cacheTtlLong = 60 * 5;
/// <summary>
/// Durata cache breve IN SECONDI
/// </summary>
private int cacheTtlShort = 60 * 1;
private Random rnd = new Random();
#endregion Private Fields
}
}
+370
View File
@@ -0,0 +1,370 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using MP.AppAuth.Services;
using MP.TaskMan.Controllers;
using MP.TaskMan.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.TaskMan.Objects.Enums;
namespace MP.TaskMan.Services
{
public class TaskService : BaseServ, IDisposable
{
#region Public Constructors
/// <summary>
/// Init servizio TAB
/// </summary>
/// <param name="configuration"></param>
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<string>("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"];
}
// conf rest call service
RCallService = new RestCallService(_configuration);
}
#endregion Public Constructors
#region Public Events
/// <summary>
/// Evento richiesta rilettura dati pagina (x refresh pagine aperte)
/// </summary>
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;
}
/// <summary>
/// Chiamata esecuzione di un singolo task programmato
/// </summary>
/// <param name="TaskRec">Task richiesto</param>
/// <param name="SchedNext">Se true rischedula successiva chiamata</param>
/// <returns></returns>
public async Task<TaskResultModel> ExecuteTask(TaskListModel TaskRec, bool SchedNext)
{
TaskResultModel answ = new TaskResultModel()
{
Task = $"TaskId: {TaskRec.TaskId} | {TaskRec.TType}",
ExecResult = -1,
TextResult = "Task Not recognized"
};
// verifico tipo di task ed eseguo di conseguenza...
switch (TaskRec.TType)
{
//case Task2ExeType.ND:
// break;
//case Task2ExeType.Exe:
// break;
//case Task2ExeType.SqlCommand:
// break;
case Task2ExeType.SqlStored:
answ = MLController.ExecuteSqlTask(TaskRec.TaskId, SchedNext);
break;
case Task2ExeType.RestCallGet:
DateTime dtStart = DateTime.Now;
// in primis testo la chiamata al servizio Health
string rAnsw = await RCallService.CheckServer();
// se ok effettuo vera chiamata...
if (rAnsw.ToUpper() == "OK")
{
var callResp = await RCallService.CallRestGet(TaskRec.Command, TaskRec.Args);
DateTime dtEnd = DateTime.Now;
TaskExecModel tExeMod = new TaskExecModel()
{
DtEnd = dtEnd,
DtStart = dtStart,
IsError = callResp.StatusCode != System.Net.HttpStatusCode.OK,
TaskId = TaskRec.TaskId,
Result = $"{callResp.Content}".Replace("\"", ""),
};
// salvo su DB
answ = MLController.TaskExecSaveExecuted(TaskRec.TaskId, SchedNext, tExeMod);
}
break;
default:
break;
}
// svuoto cache!
await FlushCache("Task");
return answ;
}
/// <summary>
/// Pulizia cache Redis (tutta)
/// </summary>
/// <returns></returns>
public async Task<bool> FlushCache()
{
RedisValue pattern = new RedisValue($"{redisBaseKey}:*");
bool answ = await ExecFlushRedisPattern(pattern);
return answ;
}
/// <summary>
/// Pulizia cache Redis per chiave specifica (da redisBaseKey...)
/// </summary>
/// <returns></returns>
public async Task<bool> FlushCache(string KeyReq)
{
RedisValue pattern = new RedisValue($"{redisBaseKey}:{KeyReq}:*");
bool answ = await ExecFlushRedisPattern(pattern);
return answ;
}
/// <summary>
/// Invio notifica rilettura (con parametro)
/// </summary>
/// <param name="message"></param>
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);
}
/// <summary>
/// Ricerca task dato tipo + num max (desc)
/// </summary>
/// <param name="TaskId">TaskId da cui deriva</param>
/// <returns></returns>
public async Task<List<TaskExecModel>> TaskExecGetFilt(int TaskId, int maxRec, string searchVal)
{
// setup parametri costanti
string source = "DB";
Stopwatch sw = new Stopwatch();
sw.Start();
List<TaskExecModel> result = new List<TaskExecModel>();
// 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<List<TaskExecModel>>($"{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<TaskExecModel>();
}
sw.Stop();
Log.Debug($"TaskExecGetFilt | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
/// <summary>
/// Elenco TaskList gestiti
/// </summary>
/// <param name="CurrFilter"></param>
/// <param name="searchVal"></param>
/// <returns></returns>
public async Task<List<TaskListModel>> TaskListAll(Task2ExeType TType, string searchVal = "")
{
// setup parametri costanti
string source = "DB";
Stopwatch sw = new Stopwatch();
sw.Start();
List<TaskListModel> result = new List<TaskListModel>();
// 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<List<TaskListModel>>($"{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<TaskListModel>();
}
// 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;
}
/// <summary>
/// Update ordinamento task
/// </summary>
/// <param name="rec2upd">Record da spostare x priorità</param>
/// <returns></returns>
public async Task<bool> TaskListMove(TaskListModel rec2upd, bool moveUp)
{
bool dbResult = MLController.TaskListMove(rec2upd, moveUp);
// svuoto cache!
await FlushCache("Task");
return await Task.FromResult(dbResult);
}
/// <summary>
/// Update/Insert record TaskList
/// </summary>
/// <param name="rec2upd"></param>
/// <returns></returns>
public async Task<bool> TaskListUpsert(TaskListModel rec2upd)
{
bool dbResult = MLController.TaskListUpsert(rec2upd);
// svuoto cache!
await FlushCache("Task");
return dbResult;
}
#endregion Public Methods
#region Protected Fields
/// <summary>
/// Oggetto per connessione a REDIS
/// </summary>
protected ConnectionMultiplexer redisConn = null!;
/// <summary>
/// Oggetto DB redis da impiegare x chiamate R/W
/// </summary>
protected IDatabase redisDb = null!;
#endregion Protected Fields
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private string CodModulo = "";
private string ConnStr = "";
private Dictionary<string, string> connStrParams = new Dictionary<string, string>();
private string DataBase = "";
private string DataSource = "";
private string redisBaseKey = "MP:TASK";
#endregion Private Fields
#region Private Properties
private static MpLandController MLController { get; set; } = null!;
private RestCallService RCallService { get; set; } = null!;
#endregion Private Properties
#region Private Methods
/// <summary>
/// Esegue flush memoria redis dato pattern
/// </summary>
/// <param name="pattern"></param>
/// <returns></returns>
private async Task<bool> 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
}
}
+3 -3
View File
@@ -11,7 +11,7 @@
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" @bind="@CurrRecord.TType">
@foreach (var option in Enum.GetValues(typeof(MP.Data.Objects.Enums.Task2ExeType)))
@foreach (var option in Enum.GetValues(typeof(MP.TaskMan.Objects.Enums.Task2ExeType)))
{
<option value="@option">
@option
@@ -30,7 +30,7 @@
@* <div class="col-md-2">
<div class="form-floating">
<select class="form-select" @bind="@CurrRecord.Freq">
@foreach (var option in Enum.GetValues(typeof(MP.Data.Objects.Enums.TaskFreqType)))
@foreach (var option in Enum.GetValues(typeof(MP.TaskMan.Objects.Enums.TaskFreqType)))
{
<option value="@option">
@option
@@ -77,7 +77,7 @@
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" @bind="@CurrRecord.Freq">
@foreach (var option in Enum.GetValues(typeof(MP.Data.Objects.Enums.TaskFreqType)))
@foreach (var option in Enum.GetValues(typeof(MP.TaskMan.Objects.Enums.TaskFreqType)))
{
<option value="@option">
@option
+1
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using MP.TaskMan.Models;
namespace MP.TaskMan
{
+1
View File
@@ -1 +1,2 @@
@using Microsoft.AspNetCore.Components.Web
@using MP.TaskMan.Models