feat(alarms): HiLo trigger type with per-band level, hysteresis, messages, overrides

Adds a new HiLo alarm trigger type with four configurable setpoints
(LoLo / Lo / Hi / HiHi). Each setpoint carries an optional priority,
deadband (for hysteresis), and operator message. The site runtime emits
AlarmStateChanged with an AlarmLevel field so consumers can differentiate
warning vs critical bands.

Plumbing:
  - new AlarmLevel enum + AlarmStateChanged.Level/Message init properties
  - AlarmTriggerEditor (Blazor) gets a HiLo render with severity tinting
  - AlarmTriggerConfigCodec extracted from the editor for testability
  - sitestream.proto carries level + message over gRPC
  - SemanticValidator enforces numeric attribute, setpoint ordering,
    non-negative deadband
  - on-trigger scripts get an Alarm global (Name/Level/Priority/Message)
    so notification routing can branch by severity
  - per-instance InstanceAlarmOverride entity + EF migration + flattening
    step + CLI commands; HiLo overrides merge setpoint-by-setpoint, binary
    types whole-replace
  - DebugView shows a Level badge + per-band message tooltip
  - App.razor auto-reloads on permanent Blazor circuit failure
  - docker/regen-proto.sh automates the proto regen workflow (the linux/arm64
    protoc segfault means generated files are checked in for now)
This commit is contained in:
Joseph Doherty
2026-05-13 03:23:32 -04:00
parent 783da8e21a
commit 751248feb6
46 changed files with 4693 additions and 204 deletions

View File

@@ -26,17 +26,54 @@
</div>
</div>
<script src="/_framework/blazor.web.js"></script>
<script src="/_framework/blazor.web.js"
autostart="false"></script>
<script>
// Reconnection overlay for failover behavior
// Blazor object is available after blazor.web.js initializes
// Reconnection overlay for failover behavior. After a docker redeploy
// (or other server-side restart), Blazor exhausts its retry budget and
// leaves the user staring at a stuck "Reconnect failed" overlay. Auto-
// reload in that case so the user lands on a fresh circuit instead of
// having to manually refresh.
Blazor.start({
circuit: {
reconnectionOptions: {
maxRetries: 8,
retryIntervalMilliseconds: 1500
},
reconnectionHandler: {
onConnectionDown: () => { /* default overlay */ },
onConnectionUp: () => {
var m = document.getElementById('reconnect-modal');
if (m) m.style.display = 'none';
}
}
}
});
document.addEventListener('DOMContentLoaded', () => {
if (typeof Blazor !== 'undefined') {
Blazor.addEventListener('enhancedload', () => {
document.getElementById('reconnect-modal').style.display = 'none';
Blazor.addEventListener?.('enhancedload', () => {
var m = document.getElementById('reconnect-modal');
if (m) m.style.display = 'none';
});
}
});
// When Blazor gives up reconnecting, it adds the
// `components-reconnect-failed` class to the reconnect modal element.
// Watch for it and auto-reload so the user gets a fresh circuit.
var mo = new MutationObserver(() => {
var m = document.getElementById('reconnect-modal');
if (!m) return;
if (m.classList.contains('components-reconnect-failed')) {
window.location.reload();
}
});
mo.observe(document.documentElement, {
attributes: true,
subtree: true,
attributeFilter: ['class']
});
</script>
<script src="/js/treeview-storage.js"></script>
<script src="_content/ScadaLink.CentralUI/js/monaco-init.js"></script>