From 02fa2569394d58d1b5d4591db87cd64eabd40eb7 Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:58:36 +0100 Subject: [PATCH 1/6] Add caching for message counts and update observer init Introduce 500ms caching for ActiveMessagesCount and RelevantMessagesCount in AxoMessageProvider to reduce flickering and improve performance. Ensure thread safety with locks and add InvalidateMessageCountCache for manual cache invalidation. Update AxoObjectSpotView.razor to use InitializeUpdate instead of InitializeLightUpdate. --- .../AxoObject/AxoObjectSpotView.razor | 2 +- .../AxoMessenger/Static/AxoMessageProvider.cs | 106 ++++++++++++++---- 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/core/src/AXOpen.Core.Blazor/AxoObject/AxoObjectSpotView.razor b/src/core/src/AXOpen.Core.Blazor/AxoObject/AxoObjectSpotView.razor index 63f365959..755d1b634 100644 --- a/src/core/src/AXOpen.Core.Blazor/AxoObject/AxoObjectSpotView.razor +++ b/src/core/src/AXOpen.Core.Blazor/AxoObject/AxoObjectSpotView.razor @@ -92,7 +92,7 @@ /// protected override async Task OnInitializedAsync() { - if (observer != null) await observer.InitializeLightUpdate(this.StartPolling); + if (observer != null) await observer.InitializeUpdate(this.StartPolling); await base.OnInitializedAsync(); } } diff --git a/src/core/src/AXOpen.Core/AxoMessenger/Static/AxoMessageProvider.cs b/src/core/src/AXOpen.Core/AxoMessenger/Static/AxoMessageProvider.cs index a050ae1d5..e6f77c2d2 100644 --- a/src/core/src/AXOpen.Core/AxoMessenger/Static/AxoMessageProvider.cs +++ b/src/core/src/AXOpen.Core/AxoMessenger/Static/AxoMessageProvider.cs @@ -23,59 +23,102 @@ private AxoMessageProvider(IEnumerable observedObjects) public IEnumerable ObservedObjects { get; } + private int? _cachedActiveMessagesCount; + private DateTime _lastActiveMessagesCountUpdate = DateTime.MinValue; + private readonly TimeSpan _activeMessagesCountCacheDuration = TimeSpan.FromMilliseconds(500); + private readonly object _activeMessagesCountLock = new object(); + /// /// Gets the number of active messages. /// /// /// This property counts the number of messages that are currently active. /// An active message is defined as a message belonging to a Messenger that has a state other than Idle or NotActiveWatingAckn. + /// The value is cached for a short period to prevent flickering due to asynchronous PLC communication. /// public int? ActiveMessagesCount { get { - try - { - return ObservedObjects - .OfType() - .Select(p => Convert.ToInt32(p.MsgCnt.LastValue)) // Convert to appropriate numeric type - .Sum(); - } - catch (Exception e) + lock (_activeMessagesCountLock) { - Console.WriteLine(e); + var now = DateTime.UtcNow; + + // Return cached value if still valid + if (_cachedActiveMessagesCount.HasValue && + (now - _lastActiveMessagesCountUpdate) < _activeMessagesCountCacheDuration) + { + return _cachedActiveMessagesCount; + } + + try + { + var count = ObservedObjects + .OfType() + .Select(p => Convert.ToInt32(p.MsgCnt.LastValue)) + .Sum(); + + _cachedActiveMessagesCount = count; + _lastActiveMessagesCountUpdate = now; + + return count; + } + catch (Exception e) + { + return -1; + } } - - return 0; } } + private int? _cachedRelevantMessagesCount; + private DateTime _lastRelevantMessagesCountUpdate = DateTime.MinValue; + private readonly TimeSpan _relevantMessagesCountCacheDuration = TimeSpan.FromMilliseconds(500); + private readonly object _relevantMessagesCountLock = new object(); + /// /// Gets the count of relevant messages based on the state of Messengers. /// /// /// The RelevantMessagesCount property will return the number of messengers that have a state greater than eAxoMessengerState.Idle. /// Messengers is a collection of objects that represents messengers. + /// The value is cached for a short period to prevent flickering due to asynchronous PLC communication. /// /// An integer that represents the count of relevant messages. public int? RelevantMessagesCount { get { - try - { - return ObservedObjects - .OfType() - .Select(p => Convert.ToInt32(p.MsgCnt.LastValue)) // Convert to appropriate numeric type - .Sum(); - } - catch (Exception e) + lock (_relevantMessagesCountLock) { - Console.WriteLine(e); - } + var now = DateTime.UtcNow; + + // Return cached value if still valid + if (_cachedRelevantMessagesCount.HasValue && + (now - _lastRelevantMessagesCountUpdate) < _relevantMessagesCountCacheDuration) + { + return _cachedRelevantMessagesCount; + } - return 0; + try + { + var count = ObservedObjects + .OfType() + .Select(p => Convert.ToInt32(p.MsgCnt.LastValue)) + .Sum(); + + _cachedRelevantMessagesCount = count; + _lastRelevantMessagesCountUpdate = now; + + return count; + } + catch (Exception e) + { + Console.WriteLine(e); + return 0; + } + } } } @@ -190,5 +233,24 @@ public async Task ReadMessageStateAsync() await con.ReadBatchAsync(r)!; } } + + /// + /// Invalidates the cached message counts, forcing them to be recalculated on next access. + /// Use this method if you need to ensure fresh values after a batch read operation. + /// + public void InvalidateMessageCountCache() + { + lock (_activeMessagesCountLock) + { + _cachedActiveMessagesCount = null; + _lastActiveMessagesCountUpdate = DateTime.MinValue; + } + + lock (_relevantMessagesCountLock) + { + _cachedRelevantMessagesCount = null; + _lastRelevantMessagesCountUpdate = DateTime.MinValue; + } + } } } From 47bb376fdee5eec5d397a3a58f6f092953895b4d Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:43:07 +0100 Subject: [PATCH 2/6] wip --- .../Data/IBrowsableDataObject.cs | 5 ++++- .../Data/Query/PredicateContainer.cs | 10 ++++++++++ .../AXOpen.Base.Abstractions/Data/RepositoryBase.cs | 4 +++- src/core/ctrl/src/AxoObject/AxoObject.st | 10 +++++++++- .../DataFragmentExchange/AxoDataFragmentExchange.cs | 2 +- .../DataPersistentExchange/PersistentRecord.cs | 7 +++++++ src/data/src/AXOpen.Data/Entity/AxoDataEntity.cs | 1 + src/data/src/AXOpen.Data/Entity/Pocos/AxoDataEntity.cs | 3 +++ .../src/AXOpen.Data/Entity/Pocos/IAxoDataEntity.cs | 2 ++ .../repositories/MongoDb/Mongo/MongoDbRepository.cs | 6 ++++++ .../MongoDb/Mongo/MongoDbRepositorySettings.cs | 10 +++++++--- src/security/src/AXOpen.Security/Entities/Group.cs | 8 ++++++++ src/security/src/AXOpen.Security/Entities/User.cs | 7 +++++++ 13 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/base/src/AXOpen.Base.Abstractions/Data/IBrowsableDataObject.cs b/src/base/src/AXOpen.Base.Abstractions/Data/IBrowsableDataObject.cs index b74ab9f8e..9fd8dfbf4 100644 --- a/src/base/src/AXOpen.Base.Abstractions/Data/IBrowsableDataObject.cs +++ b/src/base/src/AXOpen.Base.Abstractions/Data/IBrowsableDataObject.cs @@ -6,6 +6,9 @@ public interface IBrowsableDataObject { dynamic RecordId { get; set; } - string _EntityId { get; set; } + string _EntityId { get; set; } + + DateTime? ModifiedAt { get; set; } + DateTime? CreatedAt { get; set; } } } \ No newline at end of file diff --git a/src/base/src/AXOpen.Base.Abstractions/Data/Query/PredicateContainer.cs b/src/base/src/AXOpen.Base.Abstractions/Data/Query/PredicateContainer.cs index 506d92d4a..e9e8cf0dc 100644 --- a/src/base/src/AXOpen.Base.Abstractions/Data/Query/PredicateContainer.cs +++ b/src/base/src/AXOpen.Base.Abstractions/Data/Query/PredicateContainer.cs @@ -125,6 +125,16 @@ public void AddPredicates(Expression> predicate) this._predicates[typeof(T)].Add(predicate); } + public void AddPredicates(Type type, Expression> expression) + { + if (!this._predicates.ContainsKey(type)) + { + this._predicates[type] = new List(); + } + + this._predicates[type].Add(expression); + } + public void AddPredicates(Type type, LambdaExpression predicate) { if (!this._predicates.ContainsKey(type)) diff --git a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs index 9db455f5a..86d19a7d4 100644 --- a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs +++ b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs @@ -351,6 +351,7 @@ public void Create(string identifier, T data) } try { + data.ModifiedAt = DateTime.UtcNow; CreateNvi(identifier, data); } catch (Exception e) @@ -409,12 +410,13 @@ public void Update(string identifier, T data) } try { + data.ModifiedAt = DateTime.UtcNow; UpdateNvi(identifier, data); } catch (Exception e) { OnUpdateFailed?.Invoke(identifier, data, e); - throw e; + throw; } OnUpdateDone?.Invoke(identifier, data); } diff --git a/src/core/ctrl/src/AxoObject/AxoObject.st b/src/core/ctrl/src/AxoObject/AxoObject.st index aac296d8d..9948b5087 100644 --- a/src/core/ctrl/src/AxoObject/AxoObject.st +++ b/src/core/ctrl/src/AxoObject/AxoObject.st @@ -95,6 +95,9 @@ NAMESPACE AXOpen.Core NULL_CONTEXT_OBJ : IAxoObject; END_VAR + // We always reset message count upon initialization. + THIS.MsgCnt := LINT#0; + IF _isInitialized THEN RETURN; END_IF; @@ -117,7 +120,7 @@ NAMESPACE AXOpen.Core _errorState := UINT#40; RETURN; END_IF; - + _context := inParent.GetContext(); _parent := inParent; _isInitialized := TRUE; @@ -141,6 +144,9 @@ NAMESPACE AXOpen.Core inContext : IAxoContext; END_VAR + + // We always reset message count upon initialization. + THIS.MsgCnt := LINT#0; IF _isInitialized THEN RETURN; END_IF; @@ -151,6 +157,8 @@ NAMESPACE AXOpen.Core RETURN; END_IF; + + _context := inContext; _isInitialized := TRUE; diff --git a/src/data/src/AXOpen.Data/DataFragmentExchange/AxoDataFragmentExchange.cs b/src/data/src/AXOpen.Data/DataFragmentExchange/AxoDataFragmentExchange.cs index 083c609a3..d646f03ef 100644 --- a/src/data/src/AXOpen.Data/DataFragmentExchange/AxoDataFragmentExchange.cs +++ b/src/data/src/AXOpen.Data/DataFragmentExchange/AxoDataFragmentExchange.cs @@ -500,7 +500,7 @@ public IEnumerable GetRecords(string identifier, int limit { return ((dynamic)Repository)?.GetRecords(identifier, limit, skip, searchMode, sortExpression, sortAscending); } - + public IEnumerable GetRecords(PredicateContainer predicates, int limit, int skip) { diff --git a/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs b/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs index b663cd7ee..1897a432c 100644 --- a/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs +++ b/src/data/src/AXOpen.Data/DataPersistentExchange/PersistentRecord.cs @@ -1,4 +1,5 @@ using AXOpen.Base.Data; +using Microsoft.AspNetCore.Http.HttpResults; namespace AXOpen.Data { @@ -11,6 +12,12 @@ public class PersistentRecord : IBrowsableDataObject public DateTime _Created { set; get; } public DateTime _Modified { set; get; } + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? ModifiedAt { get { return _Modified; } set { _Modified = value.Value; } } + + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? CreatedAt { get { return _Created; } set { _Created = value.Value; } } + public List Tags = new(); } } \ No newline at end of file diff --git a/src/data/src/AXOpen.Data/Entity/AxoDataEntity.cs b/src/data/src/AXOpen.Data/Entity/AxoDataEntity.cs index 7c5c206e6..7c8460e7b 100644 --- a/src/data/src/AXOpen.Data/Entity/AxoDataEntity.cs +++ b/src/data/src/AXOpen.Data/Entity/AxoDataEntity.cs @@ -14,6 +14,7 @@ partial void PostConstruct(ITwinObject parent, string readableTail, string symbo ChangeTracker = new ValueChangeTracker(this); } + public List Changes { get; set; } public string Hash { get; set; } diff --git a/src/data/src/AXOpen.Data/Entity/Pocos/AxoDataEntity.cs b/src/data/src/AXOpen.Data/Entity/Pocos/AxoDataEntity.cs index 2e676e7a7..a5046a1e5 100644 --- a/src/data/src/AXOpen.Data/Entity/Pocos/AxoDataEntity.cs +++ b/src/data/src/AXOpen.Data/Entity/Pocos/AxoDataEntity.cs @@ -22,5 +22,8 @@ public List Changes } public string Hash { get; set; } + + public DateTime? ModifiedAt { get; set; } + public DateTime? CreatedAt { get; set; } } } \ No newline at end of file diff --git a/src/data/src/AXOpen.Data/Entity/Pocos/IAxoDataEntity.cs b/src/data/src/AXOpen.Data/Entity/Pocos/IAxoDataEntity.cs index 22d13c042..5469c0567 100644 --- a/src/data/src/AXOpen.Data/Entity/Pocos/IAxoDataEntity.cs +++ b/src/data/src/AXOpen.Data/Entity/Pocos/IAxoDataEntity.cs @@ -10,6 +10,8 @@ public partial interface IAxoDataEntity : IBrowsableDataObject public string _EntityId { get; set; } List Changes { get; set; } + + string Hash { get; set; } } } diff --git a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepository.cs b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepository.cs index e2a302ab3..c246779f0 100644 --- a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepository.cs +++ b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepository.cs @@ -39,6 +39,11 @@ public class MongoDbRepository : RepositoryBase public override long LastFragmentQueryCount { get; protected set; } + /// + /// Gets repository settings. + /// + public MongoDbRepositorySettings Settings { get; private set; } + /// /// Creates new instance of . /// @@ -47,6 +52,7 @@ public MongoDbRepository(MongoDbRepositorySettings parameters) { location = parameters.GetConnectionInfo(); this.collection = parameters.Collection; + Settings = parameters; } private bool RecordExists(string identifier) diff --git a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs index 1288cc2fb..0a3792702 100644 --- a/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs +++ b/src/data/src/repositories/MongoDb/Mongo/MongoDbRepositorySettings.cs @@ -21,7 +21,11 @@ namespace AXOpen.Data.MongoDb public class MongoDbRepositorySettings : RepositorySettings where T : IBrowsableDataObject { private string _databaseName; - private string _collectionName; + + /// + /// Gets collection name. + /// + public string CollectionName { get; private set; } /// /// Creates new instance of for a with NON-SECURED access. @@ -144,7 +148,7 @@ private IMongoDatabase GetDatabase(string databaseName) } private IMongoCollection GetCollection(string collectionName) { - _collectionName = collectionName; + CollectionName = collectionName; var existingClient = Collections.Where(p => p.Key == collectionName).Select(p => p.Value); if (existingClient.Count() >= 1) { @@ -258,7 +262,7 @@ private static void SetupSerialisationAndMapping(Expression> idE public string GetConnectionInfo() { - return $"{this.Client.Settings.Server.Host}:{this.Client.Settings.Server.Port} {this._databaseName}.{this._collectionName}"; + return $"{this.Client.Settings.Server.Host}:{this.Client.Settings.Server.Port} {this._databaseName}.{this.CollectionName}"; } public void WaitForMongoServerAvailability() diff --git a/src/security/src/AXOpen.Security/Entities/Group.cs b/src/security/src/AXOpen.Security/Entities/Group.cs index 70e9c6585..7231ef770 100644 --- a/src/security/src/AXOpen.Security/Entities/Group.cs +++ b/src/security/src/AXOpen.Security/Entities/Group.cs @@ -12,6 +12,14 @@ public class Group : IBrowsableDataObject public string RolesHash { get; set; } public DateTime Created { get; set; } public DateTime Modified { get; set; } + + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? ModifiedAt { get { return Modified; } set { Modified = value.Value; } } + + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? CreatedAt { get { return Created; } set { Created = value.Value; } } + + public List Changes = new List(); public Group(string name) diff --git a/src/security/src/AXOpen.Security/Entities/User.cs b/src/security/src/AXOpen.Security/Entities/User.cs index f5124ad45..4fd7ffad7 100644 --- a/src/security/src/AXOpen.Security/Entities/User.cs +++ b/src/security/src/AXOpen.Security/Entities/User.cs @@ -12,6 +12,13 @@ public class User : IdentityUser, IBrowsableDataObject public string _EntityId { get; set; } public DateTime Created { get; set; } public DateTime Modified { get; set; } + + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? ModifiedAt { get { return Modified; } set { Modified = value.Value; } } + + // Added due to IBrowsableDataObject interface and compatibility with Prometheus. + public DateTime? CreatedAt { get { return Created; } set { Created = value.Value; } } + public bool EnableAutoLogOut { get; set; } public uint AutoLogOutTimeOutMinutes { get; set; } public string? ExternalAuthId { get; set; } From 9bc9196894d5a482c6557a76de85c42c6fe12099 Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:41:47 +0100 Subject: [PATCH 3/6] Fix null reference issues in logging methods and clean up MongoDB project references --- src/base/src/AXOpen.Logging/SerilogLogger.cs | 24 +++++++++---------- .../MongoDb/AXOpen.Data.MongoDb.csproj | 5 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/base/src/AXOpen.Logging/SerilogLogger.cs b/src/base/src/AXOpen.Logging/SerilogLogger.cs index adb250d80..b20c4ad34 100644 --- a/src/base/src/AXOpen.Logging/SerilogLogger.cs +++ b/src/base/src/AXOpen.Logging/SerilogLogger.cs @@ -30,83 +30,83 @@ public SerilogLogger(Serilog.ILogger logger) public void Debug(string message, IIdentity identity) { - Log.Debug("{message} {identity}",message, new { UserName = identity.Name }); + Log.Debug("{message} {identity}",message, new { UserName = identity?.Name }); } public void Debug(string message, ITwinElement sender, IIdentity identity, object details) { Log.Debug($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details ); } public void Verbose(string message, IIdentity identity) { - Log.Verbose("{message} {identity}", message, new { UserName = identity.Name }); + Log.Verbose("{message} {identity}", message, new { UserName = identity?.Name }); } public void Verbose(string message, ITwinElement sender, IIdentity identity, object details) { Log.Verbose($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details ); } public void Information(string message, IIdentity identity) { - Log.Information("{message} {identity}", message,new { UserName = identity.Name }); + Log.Information("{message} {identity}", message,new { UserName = identity?.Name }); } public void Information(string message, ITwinElement sender, IIdentity identity, object details) { Log.Information($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details); } public void Warning(string message, IIdentity identity) { - Log.Warning("{message} {identity}", message, new { UserName = identity.Name }); + Log.Warning("{message} {identity}", message, new { UserName = identity?.Name }); } public void Warning(string message, ITwinElement sender, IIdentity identity, object details) { Log.Warning($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details ); } public void Error(string message, IIdentity identity) { - Log.Error("{message} {identity}", message, new { UserName = identity.Name }); + Log.Error("{message} {identity}", message, new { UserName = identity?.Name }); } public void Error(string message, ITwinElement sender, IIdentity identity, object details) { Log.Error($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details ); } public void Fatal(string message, IIdentity identity) { - Log.Fatal("{message} {identity}", message, new { UserName = identity.Name }); + Log.Fatal("{message} {identity}", message, new { UserName = identity?.Name }); } public void Fatal(string message, ITwinElement sender, IIdentity identity, object details) { Log.Fatal($"{message} {{sender}} {{identity}} {{details}}", new { Symbol = sender?.Symbol, Label = sender?.HumanReadable }, - new { UserName = identity.Name, Type = identity.AuthenticationType }, + new { UserName = identity.Name, Type = identity?.AuthenticationType }, details ); } diff --git a/src/data/src/repositories/MongoDb/AXOpen.Data.MongoDb.csproj b/src/data/src/repositories/MongoDb/AXOpen.Data.MongoDb.csproj index de9fe8045..18465bcd4 100644 --- a/src/data/src/repositories/MongoDb/AXOpen.Data.MongoDb.csproj +++ b/src/data/src/repositories/MongoDb/AXOpen.Data.MongoDb.csproj @@ -4,9 +4,8 @@ - - - + + From a718b2b2a94a086e366b9d55413b0f9f51460e6c Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:50:39 +0100 Subject: [PATCH 4/6] Add CreatedAt/ModifiedAt fields and apax.yml config Added CreatedAt and ModifiedAt properties to test data classes for timestamp tracking. Updated RepositoryBase to initialize these fields on creation. Introduced apax.yml for "apax.traversal" app with dependencies and config. --- .../Data/RepositoryBase.cs | 1 + .../DataTestObject.cs | 6 ++ .../DataTestObject.cs | 5 ++ .../issues/GH_ixax_AXOpen_188.cs | 3 + src/traversals/apax/apax.yml | 81 +++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 src/traversals/apax/apax.yml diff --git a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs index 86d19a7d4..3a9fc9b7c 100644 --- a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs +++ b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs @@ -351,6 +351,7 @@ public void Create(string identifier, T data) } try { + data.CreatedAt = DateTime.UtcNow; data.ModifiedAt = DateTime.UtcNow; CreateNvi(identifier, data); } diff --git a/src/data/tests/AXOpen.Repository.Integration.Tests_L1/DataTestObject.cs b/src/data/tests/AXOpen.Repository.Integration.Tests_L1/DataTestObject.cs index 4b3c7afb8..779513845 100644 --- a/src/data/tests/AXOpen.Repository.Integration.Tests_L1/DataTestObject.cs +++ b/src/data/tests/AXOpen.Repository.Integration.Tests_L1/DataTestObject.cs @@ -14,6 +14,9 @@ public DataTestObject() UlongMax = ulong.MaxValue; } + public DateTime? ModifiedAt { get; set; } = DateTime.UtcNow; + public DateTime? CreatedAt { get; set; } = DateTime.UtcNow; + public string Name { get; set; } public DateTime DateOfBirth { get; set; } @@ -158,6 +161,9 @@ public DataTestObjectAlteredStructure() UlongMax = ulong.MaxValue; } + public DateTime? ModifiedAt { get; set; } = DateTime.UtcNow; + public DateTime? CreatedAt { get; set; } = DateTime.UtcNow; + public string Name { get; set; } public DateTime DateOfBirth { get; set; } diff --git a/src/data/tests/AXOpen.Repository.Integration.Tests_L3/DataTestObject.cs b/src/data/tests/AXOpen.Repository.Integration.Tests_L3/DataTestObject.cs index 0954159fb..162c23795 100644 --- a/src/data/tests/AXOpen.Repository.Integration.Tests_L3/DataTestObject.cs +++ b/src/data/tests/AXOpen.Repository.Integration.Tests_L3/DataTestObject.cs @@ -14,6 +14,9 @@ public DataTestObject() UlongMax = ulong.MaxValue; } + public DateTime? ModifiedAt { get; set; } = DateTime.UtcNow; + public DateTime? CreatedAt { get; set; } = DateTime.UtcNow; + public string Name { get; set; } public DateTime DateOfBirth { get; set; } @@ -158,6 +161,8 @@ public DataTestObjectAlteredStructure() UlongMax = ulong.MaxValue; } + public DateTime? ModifiedAt { get; set; } = DateTime.UtcNow; + public DateTime? CreatedAt { get; set; } = DateTime.UtcNow; public string Name { get; set; } public DateTime DateOfBirth { get; set; } diff --git a/src/data/tests/AXOpen.Repository.Integration.Tests_L3/issues/GH_ixax_AXOpen_188.cs b/src/data/tests/AXOpen.Repository.Integration.Tests_L3/issues/GH_ixax_AXOpen_188.cs index 867afd54c..ad95e291c 100644 --- a/src/data/tests/AXOpen.Repository.Integration.Tests_L3/issues/GH_ixax_AXOpen_188.cs +++ b/src/data/tests/AXOpen.Repository.Integration.Tests_L3/issues/GH_ixax_AXOpen_188.cs @@ -42,6 +42,9 @@ public virtual void TearDown() public class TestStruct : IBrowsableDataObject { + public DateTime? ModifiedAt { get; set; } = DateTime.UtcNow; + public DateTime? CreatedAt { get; set; } = DateTime.UtcNow; + public dynamic RecordId { get; set; } public string _EntityId { get; set; } public List Changes { get; set; } = new(); diff --git a/src/traversals/apax/apax.yml b/src/traversals/apax/apax.yml new file mode 100644 index 000000000..46e3040d3 --- /dev/null +++ b/src/traversals/apax/apax.yml @@ -0,0 +1,81 @@ +name: "apax.traversal" +version: "0.0.0-dev.0" +type: "app" +targets: +- "1500" +registries: + '@inxton': "https://npm.pkg.github.com/" +devDependencies: + '@inxton/ax-sdk': "0.0.0-dev.0" +dependencies: + "abstractions-app": "0.0.0-dev.0" + "@inxton/axopen.abstractions": "0.0.0-dev.0" + "@inxton/ax.axopen.app": "0.0.0-dev.0" + "@inxton/ax.axopen.hwlibrary": "0.0.0-dev.0" + "@inxton/ax.axopen.min": "0.0.0-dev.0" + "@inxton/ax.catalog": "0.0.42" + "app_axopen.components.abb.robotics": "0.0.0-dev.0" + "@inxton/axopen.components.abb.robotics": "0.0.0-dev.0" + "components.abstractions-app": "0.0.0-dev.0" + "@inxton/axopen.components.abstractions": "0.0.0-dev.0" + "app_axopen.components.balluff.identification": "0.0.0-dev.0" + "@inxton/axopen.components.balluff.identification": "0.0.0-dev.0" + "app_axopen.components.cognex.vision": "0.0.0-dev.0" + "@inxton/axopen.components.cognex.vision": "0.0.0-dev.0" + "app_axopen.components.desoutter.tightening": "0.0.0-dev.0" + "@inxton/axopen.components.desoutter.tightening": "0.0.0-dev.0" + "app_axopen.components.drives": "0.0.0-dev.0" + "@inxton/axopen.components.drives": "0.0.0-dev.0" + "app_axopen.components.dukane.welders": "0.0.0-dev.0" + "@inxton/axopen.components.dukane.welders": "0.0.0-dev.0" + "elementscomponents": "0.0.0-dev.0" + "@inxton/axopen.components.elements": "0.0.0-dev.0" + "app_axopen.components.festo.drives": "0.0.0-dev.0" + "@inxton/axopen.components.festo.drives": "0.0.0-dev.0" + "app_axopen.components.keyence.vision": "0.0.0-dev.0" + "@inxton/axopen.components.keyence.vision": "0.0.0-dev.0" + "app_axopen.components.kuka.robotics": "0.0.0-dev.0" + "@inxton/axopen.components.kuka.robotics": "0.0.0-dev.0" + "app_axopen.components.mitsubishi.robotics": "0.0.0-dev.0" + "@inxton/axopen.components.mitsubishi.robotics": "0.0.0-dev.0" + "pneumaticcomponents": "0.0.0-dev.0" + "@inxton/axopen.components.pneumatics": "0.0.0-dev.0" + "app_axopen.components.rexroth.drives": "0.0.0-dev.0" + "@inxton/axopen.components.rexroth.drives": "0.0.0-dev.0" + "app_axopen.components.rexroth.press": "0.0.0-dev.0" + "@inxton/axopen.components.rexroth.press": "0.0.0-dev.0" + "app_axopen.components.rexroth.tightening": "0.0.0-dev.0" + "@inxton/axopen.components.rexroth.tightening": "0.0.0-dev.0" + "app_axopen.components.robotics": "0.0.0-dev.0" + "@inxton/axopen.components.robotics": "0.0.0-dev.0" + "app_axopen.components.siem.communication": "0.0.0-dev.0" + "@inxton/axopen.components.siem.communication": "0.0.0-dev.0" + "app_axopen.components.siem.identification": "0.0.0-dev.0" + "@inxton/axopen.components.siem.identification": "0.0.0-dev.0" + "app_axopen.components.ur.robotics": "0.0.0-dev.0" + "@inxton/axopen.components.ur.robotics": "0.0.0-dev.0" + "app_axopen.components.zebra.vision": "0.0.0-dev.0" + "@inxton/axopen.components.zebra.vision": "0.0.0-dev.0" + "ix_axopencore": "0.0.0-dev.0" + "@inxton/axopen.core": "0.0.0-dev.0" + "axopen.data-app": "0.0.0-dev.0" + "@inxton/axopen.data": "0.0.0-dev.0" + "axopen.data.tests_l1": "0.0.0-dev.0" + "axopen.integration.tests_l4": "0.0.0-dev.0" + "axopen.inspectors": "0.0.0-dev.0" + "@inxton/axopen.inspectors": "0.0.0-dev.0" + "axopen.integrations": "0.0.0-dev.0" + "app_axopen.io": "0.0.0-dev.0" + "@inxton/axopen.io": "0.0.0-dev.0" + "probers-app": "0.0.0-dev.0" + "@inxton/axopen.probers": "0.0.0-dev.0" + "simatic1500-app": "0.0.0-dev.0" + "@inxton/axopen.simatic1500": "0.0.0-dev.0" + "app_apaxappname": "0.0.0-dev.0" + "@inxton/apaxlibname": "0.0.0-dev.0" + "timers-app": "0.0.0-dev.0" + "@inxton/axopen.timers": "0.0.0-dev.0" + "utils-app": "0.0.0-dev.0" + "@inxton/axopen.utils": "0.0.0-dev.0" +installStrategy: "overridable" +... From 9c96716778d432767e212264991f14e9daa687c8 Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:05:01 +0100 Subject: [PATCH 5/6] Safely set ModifiedAt using null-conditional operator Prevent possible NullReferenceException by using the null-conditional operator when assigning to data.ModifiedAt. This ensures the assignment only occurs if the data object is not null. --- .../src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs index 3a9fc9b7c..b89a64b29 100644 --- a/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs +++ b/src/base/src/AXOpen.Base.Abstractions/Data/RepositoryBase.cs @@ -351,8 +351,8 @@ public void Create(string identifier, T data) } try { - data.CreatedAt = DateTime.UtcNow; - data.ModifiedAt = DateTime.UtcNow; + data?.CreatedAt = DateTime.UtcNow; + data?.ModifiedAt = DateTime.UtcNow; CreateNvi(identifier, data); } catch (Exception e) @@ -411,7 +411,7 @@ public void Update(string identifier, T data) } try { - data.ModifiedAt = DateTime.UtcNow; + data?.ModifiedAt = DateTime.UtcNow; UpdateNvi(identifier, data); } catch (Exception e) From fa4dfe3b3cdeb824a248caefadb644095a7b02b9 Mon Sep 17 00:00:00 2001 From: Peter Kurhajec <61538034+PTKu@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:05:30 +0100 Subject: [PATCH 6/6] Clean up add AXOpen.Operon.Blazor project and remove apax.yml Added AXOpen.Operon.Blazor to the solution with appropriate folder structure and solution configuration updates. Also removed the apax.yml file from the repository. --- src/AXOpen-L3-tests.sln | 12 ++++++ src/traversals/apax/apax.yml | 81 ------------------------------------ 2 files changed, 12 insertions(+), 81 deletions(-) delete mode 100644 src/traversals/apax/apax.yml diff --git a/src/AXOpen-L3-tests.sln b/src/AXOpen-L3-tests.sln index 7967365a8..d292d8a1a 100644 --- a/src/AXOpen-L3-tests.sln +++ b/src/AXOpen-L3-tests.sln @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Security", "Security EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "inxton_axopen_simatic1500", "simatic1500\ctrl\ix\inxton_axopen_simatic1500.csproj", "{153256A2-1F20-4652-9577-EA3F21C58604}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.Operon.Blazor", "styling\src\AXOpen.Operon.Blazor.csproj", "{AB74CFA4-4E4A-4142-A491-8059359297B8}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "inxton_axopen_timers", "timers\src\AXOpen.Timers\inxton_axopen_timers.csproj", "{64E1326E-63C4-4623-B3E9-AE6C33D3D4A4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AXOpen.ToolBox", "toolbox\src\AXOpen.ToolBox\AXOpen.ToolBox.csproj", "{4FFE9FDF-E6FB-4B5F-A4C8-9623F6358A7B}" @@ -173,6 +175,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ctrl", "simatic1500\ctrl", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "simatic1500", "simatic1500", "{09E8FEB5-793C-47E0-90A2-3B30199FB321}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "styling\src", "{9B9A3CC5-663F-4767-B17B-5EB7F2C001DE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "styling", "styling", "{91077ADC-5D83-4331-977B-BAC82A86A64D}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AXOpen.Timers", "timers\src\AXOpen.Timers", "{93475E41-C4BC-48D7-96F5-E488C3ADFCFD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "timers\src", "{D33BF467-F818-440E-AC41-00B02A851D8B}" @@ -309,6 +315,10 @@ Global {153256A2-1F20-4652-9577-EA3F21C58604}.Debug|Any CPU.Build.0 = Debug|Any CPU {153256A2-1F20-4652-9577-EA3F21C58604}.Release|Any CPU.ActiveCfg = Release|Any CPU {153256A2-1F20-4652-9577-EA3F21C58604}.Release|Any CPU.Build.0 = Release|Any CPU + {AB74CFA4-4E4A-4142-A491-8059359297B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB74CFA4-4E4A-4142-A491-8059359297B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB74CFA4-4E4A-4142-A491-8059359297B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB74CFA4-4E4A-4142-A491-8059359297B8}.Release|Any CPU.Build.0 = Release|Any CPU {64E1326E-63C4-4623-B3E9-AE6C33D3D4A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {64E1326E-63C4-4623-B3E9-AE6C33D3D4A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {64E1326E-63C4-4623-B3E9-AE6C33D3D4A4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -397,6 +407,8 @@ Global {153256A2-1F20-4652-9577-EA3F21C58604} = {5D7F1861-9390-47C6-ABB5-D0371D58292E} {5D7F1861-9390-47C6-ABB5-D0371D58292E} = {EBE4D729-DBF6-440F-9C1E-93A7D09668CC} {EBE4D729-DBF6-440F-9C1E-93A7D09668CC} = {09E8FEB5-793C-47E0-90A2-3B30199FB321} + {AB74CFA4-4E4A-4142-A491-8059359297B8} = {9B9A3CC5-663F-4767-B17B-5EB7F2C001DE} + {9B9A3CC5-663F-4767-B17B-5EB7F2C001DE} = {91077ADC-5D83-4331-977B-BAC82A86A64D} {64E1326E-63C4-4623-B3E9-AE6C33D3D4A4} = {93475E41-C4BC-48D7-96F5-E488C3ADFCFD} {93475E41-C4BC-48D7-96F5-E488C3ADFCFD} = {D33BF467-F818-440E-AC41-00B02A851D8B} {D33BF467-F818-440E-AC41-00B02A851D8B} = {89504C16-C7D9-4274-A430-850E3DC30C37} diff --git a/src/traversals/apax/apax.yml b/src/traversals/apax/apax.yml deleted file mode 100644 index 46e3040d3..000000000 --- a/src/traversals/apax/apax.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: "apax.traversal" -version: "0.0.0-dev.0" -type: "app" -targets: -- "1500" -registries: - '@inxton': "https://npm.pkg.github.com/" -devDependencies: - '@inxton/ax-sdk': "0.0.0-dev.0" -dependencies: - "abstractions-app": "0.0.0-dev.0" - "@inxton/axopen.abstractions": "0.0.0-dev.0" - "@inxton/ax.axopen.app": "0.0.0-dev.0" - "@inxton/ax.axopen.hwlibrary": "0.0.0-dev.0" - "@inxton/ax.axopen.min": "0.0.0-dev.0" - "@inxton/ax.catalog": "0.0.42" - "app_axopen.components.abb.robotics": "0.0.0-dev.0" - "@inxton/axopen.components.abb.robotics": "0.0.0-dev.0" - "components.abstractions-app": "0.0.0-dev.0" - "@inxton/axopen.components.abstractions": "0.0.0-dev.0" - "app_axopen.components.balluff.identification": "0.0.0-dev.0" - "@inxton/axopen.components.balluff.identification": "0.0.0-dev.0" - "app_axopen.components.cognex.vision": "0.0.0-dev.0" - "@inxton/axopen.components.cognex.vision": "0.0.0-dev.0" - "app_axopen.components.desoutter.tightening": "0.0.0-dev.0" - "@inxton/axopen.components.desoutter.tightening": "0.0.0-dev.0" - "app_axopen.components.drives": "0.0.0-dev.0" - "@inxton/axopen.components.drives": "0.0.0-dev.0" - "app_axopen.components.dukane.welders": "0.0.0-dev.0" - "@inxton/axopen.components.dukane.welders": "0.0.0-dev.0" - "elementscomponents": "0.0.0-dev.0" - "@inxton/axopen.components.elements": "0.0.0-dev.0" - "app_axopen.components.festo.drives": "0.0.0-dev.0" - "@inxton/axopen.components.festo.drives": "0.0.0-dev.0" - "app_axopen.components.keyence.vision": "0.0.0-dev.0" - "@inxton/axopen.components.keyence.vision": "0.0.0-dev.0" - "app_axopen.components.kuka.robotics": "0.0.0-dev.0" - "@inxton/axopen.components.kuka.robotics": "0.0.0-dev.0" - "app_axopen.components.mitsubishi.robotics": "0.0.0-dev.0" - "@inxton/axopen.components.mitsubishi.robotics": "0.0.0-dev.0" - "pneumaticcomponents": "0.0.0-dev.0" - "@inxton/axopen.components.pneumatics": "0.0.0-dev.0" - "app_axopen.components.rexroth.drives": "0.0.0-dev.0" - "@inxton/axopen.components.rexroth.drives": "0.0.0-dev.0" - "app_axopen.components.rexroth.press": "0.0.0-dev.0" - "@inxton/axopen.components.rexroth.press": "0.0.0-dev.0" - "app_axopen.components.rexroth.tightening": "0.0.0-dev.0" - "@inxton/axopen.components.rexroth.tightening": "0.0.0-dev.0" - "app_axopen.components.robotics": "0.0.0-dev.0" - "@inxton/axopen.components.robotics": "0.0.0-dev.0" - "app_axopen.components.siem.communication": "0.0.0-dev.0" - "@inxton/axopen.components.siem.communication": "0.0.0-dev.0" - "app_axopen.components.siem.identification": "0.0.0-dev.0" - "@inxton/axopen.components.siem.identification": "0.0.0-dev.0" - "app_axopen.components.ur.robotics": "0.0.0-dev.0" - "@inxton/axopen.components.ur.robotics": "0.0.0-dev.0" - "app_axopen.components.zebra.vision": "0.0.0-dev.0" - "@inxton/axopen.components.zebra.vision": "0.0.0-dev.0" - "ix_axopencore": "0.0.0-dev.0" - "@inxton/axopen.core": "0.0.0-dev.0" - "axopen.data-app": "0.0.0-dev.0" - "@inxton/axopen.data": "0.0.0-dev.0" - "axopen.data.tests_l1": "0.0.0-dev.0" - "axopen.integration.tests_l4": "0.0.0-dev.0" - "axopen.inspectors": "0.0.0-dev.0" - "@inxton/axopen.inspectors": "0.0.0-dev.0" - "axopen.integrations": "0.0.0-dev.0" - "app_axopen.io": "0.0.0-dev.0" - "@inxton/axopen.io": "0.0.0-dev.0" - "probers-app": "0.0.0-dev.0" - "@inxton/axopen.probers": "0.0.0-dev.0" - "simatic1500-app": "0.0.0-dev.0" - "@inxton/axopen.simatic1500": "0.0.0-dev.0" - "app_apaxappname": "0.0.0-dev.0" - "@inxton/apaxlibname": "0.0.0-dev.0" - "timers-app": "0.0.0-dev.0" - "@inxton/axopen.timers": "0.0.0-dev.0" - "utils-app": "0.0.0-dev.0" - "@inxton/axopen.utils": "0.0.0-dev.0" -installStrategy: "overridable" -...