feat(ui): instance configure native alarm source override panel
This commit is contained in:
+196
@@ -347,6 +347,88 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Native Alarm Source Overrides *@
|
||||
<div class="card mb-3">
|
||||
<div class="card-header py-2">
|
||||
<strong>Native Alarm Source Overrides</strong>
|
||||
<small class="text-muted ms-2">
|
||||
Retarget an inherited native alarm source binding for this instance.
|
||||
Leave a field blank to keep the inherited value.
|
||||
</small>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if (_nativeSources.Count == 0)
|
||||
{
|
||||
<p class="text-muted small p-3 mb-0">No native alarm sources on this template.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-sm table-bordered mb-0 align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<th>Inherited</th>
|
||||
<th style="width: 220px;">Connection override</th>
|
||||
<th style="width: 220px;">Source reference override</th>
|
||||
<th style="width: 170px;">Filter override</th>
|
||||
<th style="width: 140px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var src in _nativeSources)
|
||||
{
|
||||
<tr>
|
||||
<td class="small">
|
||||
@src.Name
|
||||
@if (HasNativeOverride(src.Name))
|
||||
{
|
||||
<span class="badge bg-warning text-dark ms-1" title="Override is set">●</span>
|
||||
}
|
||||
</td>
|
||||
<td class="small text-muted font-monospace text-truncate" style="max-width: 200px;"
|
||||
title="@($"{src.ConnectionName} / {src.SourceReference}")">
|
||||
@src.ConnectionName / @src.SourceReference
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm"
|
||||
value="@(_nasConnEdit.GetValueOrDefault(src.Name) ?? "")"
|
||||
@onchange="e => _nasConnEdit[src.Name] = string.IsNullOrEmpty((string?)e.Value) ? null : (string?)e.Value">
|
||||
<option value="">(inherited)</option>
|
||||
@foreach (var c in AlarmCapableConnections())
|
||||
{
|
||||
<option value="@c.Name" selected="@(_nasConnEdit.GetValueOrDefault(src.Name) == c.Name)">@c.Name (@c.Protocol)</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control form-control-sm font-monospace"
|
||||
placeholder="@src.SourceReference"
|
||||
value="@(_nasRefEdit.GetValueOrDefault(src.Name) ?? "")"
|
||||
@onchange="e => _nasRefEdit[src.Name] = string.IsNullOrWhiteSpace((string?)e.Value) ? null : ((string?)e.Value)!.Trim()" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control form-control-sm"
|
||||
placeholder="@(string.IsNullOrEmpty(src.ConditionFilter) ? "(all)" : src.ConditionFilter)"
|
||||
value="@(_nasFilterEdit.GetValueOrDefault(src.Name) ?? "")"
|
||||
@onchange="e => _nasFilterEdit[src.Name] = string.IsNullOrWhiteSpace((string?)e.Value) ? null : ((string?)e.Value)!.Trim()" />
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-success btn-sm me-1"
|
||||
@onclick="() => SaveNativeOverride(src.Name)" disabled="@_saving">Save</button>
|
||||
@if (HasNativeOverride(src.Name))
|
||||
{
|
||||
<button class="btn btn-outline-danger btn-sm"
|
||||
@onclick="() => ClearNativeOverride(src.Name)" disabled="@_saving">Clear</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Area Assignment *@
|
||||
<div class="card mb-3">
|
||||
<div class="card-header py-2">
|
||||
@@ -428,6 +510,15 @@
|
||||
private List<TemplateAlarm> _overridableAlarms = new();
|
||||
private Dictionary<string, InstanceAlarmOverride> _existingAlarmOverrides = new();
|
||||
|
||||
// Native alarm source overrides — the template's source bindings plus any
|
||||
// per-instance override rows. Editing is inline (connection / source-ref /
|
||||
// filter; blank = inherited).
|
||||
private List<TemplateNativeAlarmSource> _nativeSources = new();
|
||||
private Dictionary<string, InstanceNativeAlarmSourceOverride> _existingNativeOverrides = new();
|
||||
private Dictionary<string, string?> _nasConnEdit = new();
|
||||
private Dictionary<string, string?> _nasRefEdit = new();
|
||||
private Dictionary<string, string?> _nasFilterEdit = new();
|
||||
|
||||
// Override edit modal state — non-null while the modal is open.
|
||||
private TemplateAlarm? _editingAlarm;
|
||||
private string? _editingOverrideValue; // current Value parameter for AlarmTriggerEditor
|
||||
@@ -514,6 +605,23 @@
|
||||
_existingAlarmOverrides[o.AlarmCanonicalName] = o;
|
||||
}
|
||||
|
||||
// Native alarm source bindings + per-instance overrides. Seed the
|
||||
// inline edit maps from existing override rows (blank = inherited).
|
||||
_nativeSources = (await TemplateEngineRepository.GetNativeAlarmSourcesByTemplateIdAsync(_instance.TemplateId)).ToList();
|
||||
_existingNativeOverrides = new();
|
||||
var nativeOverrides = await TemplateEngineRepository.GetNativeAlarmSourceOverridesByInstanceIdAsync(Id);
|
||||
foreach (var o in nativeOverrides)
|
||||
{
|
||||
_existingNativeOverrides[o.SourceCanonicalName] = o;
|
||||
}
|
||||
foreach (var s in _nativeSources)
|
||||
{
|
||||
var ovr = _existingNativeOverrides.GetValueOrDefault(s.Name);
|
||||
_nasConnEdit[s.Name] = ovr?.ConnectionNameOverride;
|
||||
_nasRefEdit[s.Name] = ovr?.SourceReferenceOverride;
|
||||
_nasFilterEdit[s.Name] = ovr?.ConditionFilterOverride;
|
||||
}
|
||||
|
||||
_flattenedAttributes = await BuildFlattenedAttributesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -892,6 +1000,94 @@
|
||||
_saving = false;
|
||||
}
|
||||
|
||||
// ── Native alarm source overrides (repository-direct; blank field = inherited) ──
|
||||
|
||||
private bool HasNativeOverride(string sourceName) => _existingNativeOverrides.ContainsKey(sourceName);
|
||||
|
||||
private IEnumerable<DataConnection> AlarmCapableConnections() =>
|
||||
_siteConnections.Where(c => string.Equals(c.Protocol, "OpcUa", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(c.Protocol, "MxGateway", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private async Task SaveNativeOverride(string sourceName)
|
||||
{
|
||||
_saving = true;
|
||||
try
|
||||
{
|
||||
var conn = Blank(_nasConnEdit.GetValueOrDefault(sourceName));
|
||||
var sref = Blank(_nasRefEdit.GetValueOrDefault(sourceName));
|
||||
var filt = Blank(_nasFilterEdit.GetValueOrDefault(sourceName));
|
||||
|
||||
// All blank → no override; clear any existing row.
|
||||
if (conn == null && sref == null && filt == null)
|
||||
{
|
||||
await ClearNativeOverrideCore(sourceName);
|
||||
_toast.ShowSuccess($"No override on '{sourceName}' (inherited).");
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = await TemplateEngineRepository.GetNativeAlarmSourceOverrideAsync(Id, sourceName);
|
||||
if (existing == null)
|
||||
{
|
||||
var ovr = new InstanceNativeAlarmSourceOverride(sourceName)
|
||||
{
|
||||
InstanceId = Id,
|
||||
ConnectionNameOverride = conn,
|
||||
SourceReferenceOverride = sref,
|
||||
ConditionFilterOverride = filt
|
||||
};
|
||||
await TemplateEngineRepository.AddInstanceNativeAlarmSourceOverrideAsync(ovr);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
_existingNativeOverrides[sourceName] = ovr;
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.ConnectionNameOverride = conn;
|
||||
existing.SourceReferenceOverride = sref;
|
||||
existing.ConditionFilterOverride = filt;
|
||||
await TemplateEngineRepository.UpdateInstanceNativeAlarmSourceOverrideAsync(existing);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
_existingNativeOverrides[sourceName] = existing;
|
||||
}
|
||||
_toast.ShowSuccess($"Saved native alarm source override on '{sourceName}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_toast.ShowError($"Save failed: {ex.Message}");
|
||||
}
|
||||
_saving = false;
|
||||
}
|
||||
|
||||
private async Task ClearNativeOverride(string sourceName)
|
||||
{
|
||||
_saving = true;
|
||||
try
|
||||
{
|
||||
await ClearNativeOverrideCore(sourceName);
|
||||
_toast.ShowSuccess($"Cleared override on '{sourceName}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_toast.ShowError($"Clear failed: {ex.Message}");
|
||||
}
|
||||
_saving = false;
|
||||
}
|
||||
|
||||
private async Task ClearNativeOverrideCore(string sourceName)
|
||||
{
|
||||
var existing = await TemplateEngineRepository.GetNativeAlarmSourceOverrideAsync(Id, sourceName);
|
||||
if (existing != null)
|
||||
{
|
||||
await TemplateEngineRepository.DeleteInstanceNativeAlarmSourceOverrideAsync(existing.Id);
|
||||
await TemplateEngineRepository.SaveChangesAsync();
|
||||
}
|
||||
_existingNativeOverrides.Remove(sourceName);
|
||||
_nasConnEdit[sourceName] = null;
|
||||
_nasRefEdit[sourceName] = null;
|
||||
_nasFilterEdit[sourceName] = null;
|
||||
}
|
||||
|
||||
private static string? Blank(string? v) => string.IsNullOrWhiteSpace(v) ? null : v.Trim();
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors TemplateEdit.MapDataType — converts the persisted DataType enum
|
||||
/// to the canonical SCADA type string the AlarmTriggerEditor compares
|
||||
|
||||
Reference in New Issue
Block a user