From b112c6a5aa6e199a9a337234483c3cbe4ca842bb Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 12 Sep 2025 12:31:59 +0100 Subject: [PATCH] Add USB Devices management page with duplicate handling --- .../Components/Admin/AdminNavMenu.razor | 3 + .../Admin/Pages/Usb/Devices/List.razor | 124 ++++++++++++++++ .../Admin/Pages/Usb/Devices/List.razor.cs | 140 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor create mode 100644 Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor.cs diff --git a/Aaru.Server/Components/Admin/AdminNavMenu.razor b/Aaru.Server/Components/Admin/AdminNavMenu.razor index a2c282a1..f6c45601 100644 --- a/Aaru.Server/Components/Admin/AdminNavMenu.razor +++ b/Aaru.Server/Components/Admin/AdminNavMenu.razor @@ -59,6 +59,9 @@ Supported densities + + USB Devices + USB Products diff --git a/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor b/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor new file mode 100644 index 00000000..ab1fff7c --- /dev/null +++ b/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor @@ -0,0 +1,124 @@ +@page "/admin/usb/devices" +@attribute [Authorize] +@layout AdminLayout + +@inject Microsoft.EntityFrameworkCore.IDbContextFactory DbContextFactory + +USB Devices + +@if(!_initialized) +{ +
+

Loading...

+
+ + return; +} + +
+

USB Devices

+ + + + + + + + + + + + + @foreach(Usb item in _items) + { + + + + + + + + + } + +
+ @DisplayNameHelper.GetDisplayName(typeof(Usb), nameof(Usb.Manufacturer)) + + @DisplayNameHelper.GetDisplayName(typeof(Usb), nameof(Usb.Product)) + + @DisplayNameHelper.GetDisplayName(typeof(Usb), nameof(Usb.VendorID)) + + @DisplayNameHelper.GetDisplayName(typeof(Usb), nameof(Usb.ProductID)) + + @DisplayNameHelper.GetDisplayName(typeof(Usb), nameof(Usb.RemovableMedia)) + + Actions +
+ @item.Manufacturer + + @item.Product + + @item.VendorID + + @item.ProductID + + @item.RemovableMedia + + + Details + + +
+ + @if(_duplicates.Count > 0) + { +
+ The following USB devices have duplicates. + + + @foreach(UsbModel item in _duplicates) + { + + + + + + + } + +
+ @item.Manufacturer + + @item.Product + + @item.VendorID + + @item.ProductID +
+
+ + + } +
+ + + +
Are you sure you want to delete the duplicates?
+
+ + + + +
+ + + +
Are you sure you want to delete this USB device?
+
+ + + + +
\ No newline at end of file diff --git a/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor.cs b/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor.cs new file mode 100644 index 00000000..e2f9ac18 --- /dev/null +++ b/Aaru.Server/Components/Admin/Pages/Usb/Devices/List.razor.cs @@ -0,0 +1,140 @@ +using Aaru.Server.Database.Models; +using BlazorBootstrap; +using Microsoft.EntityFrameworkCore; +using DbContext = Aaru.Server.Database.DbContext; + +namespace Aaru.Server.Components.Admin.Pages.Usb.Devices; + +public partial class List +{ + private Modal? _consolidateModal; + private int _deleteId; + private Modal? _deleteModal; + List _duplicates; + bool _initialized; + List _items; + + + /// + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + StateHasChanged(); + + await RefreshItemsAsync(); + + _initialized = true; + + StateHasChanged(); + } + + async Task RefreshItemsAsync() + { + await using DbContext ctx = await DbContextFactory.CreateDbContextAsync(); + + _items = await ctx.Usb.OrderBy(static u => u.Manufacturer) + .ThenBy(static u => u.Product) + .ThenBy(static u => u.VendorID) + .ThenBy(static u => u.ProductID) + .ToListAsync(); + + _duplicates = await ctx.Usb.GroupBy(static x => new + { + x.Manufacturer, + x.Product, + x.VendorID, + x.ProductID + }) + .Where(static x => x.Count() > 1) + .Select(static x => new UsbModel + { + Manufacturer = x.Key.Manufacturer, + Product = x.Key.Product, + VendorID = x.Key.VendorID, + ProductID = x.Key.ProductID + }) + .ToListAsync(); + } + + Task ConsolidateDuplicatesAsync() => _consolidateModal?.ShowAsync(); + + Task HideConsolidateModalAsync() => _consolidateModal?.HideAsync(); + + async Task ConfirmConsolidateAsync() + { + await using DbContext ctx = await DbContextFactory.CreateDbContextAsync(); + + foreach(UsbModel duplicate in _duplicates) + { + CommonTypes.Metadata.Usb? master = ctx.Usb.FirstOrDefault(m => m.Manufacturer == duplicate.Manufacturer && + m.Product == duplicate.Product && + m.VendorID == duplicate.VendorID && + m.ProductID == duplicate.ProductID); + + if(master is null) continue; + + foreach(CommonTypes.Metadata.Usb slave in await ctx.Usb + .Where(m => m.Manufacturer == duplicate.Manufacturer && + m.Product == duplicate.Product && + m.VendorID == duplicate.VendorID && + m.ProductID == duplicate.ProductID) + .Skip(1) + .ToArrayAsync()) + { + if(slave.Descriptors != null && master.Descriptors != null) + { + if(!master.Descriptors.SequenceEqual(slave.Descriptors)) continue; + } + + foreach(Device device in ctx.Devices.Where(d => d.USB.Id == slave.Id)) device.USB = master; + + foreach(UploadedReport report in ctx.Reports.Where(d => d.USB.Id == slave.Id)) report.USB = master; + + if(master.Descriptors is null && slave.Descriptors != null) + { + master.Descriptors = slave.Descriptors; + ctx.Usb.Update(master); + } + + ctx.Usb.Remove(slave); + } + } + + await ctx.SaveChangesAsync(); + + await RefreshItemsAsync(); + + StateHasChanged(); + } + + private async Task ShowDeleteModal(int id) + { + _deleteId = id; + if(_deleteModal != null) await _deleteModal.ShowAsync(); + } + + private async Task HideDeleteModal() + { + if(_deleteModal != null) await _deleteModal.HideAsync(); + } + + private async Task ConfirmDelete() + { + await DeleteAsync(_deleteId); + await HideDeleteModal(); + await RefreshItemsAsync(); + } + + private async Task DeleteAsync(int id) + { + await using DbContext ctx = await DbContextFactory.CreateDbContextAsync(); + CommonTypes.Metadata.Usb? usb = await ctx.Usb.FindAsync(id); + + if(usb is not null) + { + ctx.Usb.Remove(usb); + await ctx.SaveChangesAsync(); + } + } +} \ No newline at end of file