From 58ad7ac58fafe08af2ed9c6387f99de95dda64db Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:07:55 +0100 Subject: [PATCH 01/12] =?UTF-8?q?Beep=20boop=20=F0=9F=A4=96=F0=9F=A4=96?= =?UTF-8?q?=F0=9F=A4=96=F0=9F=A4=96=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor | 3 --- .../Elektrifikatsiya/Pages/Dashboard.razor | 8 ++++---- .../Elektrifikatsiya/Pages/Settings.razor | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index 41cb0fb..f14522f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -34,9 +34,6 @@ - - @context.Item - diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index 049c64e..a379b41 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -33,7 +33,7 @@ - User: @context.Item.User + User: @context.Item.User.Name
Room: @context.Item.Room
@@ -52,14 +52,14 @@
- + - Plugs + Logs 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", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), new Device("ffff:ffff:ffff:ffff", "Huso", IPAddress.Broadcast, new User("Joe", "qiowruioewjfopa", Role.Admin), "Raum"), }; + 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"), }; //code for graph LineChart lineChart; diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor index 415044c..336dd20 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor @@ -1,6 +1,7 @@ @page "/Settings" @using Blazorise.Components; @using Elektrifikatsiya.Models; +@using System.Net plugs = new List() { new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF") }; + 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"), }; } From 70c9d54623fdf5e2918b913e4be8023e57747cf4 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:34:42 +0100 Subject: [PATCH 02/12] Ignore sqlite files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index dfcfd56..883ab1e 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# Project specific +*.sqlite* From 40b9d9aa614456f39e7ea4c0be18280f709d9543 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 15 Mar 2023 10:35:15 +0100 Subject: [PATCH 03/12] Improve device discovery service and add page --- .../DeviceManagementDatabaseContext.cs | 14 -- ...abaseContext.cs => MainDatabaseContext.cs} | 5 +- .../Elektrifikatsiya/Pages/AddPlug.razor | 226 +++++++++++------- .../Elektrifikatsiya/Program.cs | 23 +- .../Implementations/AuthenticationService.cs | 26 +- .../Implementations/DeviceManagmentService.cs | 224 ++++++++--------- .../Services/Implementations/UpdateService.cs | 17 +- .../Utilities/MdnsDiscovery.cs | 55 ++++- 8 files changed, 334 insertions(+), 256 deletions(-) delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs rename src/Elektrifikatsiya/Elektrifikatsiya/Database/{UserDatabaseContext.cs => MainDatabaseContext.cs} (58%) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs deleted file mode 100644 index 85d2699..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Database/DeviceManagementDatabaseContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elektrifikatsiya.Models; -using Microsoft.EntityFrameworkCore; - -namespace Elektrifikatsiya.Database -{ - public class DeviceManagmentDatabaseContext : DbContext - { - public DbSet Devices { get; set; } - - public DeviceManagmentDatabaseContext(DbContextOptions options) : base(options) - { - } - } -} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs similarity index 58% rename from src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs rename to src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs index b3a6730..d58b2b1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Database/UserDatabaseContext.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs @@ -4,11 +4,12 @@ using Microsoft.EntityFrameworkCore; namespace Elektrifikatsiya.Database; -public class UserDatabaseContext : DbContext +public class MainDatabaseContext : DbContext { - public UserDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) + public MainDatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions) { } public DbSet Users { get; set; } + public DbSet Devices { get; set; } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index f14522f..e3c034f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -5,98 +5,150 @@ @using Elektrifikatsiya.Services @using Elektrifikatsiya.Services.Implementations @using Elektrifikatsiya.Utilities +@using FluentResults; @inject IVersionProvider VersionProvider @inject IDeviceManagmentService DeviceManagmentService -@inject IAuthenticationService AuthenticationService +@inject IAuthenticationService AuthenticationService +@inject IMessageService MessageService -
- - - - - - - Add a plug via wifi - - - - - - - - - - - - IP: @context.Item - - - - - - - - - - - - - - - - - - - - - Add a plug via IP - - - - -
- - - -
- -
-
-
-
-
-
- -
-
+
+ + + + + + + Add a plug via wifi + + + + + + + + + + + + IP: @context.Item + + + + + + + + + + + + + + + + + + + + + Add a plug via IP + + + + +
+ + + + + + Please enter a ip address. + Ip address is valid. + Ip address is not valid! + + +
+ +
+
+
+
+
+
+
+ +
+
@code { - List ipAddresses = new List(); + List ipAddresses = new List(); - protected override void OnInitialized() - { - ipAddresses.AddRange(MdnsDiscovery.GetChachedDevices()); - MdnsDiscovery.OnDeviceFound += address => - { - ipAddresses.Add(address); - InvokeAsync(StateHasChanged); - }; - } + Validation? ipValidation; + + string ipText = null!; + + protected override void OnInitialized() + { + MdnsDiscovery.OnDeviceFound += async ipAddress => + { + if (!ipAddresses.Contains(ipAddress)) + { + ipAddresses.Add(ipAddress); + await InvokeAsync(StateHasChanged); + } + }; + + MdnsDiscovery.FetchChachedDevices(); + } + + private async Task RegisterDevice(IPAddress ipAddress) + { + Result result = await DeviceManagmentService.RegisterDevice(ipAddress, (await AuthenticationService.GetUserAsync()).ValueOrDefault); + + if (result.IsSuccess) + { + await MessageService.Success("Device added!"); + } + else + { + await MessageService.Error(string.Join(',', result.Errors.Select(e => e.Message)), "Adding device failed!"); + } + } + + public void ValidateIPv4(ValidatorEventArgs e) + { + string? input = Convert.ToString(e.Value); + + if (string.IsNullOrWhiteSpace(input)) + { + e.Status = ValidationStatus.None; + return; + } + + string[] splitValues = input.Split('.'); + if (splitValues.Length != 4) + { + e.Status = ValidationStatus.Error; + return; + } + + e.Status = splitValues.All(r => byte.TryParse(r, out _)) ? ValidationStatus.Success : ValidationStatus.Error; + } } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index 3c21487..6bf5c06 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -4,7 +4,6 @@ using Blazorise.Icons.Material; using Blazorise.Material; using Elektrifikatsiya.Database; -using Elektrifikatsiya.Models; using Elektrifikatsiya.Services; using Elektrifikatsiya.Services.Implementations; @@ -22,13 +21,12 @@ builder.Services.AddTransient() builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddDbContext(options => options.UseSqlite("Data Source=./UserDatabase.sqlite")); -builder.Services.AddDbContext((options) => options.UseSqlite("Data Source=./DeviceManagement.sqlite")); +builder.Services.AddDbContext(options => options.UseSqlite("Data Source=./MainDatabase.sqlite")); builder.Services.AddBootstrapProviders(); builder.Services.AddHttpClient(); builder.Services.AddBlazorise(options => { - options.Immediate = true; + options.Immediate = true; }); AddBlazorise(builder.Services); @@ -38,9 +36,9 @@ WebApplication app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { - _ = app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - _ = app.UseHsts(); + _ = app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + _ = app.UseHsts(); } app.UseHttpsRedirection(); @@ -53,16 +51,13 @@ app.MapFallbackToPage("/_Host"); app.MapControllers(); IServiceScope serviceScope = app.Services.GetRequiredService().CreateScope(); -serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); - -AsyncServiceScope scope = app.Services.CreateAsyncScope(); -scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); +serviceScope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); app.Run(); void AddBlazorise(IServiceCollection services) { - _ = services.AddBlazorise(); - _ = services.AddMaterialProviders(); - _ = services.AddMaterialIcons(); + _ = services.AddBlazorise(); + _ = services.AddMaterialProviders(); + _ = services.AddMaterialIcons(); } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs index 36ce68e..b0637e1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/AuthenticationService.cs @@ -12,13 +12,13 @@ namespace Elektrifikatsiya.Services.Implementations; public class AuthenticationService : IAuthenticationService { - private readonly UserDatabaseContext userDatabaseContext; + private readonly MainDatabaseContext mainDatabaseContext; private readonly IHttpContextAccessor httpContextAccessor; private readonly ICookieService cookieService; - public AuthenticationService(UserDatabaseContext userDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService) + public AuthenticationService(MainDatabaseContext mainDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService) { - this.userDatabaseContext = userDatabaseContext; + this.mainDatabaseContext = mainDatabaseContext; this.httpContextAccessor = httpContextAccessor; this.cookieService = cookieService; } @@ -32,8 +32,8 @@ public class AuthenticationService : IAuthenticationService return getUserResult.ToResult(); } - _ = userDatabaseContext.Users.Remove(getUserResult.Value); - return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult(); + _ = mainDatabaseContext.Users.Remove(getUserResult.Value); + return (await Result.Try(() => mainDatabaseContext.SaveChangesAsync())).ToResult(); } public async Task> GetUserAsync() @@ -45,7 +45,7 @@ public class AuthenticationService : IAuthenticationService return Result.Fail("Token is not valid!"); } - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); if (user is null || DateTime.UtcNow - user.LastLoginDate > TimeSpan.FromDays(7)) { @@ -62,7 +62,7 @@ public class AuthenticationService : IAuthenticationService public async Task LoginUserAsync(string name, string password) { - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (user is null) { @@ -79,7 +79,7 @@ public class AuthenticationService : IAuthenticationService user.LastLoginDate = DateTime.UtcNow; user.SessionToken = newToken; - _ = await userDatabaseContext.SaveChangesAsync(); + _ = await mainDatabaseContext.SaveChangesAsync(); await cookieService.WriteCookieAsync("token", newToken, 7); @@ -97,7 +97,7 @@ public class AuthenticationService : IAuthenticationService return Result.Ok(); } - User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); + User? user = await mainDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token); if (user is null) { @@ -106,7 +106,7 @@ public class AuthenticationService : IAuthenticationService user.SessionToken = null; - _ = await userDatabaseContext.SaveChangesAsync(); + _ = await mainDatabaseContext.SaveChangesAsync(); return Result.Ok(); } @@ -121,14 +121,14 @@ public class AuthenticationService : IAuthenticationService } User user = new User(name, BC.HashPassword(password), role); - _ = userDatabaseContext.Users.Add(user); + _ = mainDatabaseContext.Users.Add(user); - return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult(); + return (await Result.Try(() => mainDatabaseContext.SaveChangesAsync())).ToResult(); } public Task> UserExistsAsync(string name) { return Result.Try(() => - userDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))); + mainDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))); } } \ 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 34916e0..b5df353 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -10,153 +10,153 @@ namespace Elektrifikatsiya.Services.Implementations; public class DeviceManagmentService : IDeviceManagmentService { - private readonly IServiceScopeFactory serviceScopeFactory; - private readonly IDeviceStatusService deviceStatusService; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly IServiceScopeFactory serviceScopeFactory; + private readonly IDeviceStatusService deviceStatusService; + private readonly ILogger logger; + private readonly HttpClient httpClient; - public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, ILogger logger, HttpClient httpClient) - { - this.serviceScopeFactory = serviceScopeFactory; - this.deviceStatusService = deviceStatusService; - this.logger = logger; - this.httpClient = httpClient; - } + public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, ILogger logger, HttpClient httpClient) + { + this.serviceScopeFactory = serviceScopeFactory; + this.deviceStatusService = deviceStatusService; + this.logger = logger; + this.httpClient = httpClient; + } - public Result GetDevice(string macAdress) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result GetDevice(string macAdress) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress); + Device? device = getDevicesResult.Value.FirstOrDefault(d => d.MacAddress == macAdress); - if (device is null) - { - return Result.Fail("Device does not exist!"); - } + if (device is null) + { + return Result.Fail("Device does not exist!"); + } - return device; - } + return device; + } - public Result> GetDevices() - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevices() + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.ToList(); - } + return getDevicesResult.Value.ToList(); + } - public Result> GetDevicesInRoom(string room) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevicesInRoom(string room) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.Where(d => d.Room == room).ToList(); - } + return getDevicesResult.Value.Where(d => d.Room == room).ToList(); + } - public Result> GetDevicesOfUser(int userId) - { - Result> getDevicesResult = deviceStatusService.GetDevices(); + public Result> GetDevicesOfUser(int userId) + { + Result> getDevicesResult = deviceStatusService.GetDevices(); - if (getDevicesResult.IsFailed) - { - return getDevicesResult.ToResult(); - } + if (getDevicesResult.IsFailed) + { + return getDevicesResult.ToResult(); + } - return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList(); - } + return getDevicesResult.Value.Where(d => d.User.Id == userId).ToList(); + } - public async Task> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default") - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task> RegisterDevice(IPAddress ip, User user, string? name = null, string room = "default") + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - ShellyResponse? shellyResponse = null; + ShellyResponse? shellyResponse = null; - try - { - shellyResponse = await httpClient.GetFromJsonAsync($"http://{ip}/shelly"); - } - catch (Exception ex) - { - logger.LogError("HTTP request failed! {message}", ex.Message); - } + try + { + shellyResponse = await httpClient.GetFromJsonAsync($"http://{ip}/shelly"); + } + catch (Exception ex) + { + logger.LogError("HTTP request failed! {message}", ex.Message); + } - if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") - { - return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!"); - } + if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") + { + return Result.Fail("Device is not reachable or not a \"SHPLG-S\"!"); + } - string mac = shellyResponse.Mac; + string mac = shellyResponse.Mac; - if (!PhysicalAddress.TryParse(mac, out _)) - { - return Result.Fail("Invalid mac address!"); - } + if (!PhysicalAddress.TryParse(mac, out _)) + { + return Result.Fail("Invalid mac address!"); + } - Device device = new Device(mac, name ?? mac, ip, user, room); + Device device = new Device(mac, name ?? mac, ip, user, room); - _ = deviceManagmentDatabaseContext.Add(device); - Result saveDatabaseChangesResult = await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); + _ = mainDatabaseContext.Add(device); + Result saveDatabaseChangesResult = await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); - if (saveDatabaseChangesResult.IsFailed) - { - return saveDatabaseChangesResult; - } + if (saveDatabaseChangesResult.IsFailed) + { + return saveDatabaseChangesResult; + } - return deviceStatusService.TrackDevice(device); - } + return deviceStatusService.TrackDevice(device); + } - public async Task UnregisterDevice(string macAdress) - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task UnregisterDevice(string macAdress) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - Result getDeviceResult = GetDevice(macAdress); + Result getDeviceResult = GetDevice(macAdress); - if (getDeviceResult.IsFailed) - { - return getDeviceResult.ToResult(); - } + if (getDeviceResult.IsFailed) + { + return getDeviceResult.ToResult(); + } - Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); + Result untrackDeviceResult = deviceStatusService.UntrackDevice(macAdress); - if (untrackDeviceResult.IsFailed) - { - return untrackDeviceResult; - } + if (untrackDeviceResult.IsFailed) + { + return untrackDeviceResult; + } - _ = deviceManagmentDatabaseContext.Remove(getDeviceResult.Value); + _ = mainDatabaseContext.Remove(getDeviceResult.Value); - return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); - } + return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); + } - public async Task UpdateDevice(Device device) - { - using IServiceScope scope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = scope.ServiceProvider.GetRequiredService(); + public async Task UpdateDevice(Device device) + { + using IServiceScope scope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - Result result = deviceStatusService.UpdateDeviceStatus(device); + Result result = deviceStatusService.UpdateDeviceStatus(device); - if (result.IsFailed) - { - return result; - } + if (result.IsFailed) + { + return result; + } - _ = deviceManagmentDatabaseContext.Update(device); + _ = mainDatabaseContext.Update(device); - return await Result.Try(Task () => deviceManagmentDatabaseContext.SaveChangesAsync()); - } + return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index da74f9f..4055d80 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -1,8 +1,9 @@ using Elektrifikatsiya.Database; using Elektrifikatsiya.Models; + using FluentResults; + using Microsoft.EntityFrameworkCore; -using System.Threading.Tasks; namespace Elektrifikatsiya.Services.Implementations; @@ -22,22 +23,22 @@ public class UpdateService : IHostedService, IDisposable public async Task StartAsync(CancellationToken cancellationToken) { - IServiceScope serviceScope = serviceScopeFactory.CreateScope(); - DeviceManagmentDatabaseContext deviceManagmentDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); + IServiceScope serviceScope = serviceScopeFactory.CreateScope(); + MainDatabaseContext mainDatabaseContext = serviceScope.ServiceProvider.GetRequiredService(); - logger.LogInformation("Starting update service..."); + logger.LogInformation("Starting update service..."); - foreach (Device device in await deviceManagmentDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) + foreach (Device device in await mainDatabaseContext.Devices.AsNoTracking().ToListAsync(cancellationToken)) { _ = deviceStatusService.TrackDevice(device); } - timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + timer = new Timer((_) => Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); logger.LogInformation("Update service started."); } - private async Task Update() + private void Update() { Result> getDeviceStatusResult = deviceStatusService.GetDevices(); @@ -46,7 +47,7 @@ public class UpdateService : IHostedService, IDisposable logger.LogError("Updating devices failed!"); } - foreach(Device device in getDeviceStatusResult.Value) + foreach (Device device in getDeviceStatusResult.Value) { //TODO: Some update magic } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs index 55a8943..1041b79 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Utilities/MdnsDiscovery.cs @@ -1,4 +1,8 @@ -using System.Net; +using Elektrifikatsiya.Models; + +using Microsoft.Extensions.Caching.Memory; + +using System.Net; using Tmds.MDns; @@ -9,17 +13,56 @@ public static class MdnsDiscovery public static event Action OnDeviceFound = null!; private static readonly ServiceBrowser serviceBrowser = new ServiceBrowser(); + private static readonly HttpClient client = new(); + private static readonly MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); static MdnsDiscovery() { serviceBrowser.StartBrowse("_http._tcp"); - serviceBrowser.ServiceAdded += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); - serviceBrowser.ServiceChanged += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); - serviceBrowser.ServiceRemoved += (_, eventArgs) => OnDeviceFound?.Invoke(eventArgs.Announcement.Addresses.First()); + serviceBrowser.ServiceAdded += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); + serviceBrowser.ServiceChanged += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); + serviceBrowser.ServiceRemoved += async (_, eventArgs) => await AddDevice(eventArgs.Announcement); } - public static IEnumerable GetChachedDevices() + public static void FetchChachedDevices() { - return serviceBrowser.Services.Select(s => s.Addresses.First()); + serviceBrowser.Services.ToList().ForEach(async d => await AddDevice(d)); + } + + private static async Task AddDevice(ServiceAnnouncement announcement) + { + IPAddress ipAddress = announcement.Addresses.First(); + + if (!memoryCache.TryGetValue(ipAddress.ToString(), out bool isShellyDevice)) + { + isShellyDevice = await IsShellyDevice(ipAddress); + _ = memoryCache.Set(ipAddress.ToString(), isShellyDevice, TimeSpan.FromHours(1)); + } + + if (isShellyDevice) + { + OnDeviceFound?.Invoke(ipAddress); + } + } + + private static async Task IsShellyDevice(IPAddress ipAddress) + { + ShellyResponse? shellyResponse; + + try + { + shellyResponse = await client.GetFromJsonAsync($"http://{ipAddress}/shelly"); + } + catch (HttpRequestException) + { + return false; + } + + if (shellyResponse is null || shellyResponse.Type != "SHPLG-S") + { + return false; + } + + return true; } } \ No newline at end of file From 12b8004a93536d6ac6a10bb5c6eb8ae5dca990c8 Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:09:30 +0100 Subject: [PATCH 04/12] UHHHM * Added Login System * Added Query from Prom * Changed Dashboard to display data --- .../Components/Layout/NewFile.txt | 1 + .../Components/Protected.razor | 42 +++++++ .../Elektrifikatsiya/Elektrifikatsiya.csproj | 12 +- .../Layouts/LoginLayout.razor | 34 ++++- .../Elektrifikatsiya/Layouts/MainLayout.razor | 39 +++--- ...0230322092207_fix foreign keys.Designer.cs | 96 +++++++++++++++ .../20230322092207_fix foreign keys.cs | 68 ++++++++++ .../MainDatabaseContextModelSnapshot.cs | 93 ++++++++++++++ .../Elektrifikatsiya/Models/Device.cs | 2 +- .../Models/PrometheusQueryResult.cs | 52 +++++++- .../Elektrifikatsiya/Models/User.cs | 11 +- .../Elektrifikatsiya/Pages/Dashboard.razor | 50 +++++--- .../Elektrifikatsiya/Program.cs | 12 +- .../Properties/launchSettings.json | 2 +- .../Implementations/AuthenticationService.cs | 4 +- .../Implementations/DeviceManagmentService.cs | 3 + .../Services/Implementations/UpdateService.cs | 116 +++++++++--------- .../Utilities/MdnsDiscovery.cs | 2 +- .../Utilities/PrometheusQuery.cs | 11 +- src/Elektrifikatsiya/docker-compose.yml | 13 +- 20 files changed, 554 insertions(+), 109 deletions(-) create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/Layout/NewFile.txt create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/Protected.razor create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.Designer.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Migrations/20230322092207_fix foreign keys.cs create mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Migrations/MainDatabaseContextModelSnapshot.cs 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: From 9eedadec4af328284c8bc389f73369f6289f244f Mon Sep 17 00:00:00 2001 From: Friend2868 <90833416+Friend2868@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:12:13 +0200 Subject: [PATCH 05/12] yes2 --- .../Elektrifikatsiya/Database/MainDatabaseContext.cs | 1 + src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs | 2 +- src/Elektrifikatsiya/Elektrifikatsiya/Models/Event.cs | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs index d58b2b1..95caf56 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Database/MainDatabaseContext.cs @@ -12,4 +12,5 @@ public class MainDatabaseContext : DbContext public DbSet Users { get; set; } public DbSet Devices { get; set; } + public DbSet Events { get; set; } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs index 815208e..b41d572 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs @@ -35,5 +35,5 @@ public class Device Room = room; } - private Device(){} + private Device(){} } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Event.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Event.cs index 5c76207..40c67ac 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Event.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Event.cs @@ -1,7 +1,13 @@ -namespace Elektrifikatsiya.Models +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace Elektrifikatsiya.Models { public class Event { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public int Id { get; private set; } public string EventName { get; set; } public string Description { get; set; } public DateTime Date { get; set; } From f36a65009f9cbb3b12c2c631967c9a42d9f058e4 Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:09:34 +0200 Subject: [PATCH 06/12] Fixes! --- .../Models/PrometheusQueryResult.cs | 26 +++++++++---------- .../Elektrifikatsiya/Pages/Dashboard.razor | 21 +++++++-------- src/Elektrifikatsiya/docker-compose.dcproj | 3 --- .../docker-compose.override.yml | 13 ---------- 4 files changed, 21 insertions(+), 42 deletions(-) delete mode 100644 src/Elektrifikatsiya/docker-compose.override.yml diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs index 2192c33..72ef231 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs @@ -1,4 +1,7 @@ -using System.Text.Json.Serialization; +using System.Diagnostics; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Mvc.Diagnostics; + namespace Elektrifikatsiya.Models; public enum Status @@ -44,7 +47,7 @@ public class PrometheusDataWrapper ResultType = resultType; Result = result; } - + public FluentResults.Result> MatrixTypeToTimestampFloatTuple() { if (ResultType != ResultType.Matrix) @@ -54,19 +57,19 @@ public class PrometheusDataWrapper List<(double, double)> result = new List<(double, double)>(); - if (Result[0]?.Value is null) + if (Result[0]?.Values is null || Result[0]?.Values?[0] is null) { return FluentResults.Result.Fail("There was no result in the Response Body"); } - foreach (PrometheusData prometheusData in Result) + foreach (object value in Result[0].Values) { - string[] segment = prometheusData.Value.ToString()!.Split(","); + string[] segment = value.ToString()!.Split(","); result.Add((Convert.ToDouble(segment[0][2..^1]), Convert.ToDouble(segment[1][1..^2]))); } - + Debug.WriteLine(result); return result; } @@ -106,12 +109,7 @@ public class PrometheusDataMetric public class PrometheusData { - public PrometheusDataMetric Metric { get; set; } - public object Value { get; set; } - - public PrometheusData(PrometheusDataMetric metric, object value) - { - Metric = metric; - Value = value; - } + public PrometheusDataMetric? Metric { get; set; } = null; + public object? Value { get; set; } = null; + public List? Values { get; set; } = null; } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index 2b96602..77373e1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -118,10 +118,9 @@ async Task HandleRedraw() { - + labels.Clear(); await lineChart.Clear(); - - await lineChart.AddLabelsDatasetsAndUpdate(Labels, await GetLineChartDataset()); + await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset()); } //TODO: insert dataset of current and last voltage usages @@ -129,19 +128,15 @@ { return new LineChartDataset { - Label = "# of random number in thousands", + Label = "Wattage", Data = await RandomizeData(), - BackgroundColor = backgroundColors, - BorderColor = borderColors, Fill = true, PointRadius = 3, CubicInterpolationMode = "monotone", }; } - string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" }; - 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 labels = new List(); async Task> RandomizeData() { @@ -160,8 +155,10 @@ var r = new Random(DateTime.Now.Millisecond); - return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => x.Item2).ToList() ?? new List(); + return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => + { + labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString()); + return x.Item2; + }).ToList() ?? new List(); } - -} } \ No newline at end of file diff --git a/src/Elektrifikatsiya/docker-compose.dcproj b/src/Elektrifikatsiya/docker-compose.dcproj index 8ee605c..8901d12 100644 --- a/src/Elektrifikatsiya/docker-compose.dcproj +++ b/src/Elektrifikatsiya/docker-compose.dcproj @@ -9,9 +9,6 @@ elektrifikatsiya - - docker-compose.yml - diff --git a/src/Elektrifikatsiya/docker-compose.override.yml b/src/Elektrifikatsiya/docker-compose.override.yml deleted file mode 100644 index 42bf3e9..0000000 --- a/src/Elektrifikatsiya/docker-compose.override.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.4' - -services: - elektrifikatsiya: - environment: - - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=https://+:443;http://+:80 - ports: - - "80" - - "443" - volumes: - - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro \ No newline at end of file From c08740f9ffeef51092607700fe9e2bc13fe02e98 Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 3 May 2023 09:02:33 +0200 Subject: [PATCH 07/12] Fix issue with 0 plugs --- .../Elektrifikatsiya/Models/PrometheusQueryResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs index 72ef231..7121698 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs @@ -57,12 +57,12 @@ public class PrometheusDataWrapper List<(double, double)> result = new List<(double, double)>(); - if (Result[0]?.Values is null || Result[0]?.Values?[0] is null) + if (Result.Count == 0 || Result[0]?.Values is null || Result[0]?.Values?[0] is null) { return FluentResults.Result.Fail("There was no result in the Response Body"); } - foreach (object value in Result[0].Values) + foreach (object value in Result[0]!.Values!) { string[] segment = value.ToString()!.Split(","); From 358bbdacce42c3e178f500c91a50c61fbfb2a44a Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Wed, 3 May 2023 09:30:19 +0200 Subject: [PATCH 08/12] Fix merge bugs --- .../Elektrifikatsiya/Pages/AddPlug.razor | 79 ++++++++++++------- .../Elektrifikatsiya/Pages/Dashboard.razor | 78 ++++++++++-------- 2 files changed, 97 insertions(+), 60 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index 37fee70..d78020d 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -6,12 +6,12 @@ @using Elektrifikatsiya.Services.Implementations @using Elektrifikatsiya.Utilities @using FluentResults; +@using Blazorise.LoadingIndicator; @inject IVersionProvider VersionProvider @inject IDeviceManagmentService DeviceManagmentService @inject IAuthenticationService AuthenticationService @inject IMessageService MessageService -@inject IAuthenticationService AuthenticationService @@ -27,8 +27,8 @@ Add a plug via wifi - - + + @@ -97,36 +97,61 @@ @code { - List ipAddresses = new List(); - List ipAddresses = new List(); - bool visible = false; + Validation? ipValidation; - DateTime time = DateTime.UtcNow; + bool visible = true; + + string ipText = null!; protected override void OnInitialized() { - ipAddresses.AddRange(MdnsDiscovery.GetChachedDevices()); - - System.Timers.Timer aTimer = new System.Timers.Timer(1000); - aTimer.Elapsed += (_, _) => { - DateTime dateTime = DateTime.UtcNow; - if(dateTime >= time.AddSeconds(3)) - { - visible = false; - InvokeAsync(StateHasChanged); - } - }; - aTimer.Start(); - - MdnsDiscovery.OnDeviceFound += address => + MdnsDiscovery.OnDeviceFound += async ipAddress => { - visible = true; - - ipAddresses.Add(address); - InvokeAsync(StateHasChanged); - - time = DateTime.UtcNow; + visible = false; + if (!ipAddresses.Contains(ipAddress)) + { + ipAddresses.Add(ipAddress); + await InvokeAsync(StateHasChanged); + } }; + + MdnsDiscovery.FetchChachedDevices(); } + + private async Task RegisterDevice(IPAddress ipAddress) + { + Result result = await DeviceManagmentService.RegisterDevice(ipAddress, (await AuthenticationService.GetUserAsync()).ValueOrDefault); + + if (result.IsSuccess) + { + await MessageService.Success("Device added!"); + } + else + { + await MessageService.Error(string.Join(',', result.Errors.Select(e => e.Message)), "Adding device failed!"); + } + } + + public void ValidateIPv4(ValidatorEventArgs e) + { + string? input = Convert.ToString(e.Value); + + if (string.IsNullOrWhiteSpace(input)) + { + e.Status = ValidationStatus.None; + return; + } + + string[] splitValues = input.Split('.'); + if (splitValues.Length != 4) + { + e.Status = ValidationStatus.Error; + return; + } + + e.Status = splitValues.All(r => byte.TryParse(r, out _)) ? ValidationStatus.Success : ValidationStatus.Error; + } + +} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index a2b1a5e..5b11650 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -2,8 +2,13 @@ @using Blazorise.Components; @using Elektrifikatsiya.Models; @using System.Net +@using Elektrifikatsiya.Services +@using Elektrifikatsiya.Utilities +@using System.Diagnostics @inject IVersionProvider VersionProvider +@inject IDeviceStatusService DeviceStatusService; +
@@ -34,7 +39,7 @@ - User: @context.Item.User + User: @context.Item.User.Name
Room: @context.Item.Room
@@ -44,7 +49,7 @@
- @@ -92,12 +97,22 @@ @code { //TODO: insert new event here if plug produces one - List events = new List() { new Event("Placeholder", "Event", DateTime.Now), new Event("Placeholder", "Event", DateTime.Now), new Event("Placeholder", "Event", DateTime.Now) }; + 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) @@ -108,50 +123,47 @@ async Task HandleRedraw() { + labels.Clear(); 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(), - BackgroundColor = backgroundColors, - BorderColor = borderColors, + Label = "Wattage", + Data = await RandomizeData(), Fill = true, PointRadius = 3, CubicInterpolationMode = "monotone", }; } - string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" }; - 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 labels = new List(); - 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 => + { + labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString()); + return x.Item2; + }).ToList() ?? new List(); } - - //private Background buttonColor = Background.Primary; - - private void ChangeButtonColor() - { - //device.on = !device.on; - } - - -} - +} \ No newline at end of file From 54a65e98337de38d3feec7c1b87ab9ebe84c72b9 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 4 May 2023 21:45:47 +0200 Subject: [PATCH 09/12] Fix formatting and other stuff whoopsie --- .../Components/TodoApp/BaseTodoItems.cs | 74 ----- .../Components/TodoApp/Filter.cs | 8 - .../Components/TodoApp/Todo.cs | 8 - .../Components/TodoApp/TodoItem.razor | 22 -- .../Components/TodoApp/TodoItems.razor | 57 ---- .../Layouts/LoginLayout.razor | 35 ++- .../Models/PrometheusQueryResult.cs | 43 ++- .../Elektrifikatsiya/Pages/AddPlug.razor | 257 +++++++++--------- .../Elektrifikatsiya/Pages/Dashboard.razor | 6 + .../Elektrifikatsiya/Pages/TodoAppPage.razor | 3 - .../Elektrifikatsiya/Program.cs | 2 +- .../Services/Implementations/UpdateService.cs | 4 +- .../Elektrifikatsiya/UserDatabase.sqlite-shm | Bin 32768 -> 0 bytes .../Elektrifikatsiya/UserDatabase.sqlite-wal | 0 .../Utilities/PrometheusQuery.cs | 3 - 15 files changed, 194 insertions(+), 328 deletions(-) delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/BaseTodoItems.cs delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Filter.cs delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Todo.cs delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItem.razor delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItems.razor delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/Pages/TodoAppPage.razor delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/UserDatabase.sqlite-shm delete mode 100644 src/Elektrifikatsiya/Elektrifikatsiya/UserDatabase.sqlite-wal diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/BaseTodoItems.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/BaseTodoItems.cs deleted file mode 100644 index 9a7799c..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/BaseTodoItems.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Blazorise; - -using Elektrifikatsiya.Components.TodoApp; - -using Microsoft.AspNetCore.Components; - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Elektrifikatsiya.Components.TodoApp; -public abstract class BaseTodoItems : ComponentBase -{ - protected Validations validations; - - protected string description; - - protected Filter filter = Filter.All; - - protected List todos = new() - { - new() { Description = "Buy milk" }, - new() { Description = "Call John regarding the meeting" }, - new() { Description = "Walk a dog" }, - }; - - protected IEnumerable Todos - { - get - { - var query = from t in todos select t; - - if (filter == Filter.Active) - query = from q in query where !q.Completed select q; - - if (filter == Filter.Completed) - query = from q in query where q.Completed select q; - - return query; - } - } - - protected void SetFilter(Filter filter) - { - this.filter = filter; - } - - protected void OnCheckAll(bool isChecked) - { - todos.ForEach(x => x.Completed = isChecked); - } - - protected async Task OnAddTodo() - { - if (await validations.ValidateAll()) - { - todos.Add(new() { Description = description }); - description = null; - - await validations.ClearAll(); - } - } - - protected void OnClearCompleted() - { - todos.RemoveAll(x => x.Completed); - filter = Filter.All; - } - - protected Task OnTodoStatusChanged(bool isChecked) - { - return InvokeAsync(StateHasChanged); - } -} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Filter.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Filter.cs deleted file mode 100644 index 7cd3ac5..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Filter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elektrifikatsiya.Components.TodoApp; - -public enum Filter -{ - All, - Active, - Completed, -} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Todo.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Todo.cs deleted file mode 100644 index fdf4888..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/Todo.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elektrifikatsiya.Components.TodoApp; - -public class Todo -{ - public bool Completed { get; set; } - - public string Description { get; set; } -} diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItem.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItem.razor deleted file mode 100644 index 8233b49..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItem.razor +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - @Todo.Description - - - -@code{ - Task OnCheckedChanged( bool isChecked ) - { - Todo.Completed = isChecked; - - return StatusChanged?.Invoke( isChecked ); - } - - [Parameter] public Todo Todo { get; set; } - - [Parameter] public Func StatusChanged { get; set; } -} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItems.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItems.razor deleted file mode 100644 index a9db980..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Components/TodoApp/TodoItems.razor +++ /dev/null @@ -1,57 +0,0 @@ -@inherits BaseTodoItems - - - - - - Todo List - - - - - All - - - - - - - - - - - - - - - - - - - - @foreach ( var todo in Todos ) - { - - } - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor index 091b551..daca9d9 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Layouts/LoginLayout.razor @@ -1,17 +1,42 @@ @using System.Diagnostics; +@using FluentResults; + +@inject Elektrifikatsiya.Services.IAuthenticationService AuthenticationService +@inject NavigationManager NavigationManager + @inherits LayoutComponentBase - + - + - - + + @code { - + private string username = string.Empty; + private string password = string.Empty; + private Color color = Color.Primary; + private bool disabled; + private async Task 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 diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs index 7121698..a8f8d06 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/PrometheusQueryResult.cs @@ -1,6 +1,6 @@ using System.Diagnostics; +using System.Globalization; using System.Text.Json.Serialization; -using Microsoft.AspNetCore.Mvc.Diagnostics; namespace Elektrifikatsiya.Models; @@ -47,51 +47,50 @@ public 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"); - } + 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.Count == 0 || Result[0]?.Values is null || Result[0]?.Values?[0] is null) { - return FluentResults.Result.Fail("There was no result in the Response Body"); + return FluentResults.Result.Fail("There was no result in the Response Body"); } foreach (object value in Result[0]!.Values!) - { + { - string[] segment = value.ToString()!.Split(","); + string[] segment = value.ToString()!.Split(","); - result.Add((Convert.ToDouble(segment[0][2..^1]), Convert.ToDouble(segment[1][1..^2]))); - } + result.Add((Convert.ToDouble(segment[0][2..^1], CultureInfo.InvariantCulture), Convert.ToDouble(segment[1][1..^2], CultureInfo.InvariantCulture))); + } Debug.WriteLine(result); return result; } public FluentResults.Result<(double, double)> VectorTypeToTimestampFloatTuple() { - if (ResultType != ResultType.Vector) - { - return FluentResults.Result.Fail("The response did not have the Vector Type"); - } + if (ResultType != ResultType.Vector) + { + return FluentResults.Result.Fail("The response did not have the Vector Type"); + } - string[]? segment = Result.FirstOrDefault()?.Value.ToString()?.Split(",") ?? null; + string[]? segment = Result.FirstOrDefault()?.Value?.ToString()?.Split(","); - if (segment is null) - { - return FluentResults.Result.Fail("There was no result in the Response Body"); - } + 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]))); + return (Convert.ToDouble(segment[0][2..^1]), Convert.ToDouble(segment[1][1..^2], CultureInfo.InvariantCulture)); } } - public class PrometheusDataMetric { [JsonPropertyName("__name__")] public string Name { get; set; } diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor index d78020d..d7c1b7f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/AddPlug.razor @@ -14,144 +14,155 @@ - - - - - - - Add a plug via wifi - - - - - - - - - - - - - - - - - IP: @context.Item - - - - - - - - - - - - + + + + + + + Add a plug via wifi + + + + + + + + + + + + + + + + + IP: @context.Item + + + + + + + + + + + + - - - - - - - Add a plug via IP - - - - -
- - - -
- -
-
-
-
-
-
- -
+ + + + + + + Add a plug via IP + + + + +
+ + + + + + Please enter a ip address. + Ip address is valid. + Ip address is not valid! + + +
+ +
+
+
+
+
+
+
+ +
@code { - List ipAddresses = new List(); + List ipAddresses = new List(); - Validation? ipValidation; + Validation? ipValidation; - bool visible = true; + bool visible = true; + bool disabled = false; - string ipText = null!; + string ipText = null!; - protected override void OnInitialized() - { - MdnsDiscovery.OnDeviceFound += async ipAddress => - { - visible = false; - if (!ipAddresses.Contains(ipAddress)) - { - ipAddresses.Add(ipAddress); - await InvokeAsync(StateHasChanged); - } - }; + protected override void OnInitialized() + { + MdnsDiscovery.OnDeviceFound += async ipAddress => + { + visible = false; + if (!ipAddresses.Contains(ipAddress)) + { + ipAddresses.Add(ipAddress); + await InvokeAsync(StateHasChanged); + } + }; - MdnsDiscovery.FetchChachedDevices(); - } + MdnsDiscovery.FetchChachedDevices(); + } - private async Task RegisterDevice(IPAddress ipAddress) - { - Result result = await DeviceManagmentService.RegisterDevice(ipAddress, (await AuthenticationService.GetUserAsync()).ValueOrDefault); + private async Task RegisterDevice(IPAddress ipAddress) + { + disabled = true; + Result result = await DeviceManagmentService.RegisterDevice(ipAddress, (await AuthenticationService.GetUserAsync()).ValueOrDefault); - if (result.IsSuccess) - { - await MessageService.Success("Device added!"); - } - else - { - await MessageService.Error(string.Join(',', result.Errors.Select(e => e.Message)), "Adding device failed!"); - } - } + if (result.IsSuccess) + { + await MessageService.Success("Device added!"); + } + else + { + await MessageService.Error(string.Join(',', result.Errors.Select(e => e.Message)), "Adding device failed!"); + } + disabled = false; + } - public void ValidateIPv4(ValidatorEventArgs e) - { - string? input = Convert.ToString(e.Value); + public void ValidateIPv4(ValidatorEventArgs e) + { + string? input = Convert.ToString(e.Value); - if (string.IsNullOrWhiteSpace(input)) - { - e.Status = ValidationStatus.None; - return; - } + if (string.IsNullOrWhiteSpace(input)) + { + e.Status = ValidationStatus.None; + return; + } - string[] splitValues = input.Split('.'); - if (splitValues.Length != 4) - { - e.Status = ValidationStatus.Error; - return; - } + string[] splitValues = input.Split('.'); + if (splitValues.Length != 4) + { + e.Status = ValidationStatus.Error; + return; + } - e.Status = splitValues.All(r => byte.TryParse(r, out _)) ? ValidationStatus.Success : ValidationStatus.Error; - } + e.Status = splitValues.All(r => byte.TryParse(r, out _)) ? ValidationStatus.Success : ValidationStatus.Error; + } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index 5b11650..a09d7f5 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -152,6 +152,12 @@ { plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|"; } + + if(string.IsNullOrEmpty(plugnames)) + { + return new(); + } + plugnames = plugnames[0..^1]; Debug.WriteLine($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/TodoAppPage.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/TodoAppPage.razor deleted file mode 100644 index ed5201a..0000000 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/TodoAppPage.razor +++ /dev/null @@ -1,3 +0,0 @@ -@using Elektrifikatsiya.Components.TodoApp -@page "/apps/todo" - \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index a9408b5..02d9c9d 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -60,7 +60,7 @@ IAuthenticationService authenticationService = serviceScope.ServiceProvider.GetR if (!mainDatabase.Users.Any()) { - authenticationService.RegisterUserAsync("admin", "admin", Role.Admin); + _ = authenticationService.RegisterUserAsync("admin", "admin", Role.Admin); } app.Run(); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index a0b7ef0..f31d184 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -33,12 +33,12 @@ public class UpdateService : IHostedService, IDisposable _ = deviceStatusService.TrackDevice(device); } - timer = new Timer((_) => Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); logger.LogInformation("Update service started."); } - private async void Update() + private async Task Update() { Result> getDeviceStatusResult = deviceStatusService.GetDevices(); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/UserDatabase.sqlite-shm b/src/Elektrifikatsiya/Elektrifikatsiya/UserDatabase.sqlite-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3 Query(string 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, From f6fd7c17eed52b97558060dbac1440dd561ea339 Mon Sep 17 00:00:00 2001 From: Stefan <32109571+Stefan-5422@users.noreply.github.com> Date: Thu, 4 May 2023 21:51:38 +0200 Subject: [PATCH 10/12] Make settings page work Idiot big forgot to commit :/ --- .../Elektrifikatsiya/Models/Device.cs | 14 ++++ .../Elektrifikatsiya/Pages/Dashboard.razor | 1 + .../Elektrifikatsiya/Pages/Settings.razor | 73 ++++++++++++++----- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs index b41d572..86c3d8c 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Net; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Server.IIS.Core; namespace Elektrifikatsiya.Models; @@ -35,5 +37,17 @@ public class Device Room = room; } + public static Device CopyDevice(Device device) => new Device(device.MacAddress, device.Name, device.IpAddress, device.User, device.Room); + + public void OverwiteDevice(Device overwriter) + { + this.Room = overwriter.Room; + this.Available = overwriter.Available; + this.PowerUsage = overwriter.PowerUsage; + this.Name = overwriter.Name; + this.User = overwriter.User; + this.MacAddress = overwriter.MacAddress; + this.IpAddress = overwriter.IpAddress; + } private Device(){} } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index 5b11650..2952361 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -5,6 +5,7 @@ @using Elektrifikatsiya.Services @using Elektrifikatsiya.Utilities @using System.Diagnostics +@using Elektrifikatsiya.Services.Implementations @inject IVersionProvider VersionProvider @inject IDeviceStatusService DeviceStatusService; diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor index 46f8278..60e82b6 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor @@ -2,6 +2,13 @@ @using Blazorise.Components; @using Elektrifikatsiya.Models; @using System.Net +@using System.Text.Json +@using Elektrifikatsiya.Services.Implementations +@using Elektrifikatsiya.Services; +@using FluentResults + +@inject IDeviceManagmentService DeviceManagmentService +
@@ -34,7 +41,7 @@ - User: @context.Item.User + User: @context.Item.User.Name
Room: @context.Item.Room
@@ -43,14 +50,14 @@

- @@ -62,6 +69,9 @@ + + @if (DeviceCopy is not null) + { + } - +
+ @code { - 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(); private bool hideButtonSettings = true; - private void Toggle() + Device? SelectedDevice = null; + Device? DeviceCopy = null; + + protected override void OnInitialized() + { + Result> getDevicesResult = DeviceManagmentService.GetDevices(); + + if (getDevicesResult.IsSuccess) + { + plugs = getDevicesResult.Value; + } + } + + private void Toggle(Device device) { hideButtonSettings = !hideButtonSettings; + SelectedDevice = device; + DeviceCopy = Device.CopyDevice(device); } + + private void OnSave() + { + if (hideButtonSettings || DeviceCopy is null) + { + + } + else + { + SelectedDevice.OverwiteDevice(DeviceCopy); + DeviceManagmentService.UpdateDevice(SelectedDevice); + } + } + + private void Delete(Device contextItem) + { + DeviceManagmentService.UnregisterDevice(contextItem.MacAddress); + } + } \ No newline at end of file From 461298d14d318df3a5e0928fd6373c66e340449f Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 4 May 2023 22:09:27 +0200 Subject: [PATCH 11/12] Fix delete things --- .../Elektrifikatsiya/Pages/Settings.razor | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor index 60e82b6..b4afa9f 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor @@ -8,6 +8,7 @@ @using FluentResults @inject IDeviceManagmentService DeviceManagmentService +@inject IMessageService MessageService
@@ -176,7 +177,7 @@ private void OnSave() { - if (hideButtonSettings || DeviceCopy is null) + if (hideButtonSettings || DeviceCopy is null || SelectedDevice is null) { } @@ -187,9 +188,22 @@ } } - private void Delete(Device contextItem) + private async Task Delete(Device contextItem) { - DeviceManagmentService.UnregisterDevice(contextItem.MacAddress); - } + bool succ = await MessageService.Confirm($"Do you really want to delete the device \"{contextItem.Name}\"?"); + if(!succ) + { + return; + } + + await DeviceManagmentService.UnregisterDevice(contextItem.MacAddress); + + int index = plugs.FindIndex(x => x.MacAddress == contextItem.MacAddress); + + if(index != -1) + { + plugs.RemoveAt(index); + } + } } \ No newline at end of file From 711955dd09fa53d64405b43158f6c874e135a2fb Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 4 May 2023 23:11:53 +0200 Subject: [PATCH 12/12] Turn on and off button thingy --- .../Elektrifikatsiya/Elektrifikatsiya.csproj | 1 + .../Elektrifikatsiya/Models/Device.cs | 27 +- .../Elektrifikatsiya/Pages/Dashboard.razor | 244 ++++++------------ .../Elektrifikatsiya/Pages/Dashboard.razor.cs | 110 ++++++-- .../Elektrifikatsiya/Pages/Settings.razor | 2 +- .../Elektrifikatsiya/Program.cs | 32 ++- .../Implementations/DeviceManagmentService.cs | 25 +- .../Implementations/DeviceStatusService.cs | 12 +- .../Services/Implementations/UpdateService.cs | 111 ++++---- 9 files changed, 299 insertions(+), 265 deletions(-) diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj b/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj index 72696db..89ebdd6 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Elektrifikatsiya.csproj @@ -42,6 +42,7 @@ + diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs index 86c3d8c..000e5ef 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Models/Device.cs @@ -1,8 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Net; -using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.AspNetCore.Server.IIS.Core; namespace Elektrifikatsiya.Models; @@ -26,7 +24,7 @@ public class Device public string Room { get; set; } [NotMapped] - public bool Available { get; set; } + public bool Enabled { get; set; } public Device(string macAddress, string name, IPAddress address, User user, string room) { @@ -37,17 +35,22 @@ public class Device Room = room; } - public static Device CopyDevice(Device device) => new Device(device.MacAddress, device.Name, device.IpAddress, device.User, device.Room); + public Device CopyDevice() + { + return new Device(MacAddress, Name, IpAddress, User, Room); + } public void OverwiteDevice(Device overwriter) { - this.Room = overwriter.Room; - this.Available = overwriter.Available; - this.PowerUsage = overwriter.PowerUsage; - this.Name = overwriter.Name; - this.User = overwriter.User; - this.MacAddress = overwriter.MacAddress; - this.IpAddress = overwriter.IpAddress; + Room = overwriter.Room; + Enabled = overwriter.Enabled; + PowerUsage = overwriter.PowerUsage; + Name = overwriter.Name; + User = overwriter.User; + MacAddress = overwriter.MacAddress; + IpAddress = overwriter.IpAddress; } - private Device(){} + + private Device() + { } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor index 70a9522..66a3051 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor @@ -9,168 +9,88 @@ @inject IVersionProvider VersionProvider @inject IDeviceStatusService DeviceStatusService; +@inject IDeviceManagmentService DeviceManagmentService;
- -
- - - - Plugs - - - - - - - - - - @context.Item.Name - - - - - User: @context.Item.User.Name -
- Room: @context.Item.Room -
- - @context.Item.PowerUsage W - -
-
- - - -
-
-
-
-
- - - - Logs - - - - -
- @context.Item.EventName - @context.Item.Date -
- @context.Item.Description -
-
-
-
-
- - - - - - + +
+ + + + Plugs + + + + + + + + + + @context.Item.Name + + + + + User: @context.Item.User.Name +
+ Room: @context.Item.Room +
+ + @context.Item.PowerUsage W + +
+
+ + + +
+
+
+
+
+ + + + Logs + + + + +
+ @context.Item.EventName + @context.Item.Date +
+ @context.Item.Description +
+
+
+
+
+ + + + + +
- - -@code { - //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(); - //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) - { - await HandleRedraw(); - } - } - - async Task HandleRedraw() - { - labels.Clear(); - await lineChart.Clear(); - await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset()); - } - - //TODO: insert dataset of current and last voltage usages - async Task> GetLineChartDataset() - { - return new LineChartDataset - { - Label = "Wattage", - Data = await RandomizeData(), - Fill = true, - PointRadius = 3, - CubicInterpolationMode = "monotone", - }; - } - - List labels = new List(); - - async Task> RandomizeData() - { - PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); - - string plugnames = ""; - foreach (Device device in plugs) - { - plugnames += $"shellyplug-s-{device.MacAddress}/relay/0|"; - } - - if(string.IsNullOrEmpty(plugnames)) - { - return new(); - } - - 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 deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => - { - labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString()); - return x.Item2; - }).ToList() ?? new List(); - } -} \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs index fa4bbf2..3d70f26 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Dashboard.razor.cs @@ -1,23 +1,97 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using System.Net.Http; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.Web.Virtualization; -using Microsoft.JSInterop; -using Elektrifikatsiya; -using Blazorise; +using Blazorise.Charts; -namespace Elektrifikatsiya.Pages +using Elektrifikatsiya.Models; +using Elektrifikatsiya.Utilities; + +using System.Diagnostics; +using System.Text; + +namespace Elektrifikatsiya.Pages; + +public partial class Dashboard { - public partial class Dashboard - { + //TODO: insert new event here if plug produces one + private readonly 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 + private List plugs = new List(); + + //code for graph + private 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) + { + await HandleRedraw(); + } + } + + private async Task Switch(Device device) + { + device.Enabled = !device.Enabled; + _ = await DeviceManagmentService.UpdateDevice(device); + } + + private async Task HandleRedraw() + { + labels.Clear(); + await lineChart.Clear(); + await lineChart.AddLabelsDatasetsAndUpdate(labels, await GetLineChartDataset()); + } + + //TODO: insert dataset of current and last voltage usages + private async Task> GetLineChartDataset() + { + return new LineChartDataset + { + Label = "Wattage", + Data = await DeviceData(), + Fill = true, + PointRadius = 3, + CubicInterpolationMode = "monotone", + }; + } + + private readonly List labels = new List(); + + private async Task> DeviceData() + { + PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); + + StringBuilder builder = new(); + foreach (Device device in plugs) + { + _ = builder.Append($"shellyplug-s-{device.MacAddress}/relay/0|"); + } + + if (builder.Length <= 1) + { + return new(); + } + + string plugnames = builder.ToString()[..^1]; + + Debug.WriteLine($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""); + + PrometheusDataWrapper? deviceData = (await promQueryer.Query($$"""sum(power{sensor=~"{{plugnames}}"})[1h:1m]"""))?.Data; + + Random r = new Random(DateTime.Now.Millisecond); + + return deviceData?.MatrixTypeToTimestampFloatTuple().ValueOrDefault?.Select(x => + { + labels.Add(DateTimeOffset.FromUnixTimeSeconds(long.Parse($"1{x.Item1}0")).UtcDateTime.ToLocalTime().ToShortTimeString()); + return x.Item2; + }).ToList() ?? new List(); } } \ No newline at end of file diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor index b4afa9f..263b2af 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Pages/Settings.razor @@ -172,7 +172,7 @@ { hideButtonSettings = !hideButtonSettings; SelectedDevice = device; - DeviceCopy = Device.CopyDevice(device); + DeviceCopy = device.CopyDevice(); } private void OnSave() diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs index 02d9c9d..6615bf5 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Program.cs @@ -8,6 +8,9 @@ using Elektrifikatsiya.Models; using Elektrifikatsiya.Services; using Elektrifikatsiya.Services.Implementations; +using HiveMQtt.Client; +using HiveMQtt.Client.Options; + using Microsoft.EntityFrameworkCore; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -18,6 +21,19 @@ builder.Services.AddServerSideBlazor(); builder.Services.AddHttpContextAccessor(); builder.Services.AddHostedService(); builder.Services.AddSingleton(); +builder.Services.AddSingleton((provider)=> +{ + HiveMQClientOptions options = new() + { + Host = "localhost", + Port = 1883, + UseTLS = false, + }; + + HiveMQClient client = new(options); + client.ConnectAsync().ConfigureAwait(false); + return client; +}); builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -27,7 +43,7 @@ builder.Services.AddBootstrapProviders(); builder.Services.AddHttpClient(); builder.Services.AddBlazorise(options => { - options.Immediate = true; + options.Immediate = true; }); AddBlazorise(builder.Services); @@ -37,9 +53,9 @@ WebApplication app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { - _ = app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - _ = app.UseHsts(); + _ = app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + _ = app.UseHsts(); } app.UseHttpsRedirection(); @@ -60,14 +76,14 @@ IAuthenticationService authenticationService = serviceScope.ServiceProvider.GetR if (!mainDatabase.Users.Any()) { - _ = authenticationService.RegisterUserAsync("admin", "admin", Role.Admin); + _ = authenticationService.RegisterUserAsync("admin", "admin", Role.Admin); } app.Run(); void AddBlazorise(IServiceCollection services) { - _ = services.AddBlazorise(); - _ = services.AddMaterialProviders(); - _ = services.AddMaterialIcons(); + _ = services.AddBlazorise(); + _ = services.AddMaterialProviders(); + _ = services.AddMaterialIcons(); } \ 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 3343c76..12e4bb1 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceManagmentService.cs @@ -3,9 +3,13 @@ using Elektrifikatsiya.Models; using FluentResults; +using HiveMQtt.Client; +using HiveMQtt.Client.Results; + +using Microsoft.EntityFrameworkCore; + using System.Net; using System.Net.NetworkInformation; -using Microsoft.EntityFrameworkCore; namespace Elektrifikatsiya.Services.Implementations; @@ -14,13 +18,15 @@ public class DeviceManagmentService : IDeviceManagmentService private readonly IServiceScopeFactory serviceScopeFactory; private readonly IDeviceStatusService deviceStatusService; private readonly ILogger logger; + private readonly IHiveMQClient hiveMQClient; private readonly HttpClient httpClient; - public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, ILogger logger, HttpClient httpClient) + public DeviceManagmentService(IServiceScopeFactory serviceScopeFactory, IDeviceStatusService deviceStatusService, ILogger logger, IHiveMQClient hiveMQClient, HttpClient httpClient) { this.serviceScopeFactory = serviceScopeFactory; this.deviceStatusService = deviceStatusService; this.logger = logger; + this.hiveMQClient = hiveMQClient; this.httpClient = httpClient; } @@ -151,13 +157,22 @@ public class DeviceManagmentService : IDeviceManagmentService using IServiceScope scope = serviceScopeFactory.CreateScope(); MainDatabaseContext mainDatabaseContext = scope.ServiceProvider.GetRequiredService(); - Result result = deviceStatusService.UpdateDeviceStatus(device); + Result getDeviceResult = GetDevice(device.MacAddress); - if (result.IsFailed) + if (getDeviceResult.IsFailed) { - return result; + return getDeviceResult.ToResult(); } + Result updateDeviceResult = deviceStatusService.UpdateDeviceStatus(device); + + if (updateDeviceResult.IsFailed) + { + return updateDeviceResult; + } + + PublishResult res = await hiveMQClient.PublishAsync($"shellies/shellyplug-s-{device.MacAddress}/relay/0/command", device.Enabled ? "on" : "off"); + _ = mainDatabaseContext.Update(device); return await Result.Try(Task () => mainDatabaseContext.SaveChangesAsync()); diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs index 2e6e904..becc6f4 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/DeviceStatusService.cs @@ -6,15 +6,17 @@ namespace Elektrifikatsiya.Services.Implementations; public class DeviceStatusService : IDeviceStatusService { - private readonly ILogger logger; - public event EventHandler? OnDeviceStatusChanged; + private readonly ILogger logger; + + public event EventHandler? OnDeviceStatusChanged; private readonly Dictionary devices = new(); - + public DeviceStatusService(ILogger logger) { - this.logger = logger; + this.logger = logger; } + public Result> GetDevices() { return devices.Values.ToList(); @@ -30,7 +32,7 @@ public class DeviceStatusService : IDeviceStatusService } modDevice.PowerUsage = device.PowerUsage; - modDevice.Available = device.Available; + modDevice.Enabled = device.Enabled; modDevice.IpAddress = device.IpAddress; modDevice.Name = device.Name; diff --git a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs index f31d184..0a3ef4a 100644 --- a/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs +++ b/src/Elektrifikatsiya/Elektrifikatsiya/Services/Implementations/UpdateService.cs @@ -1,6 +1,7 @@ using Elektrifikatsiya.Database; using Elektrifikatsiya.Models; using Elektrifikatsiya.Utilities; + using FluentResults; using Microsoft.EntityFrameworkCore; @@ -9,75 +10,77 @@ 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.Include(d=>d.User).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(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); + timer = new Timer(async (_) => await Update(), null, TimeSpan.Zero, TimeSpan.FromSeconds(15)); - logger.LogInformation("Update service started."); - } + logger.LogInformation("Update service started."); + } - private async Task Update() - { - Result> getDeviceStatusResult = deviceStatusService.GetDevices(); + private async Task 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 (getDeviceStatusResult.IsFailed) + { + logger.LogError("Updating devices failed!"); + } + PrometheusQuery promQueryer = new PrometheusQuery("http://localhost:9090"); + foreach (Device device in getDeviceStatusResult.Value) + { + PrometheusDataWrapper? devicePowerData = (await promQueryer.Query($"power{{sensor=\"shellyplug-s-{device.MacAddress}/relay/0\"}}"))?.Data; + PrometheusDataWrapper? deviceStatusData = (await promQueryer.Query($"state{{sensor=\"shellyplug-s-{device.MacAddress}/relay\"}}"))?.Data; - if (deviceData is not null) - { - device.PowerUsage = deviceData.VectorTypeToTimestampFloatTuple()?.ValueOrDefault.Item2 ?? 0; - deviceStatusService.UpdateDeviceStatus(device); + if (devicePowerData is not null && deviceStatusData is not null) + { + device.PowerUsage = devicePowerData.VectorTypeToTimestampFloatTuple()?.ValueOrDefault.Item2 ?? 0; + device.Enabled = (deviceStatusData.VectorTypeToTimestampFloatTuple()?.ValueOrDefault.Item2 ?? 0) == 1; + _ = deviceStatusService.UpdateDeviceStatus(device); } } - } + } - public Task StopAsync(CancellationToken cancellationToken) - { - logger.LogInformation("Stopping update service."); + public Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Stopping update service."); - _ = timer?.Change(Timeout.Infinite, 0); + _ = timer?.Change(Timeout.Infinite, 0); - logger.LogInformation("Stopped update service."); + logger.LogInformation("Stopped update service."); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + 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