diff --git a/PkSoftwareService.Custom.Backend/InMemorySink.cs b/PkSoftwareService.Custom.Backend/InMemorySink.cs new file mode 100644 index 000000000..a59c894f5 --- /dev/null +++ b/PkSoftwareService.Custom.Backend/InMemorySink.cs @@ -0,0 +1,66 @@ +using Serilog.Core; +using Serilog.Events; +using Serilog.Formatting.Display; + +namespace PkSoftwareService.Custom.Backend; + +public class InMemorySink : ILogEventSink +{ + private readonly int _capacity; + private readonly Queue _logMessages; + private readonly object _syncRoot = new object(); + private readonly MessageTemplateTextFormatter _formatter; + + /// + /// Creates a new InMemorySink. + /// + /// The output template (should match your Console sink). + /// Optional format provider. + /// Max number of messages to store. + public InMemorySink(string outputTemplate, IFormatProvider? formatProvider = null, int capacity = 20000) + { + _capacity = capacity; + _logMessages = new Queue(capacity); + _formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); + } + + public void Emit(LogEvent logEvent) + { + // Format the log event into a string. + using var writer = new StringWriter(); + _formatter.Format(logEvent, writer); + var message = writer.ToString(); + + // Ensure thread safety while enqueuing/dequeuing. + lock (_syncRoot) + { + if (_logMessages.Count >= _capacity) + { + _logMessages.Dequeue(); // remove oldest + } + _logMessages.Enqueue(message); + } + } + + /// + /// Returns a snapshot of the current log messages. + /// + public List GetLogs() + { + lock (_syncRoot) + { + return _logMessages.Select(x => x.Trim()).ToList(); + } + } + + /// + /// Optionally clear all logs. + /// + public void Clear() + { + lock (_syncRoot) + { + _logMessages.Clear(); + } + } +} diff --git a/PkSoftwareService.Custom.Backend/PkSoftwareService.Custom.Backend.csproj b/PkSoftwareService.Custom.Backend/PkSoftwareService.Custom.Backend.csproj new file mode 100644 index 000000000..6e5fff9b8 --- /dev/null +++ b/PkSoftwareService.Custom.Backend/PkSoftwareService.Custom.Backend.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/TeslaSolarCharger.sln b/TeslaSolarCharger.sln index ada531858..a41ae7550 100644 --- a/TeslaSolarCharger.sln +++ b/TeslaSolarCharger.sln @@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeslaSolarCharger.SharedMod EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeslaSolarCharger.Services", "TeslaSolarCharger.Services\TeslaSolarCharger.Services.csproj", "{21A8DB64-E449-474E-94DD-360C30D1756A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkSoftwareService.Custom.Backend", "PkSoftwareService.Custom.Backend\PkSoftwareService.Custom.Backend.csproj", "{B9331EFC-2B31-4447-B6C3-3E898B560946}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -87,6 +89,10 @@ Global {21A8DB64-E449-474E-94DD-360C30D1756A}.Debug|Any CPU.Build.0 = Debug|Any CPU {21A8DB64-E449-474E-94DD-360C30D1756A}.Release|Any CPU.ActiveCfg = Release|Any CPU {21A8DB64-E449-474E-94DD-360C30D1756A}.Release|Any CPU.Build.0 = Release|Any CPU + {B9331EFC-2B31-4447-B6C3-3E898B560946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9331EFC-2B31-4447-B6C3-3E898B560946}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9331EFC-2B31-4447-B6C3-3E898B560946}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9331EFC-2B31-4447-B6C3-3E898B560946}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 2f4573b6a..f18168e25 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -51,13 +51,21 @@ else + LabelText="TeslaMate Database Server Port" + UnitText="" + HelpText="You can use the internal port of the TeslaMate database container"> + + + + + Car Settings @@ -68,8 +70,8 @@ else @if (carBasicConfiguration.Item.UseFleetTelemetry && !carBasicConfiguration.Item.IncludeTrackingRelevantFields) { + NoIcon="true" + ContentAlignment="HorizontalAlignment.Left">
Home Address of Tesla is used to determine if car is at home
As tracking relevant fields are not included, the home address set in the car is used to determine if the car is at home. This means the Home Geofence set in Base Configuration is not used for this car.
@@ -77,13 +79,27 @@ else @if (carBasicConfiguration.Item.UseFleetTelemetry) { + For="() => carBasicConfiguration.Item.IncludeTrackingRelevantFields" + OnValueChanged="Redraw" /> } } + @if (carBasicConfiguration.Item.ShouldBeManaged && carBasicConfiguration.Item.UseFleetTelemetry) + { +

Fleet Telemetry Config

+ @if (_fleetTelemetryConfigs.TryGetValue(carBasicConfiguration.Item.Vin, out var fleetTelemetryConfig)) + { +
+
Current Fleet Telemetry Config
+
@fleetTelemetryConfig
+
+ } + + } + @if (_vinsToShowBleTest.Contains(carBasicConfiguration.Item.Vin)) {
@@ -98,7 +114,6 @@ else

@result

- } @@ -152,6 +167,7 @@ else private Dictionary _pairingResults = new(); private Dictionary _bleTestResults = new(); private Dictionary _bleWakeUpTestResults = new(); + private Dictionary _fleetTelemetryConfigs = new(); private HashSet _loadingVins = new(); @@ -252,4 +268,19 @@ else } } + private async Task GetFleetTelemetryConfig(string vin) + { + var result = await HttpClientHelper.SendGetRequestAsync>($"api/Config/GetFleetTelemetryConfiguration?vin={Uri.EscapeDataString(vin)}"); + string stringToDisplay; + if (result.HasError) + { + stringToDisplay = result.ErrorMessage ?? "No error message"; + } + else + { + stringToDisplay = result.Data?.Value ?? "No data"; + } + _fleetTelemetryConfigs[vin] = stringToDisplay; + } + } \ No newline at end of file diff --git a/TeslaSolarCharger/Client/Pages/Support.razor b/TeslaSolarCharger/Client/Pages/Support.razor new file mode 100644 index 000000000..5f8cdfe92 --- /dev/null +++ b/TeslaSolarCharger/Client/Pages/Support.razor @@ -0,0 +1,142 @@ +@page "/support" +@using TeslaSolarCharger.Client.Helper.Contracts +@using TeslaSolarCharger.Shared.Dtos +@using TeslaSolarCharger.Shared.Dtos.Support + +@inject IHttpClientHelper HttpClientHelper +@inject ISnackbar Snackbar +@inject NavigationManager NavigationManager + +

Support

+ + +
Never share logs publicly
+ Logs might contain sensitive information like your vehicle's location. Do not share logs publicly. +
+

General

+ + + +

Car Debug Details

+@if (_debugCars == default) +{ + +} +else +{ + + @foreach (var car in _debugCars) + { + +
ID: @car.Key
+
VIN: @car.Value.Vin
+
Name: @car.Value.Name
+
Is Available in Tesla account: @car.Value.IsAvailableInTeslaAccount
+
Should be managed: @car.Value.ShouldBeManaged
+ + + @if (car.Value.Vin != default && _fleetTelemetryGetConfigs.TryGetValue(car.Value.Vin, out var config)) + { +

Fleet Telemetry Config

+
@config
+ } + + + + @if (car.Value.Vin != default && _fleetTelemetrySetResults.TryGetValue(car.Value.Vin, out var result)) + { +

Fleet Telemetry SetResult

+
@result
+ } + + + +
+ } +
+} + + + +@code { + private readonly Dictionary _fleetTelemetryGetConfigs = new(); + + private readonly Dictionary _fleetTelemetrySetResults = new(); + + private Dictionary? _debugCars; + + private bool _isFleetTelemetryLoading; + + + protected override async Task OnInitializedAsync() + { + var cars = await HttpClientHelper.SendGetRequestWithSnackbarAsync>("api/Debug/GetCars"); + if (cars != default) + { + _debugCars = cars; + } + } + + + private async Task GetFleetTelemetryConfig(string? vin) + { + if (vin == default) + { + Snackbar.Add("VIN is unknown", Severity.Error); + return; + } + + _isFleetTelemetryLoading = true; + var result = await HttpClientHelper.SendGetRequestAsync>($"api/Debug/GetFleetTelemetryConfiguration?vin={Uri.EscapeDataString(vin)}"); + string stringToDisplay; + if (result.HasError) + { + stringToDisplay = result.ErrorMessage ?? "No error message"; + } + else + { + stringToDisplay = result.Data?.Value ?? "No data"; + } + _fleetTelemetryGetConfigs[vin] = stringToDisplay; + _isFleetTelemetryLoading = false; + } + + private async Task SetFleetTelemetryConfig(string? vin, bool forceReconfiguration) + { + if (vin == default) + { + Snackbar.Add("VIN is unknown", Severity.Error); + return; + } + + _isFleetTelemetryLoading = true; + var result = await HttpClientHelper.SendPostRequestAsync>($"api/Debug/SetFleetTelemetryConfiguration?vin={Uri.EscapeDataString(vin)}&forceReconfiguration={forceReconfiguration}", null); + string stringToDisplay; + if (result.HasError) + { + stringToDisplay = result.ErrorMessage ?? "No error message"; + } + else + { + stringToDisplay = result.Data?.Value ?? "No data"; + } + _fleetTelemetrySetResults[vin] = stringToDisplay; + _isFleetTelemetryLoading = false; + } +} diff --git a/TeslaSolarCharger/Client/Shared/NavMenu.razor b/TeslaSolarCharger/Client/Shared/NavMenu.razor index fc02cbdc9..1b0937a23 100644 --- a/TeslaSolarCharger/Client/Shared/NavMenu.razor +++ b/TeslaSolarCharger/Client/Shared/NavMenu.razor @@ -36,6 +36,11 @@ Base Configuration +