From 45a7a7319bbae1a2ab5cfc93d7a2507cccd8770e Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Tue, 1 Dec 2020 07:40:45 +0200 Subject: Implemented action logs for data store. --- .../Tango.DataStore.EF/EFDataStoreHelper.cs | 47 +---------- .../DataStore/Tango.DataStore/DataStoreHelper.cs | 92 +++++++++++++++++++-- .../Images/configuration.png | Bin 2668 -> 2254 bytes .../Views/SelectionView.xaml | 2 +- .../Services/MachineConfigurationService.cs | 28 ++++++- .../DataStore/DefaultDataStoreProvider.cs | 49 +++++++++++ .../Visual_Studio/Tango.BL/DTO/DataStoreItemDTO.cs | 24 ++++++ .../Tango.BL/Enumerations/ActionLogType.cs | 12 +++ .../Visual_Studio/Tango.BL/ObservableEntityDTO.cs | 11 +++ Software/Visual_Studio/Tango.BL/Tango.BL.csproj | 6 +- .../Utilities/Tango.JobRunsGenerator/Program.cs | 2 +- 11 files changed, 217 insertions(+), 56 deletions(-) (limited to 'Software/Visual_Studio') diff --git a/Software/Visual_Studio/DataStore/Tango.DataStore.EF/EFDataStoreHelper.cs b/Software/Visual_Studio/DataStore/Tango.DataStore.EF/EFDataStoreHelper.cs index 5e885458b..a702c3465 100644 --- a/Software/Visual_Studio/DataStore/Tango.DataStore.EF/EFDataStoreHelper.cs +++ b/Software/Visual_Studio/DataStore/Tango.DataStore.EF/EFDataStoreHelper.cs @@ -12,55 +12,12 @@ namespace Tango.DataStore.EF { public static byte[] CreateBytes(DataType type, Object obj) { - switch (type) - { - case DataType.Int32: - return BitConverter.GetBytes((int)obj); - case DataType.Float: - return BitConverter.GetBytes((float)obj); - case DataType.Double: - return BitConverter.GetBytes((double)obj); - case DataType.Boolean: - return BitConverter.GetBytes((bool)obj); - case DataType.String: - return Encoding.Default.GetBytes(obj.ToString()); - case DataType.Bytes: - return (byte[])obj; - case DataType.Proto: - if (obj is DataStoreProtoObject protoMessage) - { - return protoMessage.ToBytes(); - } - else - { - throw new NotSupportedException($"Data type is 'Proto' but object is not of type '{nameof(DataStoreProtoObject)}'."); - } - } - - throw new NotSupportedException("The specified type is not supported."); + return DataStoreHelper.CreateBytes(type, obj); } public static Object CreateObject(DataType type, byte[] bytes) { - switch (type) - { - case DataType.Int32: - return BitConverter.ToInt32(bytes, 0); - case DataType.Float: - return BitConverter.ToSingle(bytes, 0); - case DataType.Double: - return BitConverter.ToDouble(bytes, 0); - case DataType.Boolean: - return BitConverter.ToBoolean(bytes, 0); - case DataType.String: - return Encoding.Default.GetString(bytes); - case DataType.Bytes: - return bytes; - case DataType.Proto: - return DataStoreProtoObject.FromBytes(bytes); - } - - throw new NotSupportedException("The specified type is not supported."); + return DataStoreHelper.CreateObject(type, bytes); } public static IDataStoreItem CreateDataStoreItem(DataStoreItem item) diff --git a/Software/Visual_Studio/DataStore/Tango.DataStore/DataStoreHelper.cs b/Software/Visual_Studio/DataStore/Tango.DataStore/DataStoreHelper.cs index 0ceecd81b..0409dbf7a 100644 --- a/Software/Visual_Studio/DataStore/Tango.DataStore/DataStoreHelper.cs +++ b/Software/Visual_Studio/DataStore/Tango.DataStore/DataStoreHelper.cs @@ -83,17 +83,28 @@ namespace Tango.DataStore /// public static String FormatDataStoreItem(IDataStoreItem item) { - if (item.Type == DataType.Bytes) + return FormatDataStoreValue(item.Type, item.Value); + } + + /// + /// Formats the data store value. + /// + /// The type. + /// The object. + /// + public static String FormatDataStoreValue(DataType type, object obj) + { + if (type == DataType.Bytes) { - return Convert.ToBase64String((byte[])item.Value); + return Convert.ToBase64String((byte[])obj); } - else if (item.Type == DataType.Proto) + else if (type == DataType.Proto) { - return (item.Value as DataStoreProtoObject).Message.ToJsonString(); + return (obj as DataStoreProtoObject).Message.ToJsonString(); } else { - return item.Value.ToStringSafe(); + return obj.ToStringSafe(); } } @@ -143,5 +154,76 @@ namespace Tango.DataStore var regexItem = new Regex("^[a-zA-Z0-9_-]*$"); return regexItem.IsMatch(name); } + + /// + /// Creates a data store value from byte array that are stored in database. + /// + /// The type. + /// The bytes. + /// + /// The specified type is not supported. + public static Object CreateObject(DataType type, byte[] bytes) + { + switch (type) + { + case DataType.Int32: + return BitConverter.ToInt32(bytes, 0); + case DataType.Float: + return BitConverter.ToSingle(bytes, 0); + case DataType.Double: + return BitConverter.ToDouble(bytes, 0); + case DataType.Boolean: + return BitConverter.ToBoolean(bytes, 0); + case DataType.String: + return Encoding.Default.GetString(bytes); + case DataType.Bytes: + return bytes; + case DataType.Proto: + return DataStoreProtoObject.FromBytes(bytes); + } + + throw new NotSupportedException("The specified type is not supported."); + } + + /// + /// Creates a byte array that can be stored on database from the specified data store type and value. + /// + /// The type. + /// The object. + /// + /// + /// DataStoreProtoObject + /// or + /// The specified type is not supported. + /// + public static byte[] CreateBytes(DataType type, Object obj) + { + switch (type) + { + case DataType.Int32: + return BitConverter.GetBytes((int)obj); + case DataType.Float: + return BitConverter.GetBytes((float)obj); + case DataType.Double: + return BitConverter.GetBytes((double)obj); + case DataType.Boolean: + return BitConverter.GetBytes((bool)obj); + case DataType.String: + return Encoding.Default.GetBytes(obj.ToString()); + case DataType.Bytes: + return (byte[])obj; + case DataType.Proto: + if (obj is DataStoreProtoObject protoMessage) + { + return protoMessage.ToBytes(); + } + else + { + throw new NotSupportedException($"Data type is 'Proto' but object is not of type '{nameof(DataStoreProtoObject)}'."); + } + } + + throw new NotSupportedException("The specified type is not supported."); + } } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Images/configuration.png b/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Images/configuration.png index f46a49d23..9366f42d6 100644 Binary files a/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Images/configuration.png and b/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Images/configuration.png differ diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Views/SelectionView.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Views/SelectionView.xaml index 51039ca89..a2aa7240b 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Views/SelectionView.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.MachineConfiguration/Views/SelectionView.xaml @@ -21,7 +21,7 @@ - + Select Machine diff --git a/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/MachineConfigurationService.cs b/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/MachineConfigurationService.cs index a30198ff2..5b7991f44 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/MachineConfigurationService.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/MachineConfigurationService.cs @@ -7,6 +7,8 @@ using Tango.BL; using Tango.BL.Entities; using System.Data.Entity; using Tango.BL.Builders; +using Tango.BL.DTO; +using Tango.BL.Enumerations; namespace Tango.FSE.BL.Services { @@ -14,6 +16,8 @@ namespace Tango.FSE.BL.Services { public class MachineEditingComposition : IDisposable { + internal MachineDTO PreviousMachineState { get; set; } + public Machine Machine { get; set; } public List Organizations { get; set; } public List HardwareVersions { get; set; } @@ -36,7 +40,7 @@ namespace Tango.FSE.BL.Services public Task GetMachineEditingComposition(String machineGuid) { - return Task.Factory.StartNew(() => + return Task.Factory.StartNew(() => { ObservablesContext db = ObservablesContext.CreateDefault(); @@ -58,6 +62,9 @@ namespace Tango.FSE.BL.Services int meters = (int)jobRuns.Select(x => x.EndPosition).Sum(); composition.TotalDyeMeters = $"{meters.ToString("N0")} meters"; + //Store current machine state for action log. + composition.PreviousMachineState = MachineDTO.FromObservable(composition.Machine); + composition.Context = db; return composition; @@ -68,7 +75,20 @@ namespace Tango.FSE.BL.Services { return Task.Factory.StartNew(() => { - composition.Machine.SetupFpga = composition.Machine.SetupFirmware; + Authentication.ThrowIfNoPermission(Permissions.FSE_RunConfigurationModule); + ConnectivityProvider.ThrowIfNoInternet(); + + composition.Machine.SetupFpga = composition.Machine.SetupFirmware; //Unify setup FPGA with setup firmware. + + try + { + var machineAfterUpdateDTO = MachineDTO.FromObservable(composition.Machine); + ActionLogManager.InsertLog(ActionLogType.MachineSaved, CurrentUser, composition.Machine.SerialNumber, composition.PreviousMachineState, machineAfterUpdateDTO, "Machine saved via FSE."); + } + catch (Exception ex) + { + LogManager.Log(ex, "Error inserting machine save action log."); + } composition.Context.SaveChanges(); composition.Context.Dispose(); @@ -78,10 +98,12 @@ namespace Tango.FSE.BL.Services public Task ResetCounters(String machineGuid) { - return Task.Factory.StartNew(() => + return Task.Factory.StartNew(() => { using (ObservablesContext db = new ObservablesContext()) { + var sn = db.Machines.SingleOrDefault(x => x.Guid == machineGuid).SerialNumber; + ActionLogManager.InsertLog(ActionLogType.MachineCountersReset, CurrentUser.Guid, sn, machineGuid, "Machine counters reset via FSE."); return db.JobRuns.Where(x => x.MachineGuid == machineGuid).DeleteFromQuery(); } }); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/DataStore/DefaultDataStoreProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/DataStore/DefaultDataStoreProvider.cs index 7b8e6684d..f6ea90378 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/DataStore/DefaultDataStoreProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/DataStore/DefaultDataStoreProvider.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL; +using Tango.BL.ActionLogs; using Tango.BL.DTO; using Tango.BL.Entities; +using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.DI; using Tango.DataStore; @@ -13,6 +15,7 @@ using Tango.DataStore.Editing; using Tango.DataStore.EF; using Tango.DataStore.Remote; using Tango.FSE.BL; +using Tango.FSE.Common.Authentication; using Tango.FSE.Common.Connection; using Tango.FSE.Common.DataStore; @@ -26,6 +29,12 @@ namespace Tango.FSE.UI.DataStore [TangoInject] private FSEServicesContainer Services { get; set; } + [TangoInject] + protected IActionLogManager ActionLogManager { get; set; } + + [TangoInject] + protected IAuthenticationProvider AuthenticationProvider { get; set; } + private bool _acceptFirmwareChanges; public bool AcceptFirmwareChanges { @@ -227,6 +236,11 @@ namespace Tango.FSE.UI.DataStore List globals = new List(); List locals = new List(); List deleted = new List(); + + List actionLogInserted = new List(); + List> actionLogModified = new List>(); + List actionLogDeleted = new List(); + UpdateDataStoreRequest updateRequest = new UpdateDataStoreRequest(); foreach (var collection in model.Collections.Where(x => !x.IsDeleted)) @@ -250,6 +264,8 @@ namespace Tango.FSE.UI.DataStore using (ObservablesContext db = ObservablesContext.CreateDefault()) { + var machine = db.Machines.SingleOrDefault(x => x.Guid == machineGuid); + var allItems = db.DataStoreItems.Where(x => x.MachineGuid == machineGuid).ToList(); //Deleted collections @@ -263,6 +279,7 @@ namespace Tango.FSE.UI.DataStore itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; updateRequest.ToDelete.Add(itemDb.Guid); + actionLogDeleted.Add(DataStoreItemDTO.FromObservable(itemDb)); } } } @@ -277,6 +294,7 @@ namespace Tango.FSE.UI.DataStore itemDb.LastUpdated = DateTime.UtcNow; itemDb.IsSynchronized = false; updateRequest.ToDelete.Add(itemDb.Guid); + actionLogDeleted.Add(DataStoreItemDTO.FromObservable(itemDb)); } } @@ -296,11 +314,14 @@ namespace Tango.FSE.UI.DataStore itemDb.Value = EFDataStoreHelper.CreateBytes(item.Item.Type, item.Item.Value); db.DataStoreItems.Add(itemDb); updateRequest.ToUpsert.Add(DataStoreItemDTO.FromObservable(itemDb)); + actionLogInserted.Add(DataStoreItemDTO.FromObservable(itemDb)); } else //update local item only if changed... { bool upsert = MachineProvider.IsPPCAvailable && !item.Item.ExistsOnMachine; + var itemDbDTO = DataStoreItemDTO.FromObservable(itemDb); + if (itemDb.IsDeleted) //restore if item was deleted.. { itemDb.IsDeleted = false; @@ -324,6 +345,7 @@ namespace Tango.FSE.UI.DataStore if (upsert) { updateRequest.ToUpsert.Add(DataStoreItemDTO.FromObservable(itemDb)); + actionLogModified.Add(new Tuple(itemDbDTO, DataStoreItemDTO.FromObservable(itemDb))); } } } @@ -338,6 +360,33 @@ namespace Tango.FSE.UI.DataStore } db.SaveChanges(); + + try + { + //Save action logs.. + actionLogDeleted = actionLogDeleted.DistinctBy(x => x.Guid).ToList(); + actionLogInserted = actionLogInserted.DistinctBy(x => x.Guid).ToList(); + actionLogModified = actionLogModified.DistinctBy(x => x.Item1.Guid).ToList(); + + foreach (var item in actionLogDeleted) + { + ActionLogManager.InsertLog(ActionLogType.DataStoreItemDeleted, AuthenticationProvider.CurrentUser,$"{machine.SerialNumber} => {item.Key}", item, "Data store item deleted via FSE.", true); + } + + foreach (var item in actionLogInserted) + { + ActionLogManager.InsertLog(ActionLogType.DataStoreItemCreated, AuthenticationProvider.CurrentUser, $"{machine.SerialNumber} => {item.Key}", item, "Data store item created via FSE.", false); + } + + foreach (var item in actionLogModified) + { + ActionLogManager.InsertLog(ActionLogType.DataStoreItemModified, AuthenticationProvider.CurrentUser, $"{machine.SerialNumber} => {item.Item1.Key}", item.Item1, item.Item2, "Data store item modified via FSE."); + } + } + catch (Exception ex) + { + LogManager.Log(ex, "Error saving action logs for data store updates."); + } } return GetDataStoreModel(machineGuid).Result; diff --git a/Software/Visual_Studio/Tango.BL/DTO/DataStoreItemDTO.cs b/Software/Visual_Studio/Tango.BL/DTO/DataStoreItemDTO.cs index 475c9f648..3262970a1 100644 --- a/Software/Visual_Studio/Tango.BL/DTO/DataStoreItemDTO.cs +++ b/Software/Visual_Studio/Tango.BL/DTO/DataStoreItemDTO.cs @@ -4,11 +4,35 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Tango.BL.Entities; +using Tango.DataStore; namespace Tango.BL.DTO { public class DataStoreItemDTO : DataStoreItemDTOBase { + protected override string OnGetActionLogName() + { + return $"'{CollectionName}' => '{Key}'"; + } + public DataType Type { get; set; } + + public String Val { get; set; } + + protected override void OnFromObservableCompleted(DataStoreItem observable) + { + base.OnFromObservableCompleted(observable); + Type = (DataType)observable.DataType; + Val = DataStoreHelper.FormatDataStoreValue(Type, DataStoreHelper.CreateObject(Type, observable.Value)); + } + + protected override bool OnShouldActionLogIgnore(string propName) + { + return + propName == nameof(Value) || + propName == nameof(DataType) || + propName == nameof(IsSynchronized); + } } } diff --git a/Software/Visual_Studio/Tango.BL/Enumerations/ActionLogType.cs b/Software/Visual_Studio/Tango.BL/Enumerations/ActionLogType.cs index e0ba5d586..868bf7915 100644 --- a/Software/Visual_Studio/Tango.BL/Enumerations/ActionLogType.cs +++ b/Software/Visual_Studio/Tango.BL/Enumerations/ActionLogType.cs @@ -96,5 +96,17 @@ namespace Tango.BL.Enumerations //Firmware [Description("Firmware Upgraded")] FirmwareUpgraded = 800, + + //Job Runs + [Description("Machine Counters Reset")] + MachineCountersReset = 900, + + //Data Store + [Description("Data Store Item Created")] + DataStoreItemCreated = 1000, + [Description("Data Store Item Modified")] + DataStoreItemModified = 1001, + [Description("Data Store Item Deleted")] + DataStoreItemDeleted = 1002, } } diff --git a/Software/Visual_Studio/Tango.BL/ObservableEntityDTO.cs b/Software/Visual_Studio/Tango.BL/ObservableEntityDTO.cs index 9b8aa0687..1c3edac82 100644 --- a/Software/Visual_Studio/Tango.BL/ObservableEntityDTO.cs +++ b/Software/Visual_Studio/Tango.BL/ObservableEntityDTO.cs @@ -106,6 +106,8 @@ namespace Tango.BL } } + dto.OnFromObservableCompleted(observable); + return dto; } @@ -319,5 +321,14 @@ namespace Tango.BL { return this.GetType().Name; } + + /// + /// Called when the static method completes. + /// + /// The observable. + protected virtual void OnFromObservableCompleted(T observable) + { + //Just for override + } } } diff --git a/Software/Visual_Studio/Tango.BL/Tango.BL.csproj b/Software/Visual_Studio/Tango.BL/Tango.BL.csproj index 1281c4949..4da8606f7 100644 --- a/Software/Visual_Studio/Tango.BL/Tango.BL.csproj +++ b/Software/Visual_Studio/Tango.BL/Tango.BL.csproj @@ -597,6 +597,10 @@ + + {e0364dfa-0721-4637-9d32-9d22aac109d6} + Tango.DataStore + {37e4ceab-b54b-451f-b535-04cf7da9c459} ColorMine @@ -641,7 +645,7 @@ - + \ No newline at end of file diff --git a/Software/Visual_Studio/Utilities/Tango.JobRunsGenerator/Program.cs b/Software/Visual_Studio/Utilities/Tango.JobRunsGenerator/Program.cs index 11c54cf64..71d5779a6 100644 --- a/Software/Visual_Studio/Utilities/Tango.JobRunsGenerator/Program.cs +++ b/Software/Visual_Studio/Utilities/Tango.JobRunsGenerator/Program.cs @@ -19,7 +19,7 @@ namespace Tango.JobRunsGenerator static void Main(string[] args) { DataSource dataSource = new DataSource(); - dataSource.Catalog = "Tango_TEST"; + dataSource.Catalog = "Tango_DEV"; dataSource.Address = "twine.database.windows.net"; dataSource.IntegratedSecurity = false; dataSource.UserName = "Roy"; -- cgit v1.3.1