Contiuo bozza display task exec
This commit is contained in:
@@ -1,5 +1,71 @@
|
||||
<h3>TaskExeList</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
|
||||
<div class="card text-bg-light">
|
||||
<div class="card-header table-primary p-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="px-2">
|
||||
<b class="fs-4">History</b>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<div class="input-group">
|
||||
<select class="form-select" @bind="@ShowErrorMode">
|
||||
<option value="0">--- ALL ---</option>
|
||||
<option value="1">Solo Errori</option>
|
||||
<option value="2">Solo OK</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body py-0 px-1">
|
||||
@if (ListRecords == null)
|
||||
{
|
||||
<LoadingData></LoadingData>
|
||||
}
|
||||
else if (totalCount == 0)
|
||||
{
|
||||
<div class="alert alert-warning text-center display-6">Nessun record trovato</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Inizio</th>
|
||||
<th>Fine</th>
|
||||
<th class="text-right">Esito</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var record in ListRecords)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@record.TaskExecId
|
||||
</td>
|
||||
<td>
|
||||
@($"{record.DtStart:HH:mm:ss}")
|
||||
<div class="small">@($"{record.DtStart:yyyy-MM.dd ddd}")</div>
|
||||
</td>
|
||||
<td>
|
||||
@($"{record.DtEnd:HH:mm:ss}")
|
||||
<div class="small">@($"{record.DtEnd:yyyy-MM.dd ddd}")</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div>@record.IsError</div>
|
||||
<div class="small">@record.Result</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-footer py-1">
|
||||
<DataPager PageSize="numRecord" currPage="currPage" numRecordChanged="ForceReload" numPageChanged="ForceReloadPage" exportEnabled="false" totalCount="totalCount" showLoading="isLoading" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,89 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MP.Data.DatabaseModels;
|
||||
using MP.Stats.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MP.Stats.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; }
|
||||
|
||||
/// <summary>
|
||||
/// Show error mode: 0 = tutti 1 = solo errori 2 = solo ok
|
||||
/// </summary>
|
||||
protected int ShowErrorMode { get; set; } = 0;
|
||||
|
||||
[Inject]
|
||||
protected MpStatsService StatService { get; set; }
|
||||
|
||||
protected int totalCount { get; set; } = 0;
|
||||
|
||||
#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<TaskExecModel> ListRecords;
|
||||
private List<TaskExecModel> SearchRecords;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private int currPage { get; set; } = 1;
|
||||
|
||||
private int numRecord { get; set; } = 10;
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task ReloadData()
|
||||
{
|
||||
SearchRecords = await StatService.TaskExecGetFilt(CurrRecord.TaskId, 1000, "");
|
||||
totalCount = SearchRecords.Count;
|
||||
ListRecords = SearchRecords.Skip(numRecord * (currPage - 1)).Take(numRecord).ToList();
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
+164
-125
@@ -17,39 +17,6 @@ namespace MP.Stats.Data
|
||||
{
|
||||
public class MpStatsService : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private static IConfiguration _configuration;
|
||||
|
||||
private static ILogger<MpStatsService> _logger;
|
||||
|
||||
private static List<AzioniUL> ActionsList = new List<AzioniUL>();
|
||||
|
||||
private readonly IDistributedCache distributedCache;
|
||||
|
||||
private readonly IMemoryCache memoryCache;
|
||||
|
||||
/// <summary>
|
||||
/// Durata assoluta massima della cache
|
||||
/// </summary>
|
||||
private int chAbsExp = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache in modalità inattiva (non acceduta) prima di venire rimossa
|
||||
/// NON estende oltre il tempo massimo di validità della cache (chAbsExp)
|
||||
/// </summary>
|
||||
private int chSliExp = 5;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected static string connStringBBM = "";
|
||||
|
||||
protected static string connStringFatt = "";
|
||||
|
||||
#endregion Protected Fields
|
||||
|
||||
#region Public Fields
|
||||
|
||||
public static MP.Data.Controllers.MpStatsController dbController;
|
||||
@@ -82,42 +49,6 @@ namespace MP.Stats.Data
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private DistributedCacheEntryOptions cacheOpt
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
|
||||
}
|
||||
}
|
||||
|
||||
private DistributedCacheEntryOptions cacheOptLong
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp * 10)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected string getCacheKey(string TableName, SelectData CurrFilter)
|
||||
{
|
||||
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:AZ_{CurrFilter.Azione}:ART_{CurrFilter.CodArticolo}:KR_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyyyMMddHHmm}_{CurrFilter.DateEnd:yyyyMMddHHmm}";
|
||||
return answ;
|
||||
}
|
||||
|
||||
protected string getCacheKeyPaged(string TableName, SelectData CurrFilter)
|
||||
{
|
||||
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:A_{CurrFilter.CodArticolo}:K_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyMMddHHmm}_{CurrFilter.DateEnd:yyMMddHHmm}:R_{CurrFilter.FirstRecord}_{CurrFilter.FirstRecord + CurrFilter.NumRecord}";
|
||||
return answ;
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public async Task<List<AzioniUL>> ActionsGetAll()
|
||||
@@ -315,6 +246,7 @@ namespace MP.Stats.Data
|
||||
}
|
||||
return await Task.FromResult(numRec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistiche ODL
|
||||
/// </summary>
|
||||
@@ -379,62 +311,6 @@ namespace MP.Stats.Data
|
||||
return await Task.FromResult(dbResult);
|
||||
}
|
||||
|
||||
|
||||
/// <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 = "")
|
||||
{
|
||||
List<TaskListModel> dbResult = new List<TaskListModel>();
|
||||
string cacheKey = $"MP:STATS:TaskList:{TType}";
|
||||
string rawData;
|
||||
var redisDataList = await distributedCache.GetAsync(cacheKey);
|
||||
if (redisDataList != null)
|
||||
{
|
||||
rawData = Encoding.UTF8.GetString(redisDataList);
|
||||
dbResult = JsonConvert.DeserializeObject<List<TaskListModel>>(rawData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stopwatch stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
dbResult = dbController.TaskListGetAll(TType);
|
||||
rawData = JsonConvert.SerializeObject(dbResult);
|
||||
redisDataList = Encoding.UTF8.GetBytes(rawData);
|
||||
await distributedCache.SetAsync(cacheKey, redisDataList, cacheOpt);
|
||||
stopWatch.Stop();
|
||||
TimeSpan ts = stopWatch.Elapsed;
|
||||
_logger.LogTrace($"Effettuata lettura da DB + caching per TaskList: {ts.TotalMilliseconds} ms");
|
||||
}
|
||||
// se necessario filtro..
|
||||
if (!string.IsNullOrEmpty(searchVal))
|
||||
{
|
||||
dbResult = dbResult
|
||||
.Where(x => x.Name.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| x.Descript.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
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 = dbController.TaskListUpsert(rec2upd);
|
||||
// svuoto cache!
|
||||
string cacheKey = $"MP:STATS:TaskList:{rec2upd.TType}";
|
||||
await distributedCache.RemoveAsync(cacheKey);
|
||||
cacheKey = $"MP:STATS:TaskList:ND";
|
||||
await distributedCache.RemoveAsync(cacheKey);
|
||||
return await Task.FromResult(dbResult);
|
||||
}
|
||||
|
||||
public async Task<List<ResScarti>> StatScartiGetAll(SelectData CurrFilter, string searchVal = "")
|
||||
{
|
||||
//return Task.FromResult(dbController.StatScartiGetAll(DataStart, DataEnd, IdxMacchina, IdxODL, KeyRichiesta, CodArticolo).ToArray());
|
||||
@@ -525,6 +401,169 @@ namespace MP.Stats.Data
|
||||
return await Task.FromResult(dbResult);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
List<TaskExecModel> dbResult = new List<TaskExecModel>();
|
||||
DateTime adesso = DateTime.Now;
|
||||
string cacheKey = $"MP:STATS:TaskExecList:{TaskId}:{adesso:yyMMdd}:{adesso:HHmm}:{maxRec}";
|
||||
string rawData;
|
||||
var redisDataList = await distributedCache.GetAsync(cacheKey);
|
||||
if (redisDataList != null)
|
||||
{
|
||||
rawData = Encoding.UTF8.GetString(redisDataList);
|
||||
dbResult = JsonConvert.DeserializeObject<List<TaskExecModel>>(rawData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stopwatch stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
dbResult = dbController.TaskExecGetFilt(TaskId, maxRec);
|
||||
rawData = JsonConvert.SerializeObject(dbResult);
|
||||
redisDataList = Encoding.UTF8.GetBytes(rawData);
|
||||
await distributedCache.SetAsync(cacheKey, redisDataList, cacheOpt);
|
||||
stopWatch.Stop();
|
||||
TimeSpan ts = stopWatch.Elapsed;
|
||||
_logger.LogTrace($"Effettuata lettura da DB + caching per TaskExecGetFilt: {ts.TotalMilliseconds} ms");
|
||||
}
|
||||
// se necessario filtro..
|
||||
if (!string.IsNullOrEmpty(searchVal))
|
||||
{
|
||||
dbResult = dbResult
|
||||
.Where(x => x.Result.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
return await Task.FromResult(dbResult);
|
||||
}
|
||||
|
||||
/// <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 = "")
|
||||
{
|
||||
List<TaskListModel> dbResult = new List<TaskListModel>();
|
||||
string cacheKey = $"MP:STATS:TaskList:{TType}";
|
||||
string rawData;
|
||||
var redisDataList = await distributedCache.GetAsync(cacheKey);
|
||||
if (redisDataList != null)
|
||||
{
|
||||
rawData = Encoding.UTF8.GetString(redisDataList);
|
||||
dbResult = JsonConvert.DeserializeObject<List<TaskListModel>>(rawData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stopwatch stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
dbResult = dbController.TaskListGetAll(TType);
|
||||
rawData = JsonConvert.SerializeObject(dbResult);
|
||||
redisDataList = Encoding.UTF8.GetBytes(rawData);
|
||||
await distributedCache.SetAsync(cacheKey, redisDataList, cacheOpt);
|
||||
stopWatch.Stop();
|
||||
TimeSpan ts = stopWatch.Elapsed;
|
||||
_logger.LogTrace($"Effettuata lettura da DB + caching per TaskList: {ts.TotalMilliseconds} ms");
|
||||
}
|
||||
// se necessario filtro..
|
||||
if (!string.IsNullOrEmpty(searchVal))
|
||||
{
|
||||
dbResult = dbResult
|
||||
.Where(x => x.Name.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| x.Descript.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
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 = dbController.TaskListUpsert(rec2upd);
|
||||
// svuoto cache!
|
||||
string cacheKey = $"MP:STATS:TaskList:{rec2upd.TType}";
|
||||
await distributedCache.RemoveAsync(cacheKey);
|
||||
cacheKey = $"MP:STATS:TaskList:ND";
|
||||
await distributedCache.RemoveAsync(cacheKey);
|
||||
return await Task.FromResult(dbResult);
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected static string connStringBBM = "";
|
||||
protected static string connStringFatt = "";
|
||||
|
||||
#endregion Protected Fields
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected string getCacheKey(string TableName, SelectData CurrFilter)
|
||||
{
|
||||
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:AZ_{CurrFilter.Azione}:ART_{CurrFilter.CodArticolo}:KR_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyyyMMddHHmm}_{CurrFilter.DateEnd:yyyyMMddHHmm}";
|
||||
return answ;
|
||||
}
|
||||
|
||||
protected string getCacheKeyPaged(string TableName, SelectData CurrFilter)
|
||||
{
|
||||
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:A_{CurrFilter.CodArticolo}:K_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyMMddHHmm}_{CurrFilter.DateEnd:yyMMddHHmm}:R_{CurrFilter.FirstRecord}_{CurrFilter.FirstRecord + CurrFilter.NumRecord}";
|
||||
return answ;
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private static IConfiguration _configuration;
|
||||
|
||||
private static ILogger<MpStatsService> _logger;
|
||||
|
||||
private static List<AzioniUL> ActionsList = new List<AzioniUL>();
|
||||
|
||||
private readonly IDistributedCache distributedCache;
|
||||
|
||||
private readonly IMemoryCache memoryCache;
|
||||
|
||||
/// <summary>
|
||||
/// Durata assoluta massima della cache
|
||||
/// </summary>
|
||||
private int chAbsExp = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Durata della cache in modalità inattiva (non acceduta) prima di venire rimossa NON
|
||||
/// estende oltre il tempo massimo di validità della cache (chAbsExp)
|
||||
/// </summary>
|
||||
private int chSliExp = 5;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private DistributedCacheEntryOptions cacheOpt
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
|
||||
}
|
||||
}
|
||||
|
||||
private DistributedCacheEntryOptions cacheOptLong
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp * 10)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Properties
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>MP.Stats</RootNamespace>
|
||||
<UserSecretsId>826e877c-ba70-4253-84cb-d0b1cafd4440</UserSecretsId>
|
||||
<Version>6.16.2404.0210</Version>
|
||||
<Version>6.16.2404.0210</Version>
|
||||
<Version>6.16.2404.0211</Version>
|
||||
<Version>6.16.2404.0211</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
</div>
|
||||
@if (detRecord != null)
|
||||
{
|
||||
<div class="col-4">
|
||||
<TaskExeList></TaskExeList>
|
||||
<div class="col-6">
|
||||
<TaskExeList CurrRecord="detRecord"></TaskExeList>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -76,18 +76,7 @@ namespace MP.Stats.Pages
|
||||
[Inject]
|
||||
protected MpStatsService StatService { get; set; }
|
||||
|
||||
protected int totalCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int answ = 0;
|
||||
if (SearchRecords != null)
|
||||
{
|
||||
answ = SearchRecords.Count;
|
||||
}
|
||||
return answ;
|
||||
}
|
||||
}
|
||||
protected int totalCount { get; set; } = 0;
|
||||
|
||||
protected Task2ExeType TypeSel
|
||||
{
|
||||
@@ -100,7 +89,6 @@ namespace MP.Stats.Pages
|
||||
var pUpd = Task.Run(async () =>
|
||||
{
|
||||
await ReloadData();
|
||||
//await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
pUpd.Wait();
|
||||
}
|
||||
@@ -278,6 +266,7 @@ namespace MP.Stats.Pages
|
||||
private async Task ReloadData()
|
||||
{
|
||||
SearchRecords = await StatService.TaskListAll(TypeSel, "");
|
||||
totalCount = SearchRecords.Count;
|
||||
ListRecords = SearchRecords.Skip(numRecord * (currPage - 1)).Take(numRecord).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>Modulo statistiche MAPO</i>
|
||||
<h4>Versione: 6.16.2404.0210</h4>
|
||||
<h4>Versione: 6.16.2404.0211</h4>
|
||||
<br />
|
||||
Note di rilascio:
|
||||
<ul>
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.16.2404.0210
|
||||
6.16.2404.0211
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>6.16.2404.0210</version>
|
||||
<version>6.16.2404.0211</version>
|
||||
<url>https://nexus.steamware.net/repository/SWS/MP-STATS/stable/LAST/MP.Stats.zip</url>
|
||||
<changelog>https://nexus.steamware.net/repository/SWS/MP-STATS/stable/LAST/ChangeLog.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
Reference in New Issue
Block a user