feat: add JoeAppEngine OPC UA nodes, fix DCL auto-reconnect and quality push
- Add JoeAppEngine folder to OPC UA nodes.json (BTCS, AlarmCntsBySeverity, Scheduler/ScanTime) - Fix DataConnectionActor: capture Self in PreStart for use from non-actor threads, preventing Self.Tell failure in Disconnected event handler - Implement InstanceActor.HandleConnectionQualityChanged to mark attributes Bad on disconnect - Fix LmxFakeProxy TagMapper to serialize arrays as JSON instead of "System.Int32[]" - Allow DataType and DataSourceReference updates in TemplateService.UpdateAttributeAsync - Update test_infra_opcua.md with JoeAppEngine documentation
This commit is contained in:
@@ -44,10 +44,14 @@
|
||||
<label class="form-label small">To</label>
|
||||
<input type="datetime-local" class="form-control form-control-sm" @bind="_filterTo" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-1">
|
||||
<label class="form-label small">Keyword</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_filterKeyword" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small">Instance</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_filterInstanceName" placeholder="Instance name" />
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button class="btn btn-primary btn-sm" @onclick="Search" disabled="@(string.IsNullOrEmpty(_selectedSiteId) || _searching)">
|
||||
@if (_searching) { <span class="spinner-border spinner-border-sm"></span> }
|
||||
@@ -111,6 +115,7 @@
|
||||
private DateTime? _filterFrom;
|
||||
private DateTime? _filterTo;
|
||||
private string? _filterKeyword;
|
||||
private string? _filterInstanceName;
|
||||
|
||||
private List<EventLogEntry>? _entries;
|
||||
private bool _hasMore;
|
||||
@@ -146,7 +151,7 @@
|
||||
To: _filterTo.HasValue ? new DateTimeOffset(_filterTo.Value, TimeSpan.Zero) : null,
|
||||
EventType: string.IsNullOrWhiteSpace(_filterEventType) ? null : _filterEventType.Trim(),
|
||||
Severity: string.IsNullOrWhiteSpace(_filterSeverity) ? null : _filterSeverity,
|
||||
InstanceId: null,
|
||||
InstanceId: string.IsNullOrWhiteSpace(_filterInstanceName) ? null : _filterInstanceName.Trim(),
|
||||
KeywordFilter: string.IsNullOrWhiteSpace(_filterKeyword) ? null : _filterKeyword.Trim(),
|
||||
ContinuationToken: _continuationToken,
|
||||
PageSize: 50,
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
<span class="badge bg-danger me-2">Offline</span>
|
||||
}
|
||||
<strong>@siteId</strong>
|
||||
@if (state.LatestReport?.NodeRole != null)
|
||||
{
|
||||
<span class="badge @(state.LatestReport.NodeRole == "Active" ? "bg-primary" : "bg-secondary") ms-2">@state.LatestReport.NodeRole</span>
|
||||
}
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
Last report: @state.LastReportReceivedAt.LocalDateTime.ToString("HH:mm:ss") | Seq: @state.LastSequenceNumber
|
||||
|
||||
@@ -70,9 +70,11 @@
|
||||
<td class="small"><TimestampDisplay Value="@msg.LastAttemptTimestamp" /></td>
|
||||
<td>
|
||||
<button class="btn btn-outline-success btn-sm py-0 px-1 me-1"
|
||||
title="Retry message (not yet implemented)">Retry</button>
|
||||
@onclick="() => RetryMessage(msg)" disabled="@_actionInProgress"
|
||||
title="Retry message (move back to pending)">Retry</button>
|
||||
<button class="btn btn-outline-danger btn-sm py-0 px-1"
|
||||
title="Discard message (not yet implemented)">Discard</button>
|
||||
@onclick="() => DiscardMessage(msg)" disabled="@_actionInProgress"
|
||||
title="Permanently discard message">Discard</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -102,6 +104,7 @@
|
||||
private bool _searching;
|
||||
private string? _errorMessage;
|
||||
|
||||
private bool _actionInProgress;
|
||||
private ToastNotification _toast = default!;
|
||||
private ConfirmDialog _confirmDialog = default!;
|
||||
|
||||
@@ -150,4 +153,65 @@
|
||||
}
|
||||
_searching = false;
|
||||
}
|
||||
|
||||
private async Task RetryMessage(ParkedMessageEntry msg)
|
||||
{
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var request = new ParkedMessageRetryRequest(
|
||||
CorrelationId: Guid.NewGuid().ToString("N"),
|
||||
SiteId: _selectedSiteId,
|
||||
MessageId: msg.MessageId,
|
||||
Timestamp: DateTimeOffset.UtcNow);
|
||||
var response = await CommunicationService.RetryParkedMessageAsync(_selectedSiteId, request);
|
||||
if (response.Success)
|
||||
{
|
||||
_toast.ShowSuccess($"Message {msg.MessageId[..Math.Min(12, msg.MessageId.Length)]} queued for retry.");
|
||||
await FetchPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
_toast.ShowError(response.ErrorMessage ?? "Retry failed.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_toast.ShowError($"Retry failed: {ex.Message}");
|
||||
}
|
||||
_actionInProgress = false;
|
||||
}
|
||||
|
||||
private async Task DiscardMessage(ParkedMessageEntry msg)
|
||||
{
|
||||
var confirmed = await _confirmDialog.ShowAsync(
|
||||
$"Permanently discard message {msg.MessageId[..Math.Min(12, msg.MessageId.Length)]}? This cannot be undone.",
|
||||
"Discard Parked Message");
|
||||
if (!confirmed) return;
|
||||
|
||||
_actionInProgress = true;
|
||||
try
|
||||
{
|
||||
var request = new ParkedMessageDiscardRequest(
|
||||
CorrelationId: Guid.NewGuid().ToString("N"),
|
||||
SiteId: _selectedSiteId,
|
||||
MessageId: msg.MessageId,
|
||||
Timestamp: DateTimeOffset.UtcNow);
|
||||
var response = await CommunicationService.DiscardParkedMessageAsync(_selectedSiteId, request);
|
||||
if (response.Success)
|
||||
{
|
||||
_toast.ShowSuccess($"Message {msg.MessageId[..Math.Min(12, msg.MessageId.Length)]} discarded.");
|
||||
await FetchPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
_toast.ShowError(response.ErrorMessage ?? "Discard failed.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_toast.ShowError($"Discard failed: {ex.Message}");
|
||||
}
|
||||
_actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user