diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/Layout/NewFile.txt b/src/Elektrifikatsiya/Elektrifikatsiya/Components/Layout/NewFile.txt new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Components/Layout/NewFile.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/Protected.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Components/Protected.razor new file mode 100644 index 0000000..38b04b4 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Components/Protected.razor @@ -0,0 +1,42 @@ +@using Elektrifikatsiya.Models +@inject Services.IAuthorizationService AuthorizationService + +@if (IsAuthorized) +{ + @Authorized +} +else if (!loaded && UseLoadingScreen) +{ + @Loading +} +else if (loaded) +{ + @NotAuthorized +} + +@code { + [Parameter] + public RenderFragment? Authorized { get; set; } + + [Parameter] + public RenderFragment? NotAuthorized { get; set; } + + [Parameter] + public RenderFragment? Loading { get; set; } + + [Parameter] + public Role RequiredRole { get; set; } + + [Parameter] + public bool UseLoadingScreen { get; set; } + + public bool IsAuthorized { get; set; } + private bool loaded; + + protected override async Task OnParametersSetAsync() + { + IsAuthorized = (await AuthorizationService.IsAuthorized(RequiredRole)).ValueOrDefault; + StateHasChanged(); + loaded = true; + } +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj b/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj index 4dca5d7..c19f966 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj @@ -40,10 +40,14 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor index 4962196..83982b9 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor @@ -1,18 +1,40 @@ @using System.Diagnostics; +@using FluentResults +@inject Elektrifikatsiya.Services.IAuthenticationService AuthenticationService +@inject NavigationManager NavigationManager @inherits LayoutComponentBase - + - + - + @code { - private void Clicked() + private string username = string.Empty; + private string password = string.Empty; + private Color color = Color.Primary; + private bool disabled; + private async Task Clicked() { - Debug.WriteLine("Button was clicked!"); + color = Color.Primary; + disabled = true; + + Result loginResult = await AuthenticationService.LoginUserAsync(username, password); + + if (loginResult.IsSuccess) + { + NavigationManager.NavigateTo("/", true); + } + else + { + color = Color.Danger; + disabled = false; + } } -} \ No newline at end of file +} + + \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/MainLayout.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/MainLayout.razor index e268a9f..1cc0b66 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/MainLayout.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/MainLayout.razor @@ -1,15 +1,26 @@ -@using Elektrifikatsiya.Components.Layout +@using Elektrifikatsiya.Components +@using Elektrifikatsiya.Components.Layout +@using Elektrifikatsiya.Models @inherits LayoutComponentBase - - - - - - - - - - @Body - - - +@inject NavigationManager NavigationManager + + + + + + + + + + + @Body + + + + + + @{ + NavigationManager.NavigateTo("/login"); + } + + diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.Designer.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.Designer.cs new file mode 100644 index 0000000..1b26893 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.Designer.cs @@ -0,0 +1,96 @@ +// +using System; +using Elektrifikatsiya.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elektrifikatsiya.Migrations +{ + [DbContext(typeof(MainDatabaseContext))] + [Migration("20230322092207_fix foreign keys")] + partial class fixforeignkeys + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.4"); + + modelBuilder.Entity("Elektrifikatsiya.Models.Device", b => + { + b.Property("MacAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Room") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("MacAddress"); + + b.HasIndex("UserId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("SessionToken") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.Device", b => + { + b.HasOne("Elektrifikatsiya.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.User", b => + { + b.Navigation("Devices"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.cs new file mode 100644 index 0000000..4617cb2 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elektrifikatsiya.Migrations +{ + /// + public partial class fixforeignkeys : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: false), + Role = table.Column(type: "INTEGER", nullable: false), + SessionToken = table.Column(type: "TEXT", nullable: true), + LastLoginDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Devices", + columns: table => new + { + MacAddress = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + IpAddress = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "INTEGER", nullable: false), + Room = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.MacAddress); + table.ForeignKey( + name: "FK_Devices_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Devices_UserId", + table: "Devices", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Devices"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/MainDatabaseContextModelSnapshot.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/MainDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000..01b1092 --- /dev/null +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Migrations/MainDatabaseContextModelSnapshot.cs @@ -0,0 +1,93 @@ +// +using System; +using Elektrifikatsiya.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elektrifikatsiya.Migrations +{ + [DbContext(typeof(MainDatabaseContext))] + partial class MainDatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.4"); + + modelBuilder.Entity("Elektrifikatsiya.Models.Device", b => + { + b.Property("MacAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Room") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("MacAddress"); + + b.HasIndex("UserId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("SessionToken") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.Device", b => + { + b.HasOne("Elektrifikatsiya.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Elektrifikatsiya.Models.User", b => + { + b.Navigation("Devices"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs index 4d15c15..815208e 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs @@ -19,7 +19,7 @@ public class Device public User User { get; set; } [NotMapped] - public int PowerUsage { get; set; } + public double PowerUsage { get; set; } public string Room { get; set; } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs index 9732592..2192c33 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs @@ -1,5 +1,4 @@ using System.Text.Json.Serialization; - namespace Elektrifikatsiya.Models; public enum Status @@ -24,6 +23,8 @@ public class PrometheusQueryResult public string? Error { get; set; } public List? Warnings { get; set; } + public PrometheusDataWrapper? Data { get; set; } + public PrometheusQueryResult(Status status, string? errorType, string? error, List? warnings) { Status = status; @@ -33,7 +34,7 @@ public class PrometheusQueryResult } } -internal class PrometheusDataWrapper +public class PrometheusDataWrapper { public ResultType ResultType { get; set; } public List Result { get; set; } @@ -43,9 +44,52 @@ internal class PrometheusDataWrapper ResultType = resultType; Result = result; } + + public FluentResults.Result> MatrixTypeToTimestampFloatTuple() + { + if (ResultType != ResultType.Matrix) + { + return FluentResults.Result.Fail("The response did not have the Matrix Type"); + } + + List<(double, double)> result = new List<(double, double)>(); + + if (Result[0]?.Value is null) + { + return FluentResults.Result.Fail("There was no result in the Response Body"); + } + + foreach (PrometheusData prometheusData in Result) + { + + string[] segment = prometheusData.Value.ToString()!.Split(","); + + result.Add((Convert.ToDouble(segment[0][2..^1]), Convert.ToDouble(segment[1][1..^2]))); + } + + return result; + } + + public FluentResults.Result<(double, double)> VectorTypeToTimestampFloatTuple() + { + if (ResultType != ResultType.Vector) + { + return FluentResults.Result.Fail("The response did not have the Vector Type"); + } + + string[]? segment = Result.FirstOrDefault()?.Value.ToString()?.Split(",") ?? null; + + if (segment is null) + { + return FluentResults.Result.Fail("There was no result in the Response Body"); + } + + return((Convert.ToDouble(segment[0][2..^1]), Convert.ToDouble(segment[1][1..^2]))); + } } -internal class PrometheusDataMetric + +public class PrometheusDataMetric { [JsonPropertyName("__name__")] public string Name { get; set; } @@ -60,7 +104,7 @@ internal class PrometheusDataMetric } } -internal class PrometheusData +public class PrometheusData { public PrometheusDataMetric Metric { get; set; } public object Value { get; set; } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs index 175c25d..be6b1d8 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/User.cs @@ -9,11 +9,20 @@ public class User [Key] public int Id { get; private set; } + [Required] public string Name { get; private set; } + + [Required] public string PasswordHash { get; private set; } + + [Required] + public Role Role { get; set; } + public string? SessionToken { get; set; } public DateTime LastLoginDate { get; set; } - public Role Role { get; set; } + + [Required] + public List Devices { get; set; } = new(); public User(string name, string passwordHash, Role role) { diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index a379b41..2b96602 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -2,8 +2,11 @@ @using Blazorise.Components; @using Elektrifikatsiya.Models; @using System.Net +@using Elektrifikatsiya.Services +@using Elektrifikatsiya.Utilities +@using System.Diagnostics @inject IVersionProvider VersionProvider - +@inject IDeviceStatusService DeviceStatusService
@@ -91,10 +94,20 @@ //TODO: insert new event here if plug produces one List events = new List() { new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now) }; //TODO: insert new Device here if user adds one - List plugs = new List() { new Device("ffff:ffff:ffff:ffff", "Josne", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Josne", IPAddress.Broadcast, new User("Joe", "Josne", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Josne", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), }; + List plugs = new List(); //code for graph LineChart lineChart; + protected override void OnInitialized() + { + plugs = DeviceStatusService.GetDevices().ValueOrDefault ?? new List(); + + DeviceStatusService.OnDeviceStatusChanged += (_, e) => + { + InvokeAsync(StateHasChanged); + }; + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) @@ -105,18 +118,19 @@ async Task HandleRedraw() { + await lineChart.Clear(); - await lineChart.AddLabelsDatasetsAndUpdate(Labels, GetLineChartDataset()); + await lineChart.AddLabelsDatasetsAndUpdate(Labels, await GetLineChartDataset()); } //TODO: insert dataset of current and last voltage usages - LineChartDataset GetLineChartDataset() + async Task> GetLineChartDataset() { return new LineChartDataset { Label = "# of random number in thousands", - Data = RandomizeData(), + Data = await RandomizeData(), BackgroundColor = backgroundColors, BorderColor = borderColors, Fill = true, @@ -129,17 +143,25 @@ List backgroundColors = new List { ChartColor.FromRgba(255, 99, 132, 0.2f), ChartColor.FromRgba(54, 162, 235, 0.2f), ChartColor.FromRgba(255, 206, 86, 0.2f), ChartColor.FromRgba(75, 192, 192, 0.2f), ChartColor.FromRgba(153, 102, 255, 0.2f), ChartColor.FromRgba(255, 159, 64, 0.2f) }; List borderColors = new List { ChartColor.FromRgba(255, 99, 132, 1f), ChartColor.FromRgba(54, 162, 235, 1f), ChartColor.FromRgba(255, 206, 86, 1f), ChartColor.FromRgba(75, 192, 192, 1f), ChartColor.FromRgba(153, 102, 255, 1f), ChartColor.FromRgba(255, 159, 64, 1f) }; - List RandomizeData() + async Task> RandomizeData() { + PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); + + string plugnames = ""; + foreach (Device device in plugs) + { + plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|"; + } + plugnames = plugnames[0..^1]; + + Debug.WriteLine($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""); + + PrometheusDataWrapper? deviceData = (await promQueryer.Query($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""))?.Data; + var r = new Random(DateTime.Now.Millisecond); - return new List { - r.Next( 3, 50 ) * r.NextDouble(), - r.Next( 3, 50 ) * r.NextDouble(), - r.Next( 3, 50 ) * r.NextDouble(), - r.Next( 3, 50 ) * r.NextDouble(), - r.Next( 3, 50 ) * r.NextDouble(), - r.Next( 3, 50 ) * r.NextDouble() }; + return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => x.Item2).ToList() ?? new List(); } -} +} +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index 6bf5c06..a9408b5 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -4,6 +4,7 @@ using Blazorise.Icons.Material; using Blazorise.Material; using Elektrifikatsiya.Database; +using Elektrifikatsiya.Models; using Elektrifikatsiya.Services; using Elektrifikatsiya.Services.Implementations; @@ -51,7 +52,16 @@ app.MapFallbackToPage("/_Host"); app.MapControllers(); IServiceScope serviceScope = app.Services.GetRequiredService().CreateScope(); -serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); + +MainDatabaseContext mainDatabase = serviceScope.ServiceProvider.GetRequiredService(); +mainDatabase.Database.EnsureCreated(); + +IAuthenticationService authenticationService = serviceScope.ServiceProvider.GetRequiredService(); + +if (!mainDatabase.Users.Any()) +{ + authenticationService.RegisterUserAsync("admin", "admin", Role.Admin); +} app.Run(); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json b/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json index fe359ad..a35b142 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Properties/launchSettings.json @@ -7,7 +7,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://localhost:56656;http://localhost:5255" + "applicationUrl": "https://localhost:56656;http://localhost:55552" }, "IIS Express": { "commandName": "IISExpress", diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs index b0637e1..69b3ba9 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs @@ -62,7 +62,7 @@ public class AuthenticationService : IAuthenticationService public async Task LoginUserAsync(string name, string password) { - User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name == name); if (user is null) { @@ -129,6 +129,6 @@ public class AuthenticationService : IAuthenticationService public Task> UserExistsAsync(string name) { return Result.Try(() => - mainDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))); + mainDatabaseContext.Users.AnyAsync(u => u.Name == name)); } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs index b5df353..3343c76 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -5,6 +5,7 @@ using FluentResults; using System.Net; using System.Net.NetworkInformation; +using Microsoft.EntityFrameworkCore; namespace Elektrifikatsiya.Services.Implementations; @@ -108,6 +109,8 @@ public class DeviceManagmentService : IDeviceManagmentService Device device = new Device(mac, name ?? mac, ip, user, room); + mainDatabaseContext.Entry(user).State = EntityState.Unchanged; + _ = mainDatabaseContext.Add(device); Result saveDatabaseChangesResult = await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index 4055d80..a0b7ef0 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -1,6 +1,6 @@ using Elektrifikatsiya.Database; using Elektrifikatsiya.Models; - +using Elektrifikatsiya.Utilities; using FluentResults; using Microsoft.EntityFrameworkCore; @@ -9,69 +9,75 @@ namespace Elektrifikatsiya.Services.Implementations; public class UpdateService : IHostedService, IDisposable { - private readonly ILogger logger; - private readonly IDeviceStatusService deviceStatusService; - private readonly IServiceScopeFactory serviceScopeFactory; - private Timer? timer = null; + private readonly ILogger logger; + private readonly IDeviceStatusService deviceStatusService; + private readonly IServiceScopeFactory serviceScopeFactory; + private Timer? timer = null; - public UpdateService(ILogger logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory) - { - this.logger = logger; - this.deviceStatusService = deviceStatusService; - this.serviceScopeFactory = serviceScopeFactory; - } + public UpdateService(ILogger logger, IDeviceStatusService deviceStatusService, IServiceScopeFactory serviceScopeFactory) + { + this.logger = logger; + this.deviceStatusService = deviceStatusService; + this.serviceScopeFactory = serviceScopeFactory; + } - public async Task StartAsync(CancellationToken cancellationToken) - { - IServiceScope serviceScope = serviceScopeFactory.CreateScope(); - MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); + public async Task StartAsync(CancellationToken cancellationToken) + { + IServiceScope serviceScope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); - logger.LogInformation("Starting update service..."); + logger.LogInformation("Starting update service..."); - foreach (Device device in await mainDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) - { - _ = deviceStatusService.TrackDevice(device); + foreach (Device device in await mainDatabaseContext.Devices.Include(d=>d.User).AsNoTracking().ToListAsync(cancellationToken)) + { + _ = deviceStatusService.TrackDevice(device); + } + + timer = new Timer((_) => Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + + logger.LogInformation("Update service started."); + } + + private async void Update() + { + Result> getDeviceStatusResult = deviceStatusService.GetDevices(); + + if (getDeviceStatusResult.IsFailed) + { + logger.LogError("Updating devices failed!"); + } + PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); + foreach (Device device in getDeviceStatusResult.Value) + { + PrometheusDataWrapper? deviceData = (await promQueryer.Query($"power{{sensor=\"shellyplug-s-{device.MacAddress}/relay/0\"}}"))?.Data; + + if (deviceData is not null) + { + device.PowerUsage = deviceData.VectorTypeToTimestampFloatTuple()?.ValueOrDefault.Item2 ?? 0; + deviceStatusService.UpdateDeviceStatus(device); + } } + } - timer = new Timer((_) => Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + public Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Stopping update service."); - logger.LogInformation("Update service started."); - } + _ = timer?.Change(Timeout.Infinite, 0); - private void Update() - { - Result> getDeviceStatusResult = deviceStatusService.GetDevices(); + logger.LogInformation("Stopped update service."); - if (getDeviceStatusResult.IsFailed) - { - logger.LogError("Updating devices failed!"); - } + return Task.CompletedTask; + } - foreach (Device device in getDeviceStatusResult.Value) - { - //TODO: Some update magic - } - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - public Task StopAsync(CancellationToken cancellationToken) - { - logger.LogInformation("Stopping update service."); - - _ = timer?.Change(Timeout.Infinite, 0); - - logger.LogInformation("Stopped update service."); - - return Task.CompletedTask; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - timer?.Dispose(); - } + protected virtual void Dispose(bool disposing) + { + timer?.Dispose(); + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs index 1041b79..813c50c 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs @@ -31,7 +31,7 @@ public static class MdnsDiscovery private static async Task AddDevice(ServiceAnnouncement announcement) { - IPAddress ipAddress = announcement.Addresses.First(); + IPAddress ipAddress = announcement.Addresses.First(); if (!memoryCache.TryGetValue(ipAddress.ToString(), out bool isShellyDevice)) { diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs index a635857..456521f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/PrometheusQuery.cs @@ -2,6 +2,7 @@ using System.Net.Sockets; using System.Text.Encodings.Web; using System.Text.Json; +using System.Text.Json.Serialization; using Elektrifikatsiya.Models; using FluentResults; @@ -20,6 +21,14 @@ public class PrometheusQuery public Task Query(string query) { - return client.GetFromJsonAsync($"/v1/query?{UrlEncoder.Create().Encode(query)}"); + var res = client.GetStringAsync($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}").Result; + return client.GetFromJsonAsync($"/api/v1/query?query={UrlEncoder.Create().Encode(query)}", new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = + { + new JsonStringEnumConverter() + } + }); } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/docker-compose.yml b/src/Elektrifikatsiya/docker-compose.yml index dc7c374..70891b2 100644 --- a/src/Elektrifikatsiya/docker-compose.yml +++ b/src/Elektrifikatsiya/docker-compose.yml @@ -1,10 +1,15 @@ version: '3.4' services: - elektrifikatsiya: - build: - context: . - dockerfile: Elektrifikatsiya/Dockerfile + #elektrifikatsiya: + # build: + # context: . + # dockerfile: Elektrifikatsiya/Dockerfile + # ports: + # - target: 5353 + # published: 5353 + # protocol: udp + # mode: host prometheus: image: prom/prometheus:latest volumes: