diff --git a/src/App/Components/Pages/MeterDetail.razor b/src/App/Components/Pages/MeterDetail.razor index 1d00847..ec33cfa 100644 --- a/src/App/Components/Pages/MeterDetail.razor +++ b/src/App/Components/Pages/MeterDetail.razor @@ -214,20 +214,30 @@ else @(_sourceEdit.Id == 0 ? "New source" : "Edit source") - + @foreach (var type in Enum.GetValues()) { @type } - @if (_sourceEdit.SourceType is SourceType.HomeAssistant or SourceType.Mqtt or SourceType.Tasmota) + @if (RequiredEndpointType(_sourceEdit.SourceType) is { } needed) { - - @foreach (var e in _endpoints) - { - @e.Name (@e.Type) - } - + if (ConnectorsFor(needed).Count == 0) + { + + No @needed connector yet — create one + (set it up once; every source then just picks it). + + } + else + { + + @foreach (var e in ConnectorsFor(needed)) + { + @e.Name + } + + } } @if (_sourceEdit.SourceType == SourceType.HomeAssistant) { @@ -305,6 +315,7 @@ else if (source is null) { _sourceEdit = new SourceEdit(); + OnSourceTypeChanged(_sourceEdit.SourceType); } else { @@ -332,6 +343,28 @@ else private async Task SaveSourceAsync() { + // A live source without a matching connector has no connection details and would silently + // never ingest, so refuse it here rather than letting it look configured. + if (RequiredEndpointType(_sourceEdit.SourceType) is { } needed) + { + var selected = _endpoints.FirstOrDefault(e => e.Id == _sourceEdit.EndpointId); + if (selected is null) + { + Snackbar.Add($"Pick a {needed} connector for this {_sourceEdit.SourceType} source.", Severity.Error); + return; + } + + if (selected.Type != needed) + { + Snackbar.Add($"'{selected.Name}' is a {selected.Type} connector; a {_sourceEdit.SourceType} source needs {needed}.", Severity.Error); + return; + } + } + else + { + _sourceEdit.EndpointId = null; + } + var config = new SourceConfig { EntityId = Trim(_sourceEdit.EntityId), @@ -394,6 +427,44 @@ else private static string? Trim(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + /// + /// Which connector kind a source type needs, or null if it needs none (manual/import/virtual). + /// Tasmota has no endpoint kind of its own — it is served by an MQTT broker connector. + /// + private static EndpointType? RequiredEndpointType(SourceType sourceType) => sourceType switch + { + SourceType.HomeAssistant => EndpointType.HomeAssistant, + SourceType.Mqtt or SourceType.Tasmota => EndpointType.MqttBroker, + _ => null, + }; + + private List ConnectorsFor(EndpointType type) => + _endpoints.Where(e => e.Type == type).ToList(); + + // Changing the source type can invalidate the chosen connector (an HA connector cannot serve an + // MQTT source), so drop a selection that no longer fits rather than saving a mismatched pair. + private void OnSourceTypeChanged(SourceType sourceType) + { + _sourceEdit.SourceType = sourceType; + + var needed = RequiredEndpointType(sourceType); + var selected = _endpoints.FirstOrDefault(e => e.Id == _sourceEdit.EndpointId); + if (needed is null || (selected is not null && selected.Type != needed)) + { + _sourceEdit.EndpointId = null; + } + + // Sole candidate: preselect it, so the common single-broker / single-HA setup is one click. + if (needed is not null && _sourceEdit.EndpointId is null) + { + var candidates = _endpoints.Where(e => e.Type == needed).ToList(); + if (candidates.Count == 1) + { + _sourceEdit.EndpointId = candidates[0].Id; + } + } + } + private sealed class SourceEdit { public int Id { get; set; }