From c71970f8de02dafaf0c63a77779db82a8aeb796b Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 12 Sep 2025 19:53:07 +0100 Subject: [PATCH] Add ATA IDENTIFY DEVICE responses management view with delete and consolidate functionality --- Aaru.Server.Database/Models/IdHashModel.cs | 17 +- .../Components/Admin/AdminNavMenu.razor | 3 + .../Components/Admin/Pages/Ata/List.razor | 103 ++++++++++++ .../Components/Admin/Pages/Ata/List.razor.cs | 150 ++++++++++++++++++ 4 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 Aaru.Server/Components/Admin/Pages/Ata/List.razor create mode 100644 Aaru.Server/Components/Admin/Pages/Ata/List.razor.cs diff --git a/Aaru.Server.Database/Models/IdHashModel.cs b/Aaru.Server.Database/Models/IdHashModel.cs index 75b17b27..27ecfcfb 100644 --- a/Aaru.Server.Database/Models/IdHashModel.cs +++ b/Aaru.Server.Database/Models/IdHashModel.cs @@ -5,11 +5,16 @@ namespace Aaru.Server.Database.Models; public class IdHashModel : BaseModel { - public string Hash { get; set; } - public string Description => string.Join(' ', VendorIdentification, ProductIdentification, ProductRevisionLevel); - public int[] Duplicates { get; set; } - public string VendorIdentification => StringHandlers.CToString(Inquiry?.VendorIdentification); + string? _description; + public string Hash { get; set; } + public string? Description + { + get => _description ?? string.Join(' ', VendorIdentification, ProductIdentification, ProductRevisionLevel); + set => _description = value; + } + public int[] Duplicates { get; set; } + public string VendorIdentification => StringHandlers.CToString(Inquiry?.VendorIdentification); public string ProductIdentification => StringHandlers.CToString(Inquiry?.ProductIdentification); - public string ProductRevisionLevel => StringHandlers.CToString(Inquiry?.ProductRevisionLevel); - public Inquiry? Inquiry { get; set; } + public string ProductRevisionLevel => StringHandlers.CToString(Inquiry?.ProductRevisionLevel); + public Inquiry? Inquiry { get; set; } } \ No newline at end of file diff --git a/Aaru.Server/Components/Admin/AdminNavMenu.razor b/Aaru.Server/Components/Admin/AdminNavMenu.razor index 6a64193f..21d25952 100644 --- a/Aaru.Server/Components/Admin/AdminNavMenu.razor +++ b/Aaru.Server/Components/Admin/AdminNavMenu.razor @@ -8,6 +8,9 @@ Dashboard + + ATA IDENTIFY DEVICE responses + Block descriptors diff --git a/Aaru.Server/Components/Admin/Pages/Ata/List.razor b/Aaru.Server/Components/Admin/Pages/Ata/List.razor new file mode 100644 index 00000000..55dca607 --- /dev/null +++ b/Aaru.Server/Components/Admin/Pages/Ata/List.razor @@ -0,0 +1,103 @@ +@page "/admin/ata" +@attribute [Authorize] +@layout AdminLayout + +@inject Microsoft.EntityFrameworkCore.IDbContextFactory DbContextFactory + +ATA IDENTIFY DEVICE responses + +@if(!_initialized) +{ +
+

Loading...

+
+ + return; +} + +
+

ATA IDENTIFY DEVICE responses

+ + + + + + + + + + + @foreach(Ata item in _items) + { + + + + + + + } + +
+ @DisplayNameHelper.GetDisplayName(typeof(Ata), nameof(Ata.Id)) + + @DisplayNameHelper.GetDisplayName(typeof(Ata), nameof(Ata.IdentifyDevice.Value.Model)) + + Firmware revision + + Actions +
+ @item.Id + + @item.IdentifyDevice?.Model + + @item.IdentifyDevice?.FirmwareRevision + + + Details + + +
+ + @if(_duplicates.Count > 0) + { +
+ The following ATA IDENTIFY DEVICE responses have duplicates. + + + @foreach(IdHashModel item in _duplicates) + { + + + + } + +
+ @item.Description +
+
+ + + } +
+ + + +
Are you sure you want to delete the duplicates?
+
+ + + + +
+ + + +
Are you sure you want to delete this ATA IDENTIFY DEVICE response?
+
+ + + + +
\ No newline at end of file diff --git a/Aaru.Server/Components/Admin/Pages/Ata/List.razor.cs b/Aaru.Server/Components/Admin/Pages/Ata/List.razor.cs new file mode 100644 index 00000000..27be7729 --- /dev/null +++ b/Aaru.Server/Components/Admin/Pages/Ata/List.razor.cs @@ -0,0 +1,150 @@ +using Aaru.Server.Core; +using Aaru.Server.Database.Models; +using BlazorBootstrap; +using Microsoft.EntityFrameworkCore; +using DbContext = Aaru.Server.Database.DbContext; + +namespace Aaru.Server.Components.Admin.Pages.Ata; + +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 = ctx.Ata.AsEnumerable() + .OrderBy(static m => m.IdentifyDevice?.Model) + .ThenBy(static m => m.IdentifyDevice?.FirmwareRevision) + .ToList(); + + List hashes = await ctx.Ata.Select(static m => new IdHashModel + { + Id = m.Id, + Hash = Hash.Sha512(m.Identify) + }) + .ToListAsync(); + + _duplicates = hashes.GroupBy(static x => x.Hash) + .Where(static g => g.Count() > 1) + .Select(x => hashes.FirstOrDefault(y => y.Hash == x.Key)) + .ToList(); + + for(int i = 0; i < _duplicates.Count; i++) + { + CommonTypes.Metadata.Ata unique = ctx.Ata.First(a => a.Id == _duplicates[i].Id); + + _duplicates[i].Description = unique.IdentifyDevice?.Model; + + _duplicates[i].Duplicates = hashes.Where(h => h.Hash == _duplicates[i]?.Hash) + .Skip(1) + .Select(static x => x.Id) + .ToArray(); + } + } + + Task ConsolidateDuplicatesAsync() => _consolidateModal?.ShowAsync(); + + Task HideConsolidateModalAsync() => _consolidateModal?.HideAsync(); + + async Task ConfirmConsolidateAsync() + { + await using DbContext ctx = await DbContextFactory.CreateDbContextAsync(); + + foreach(IdHashModel? duplicate in _duplicates) + { + CommonTypes.Metadata.Ata? master = ctx.Ata.FirstOrDefault(m => duplicate != null && m.Id == duplicate.Id); + + if(master is null) continue; + + if(duplicate?.Duplicates == null) continue; + + foreach(int duplicateId in duplicate.Duplicates) + { + CommonTypes.Metadata.Ata? slave = ctx.Ata.Include(static ata => ata.ReadCapabilities) + .FirstOrDefault(m => m.Id == duplicateId); + + if(slave is null) continue; + + foreach(Device ataDevice in ctx.Devices.Where(d => d.ATA.Id == duplicateId)) ataDevice.ATA = master; + + foreach(Device atapiDevice in ctx.Devices.Where(d => d.ATAPI.Id == duplicateId)) + atapiDevice.ATAPI = master; + + foreach(UploadedReport ataReport in ctx.Reports.Where(d => d.ATA.Id == duplicateId)) + ataReport.ATA = master; + + foreach(UploadedReport atapiReport in ctx.Reports.Where(d => d.ATAPI.Id == duplicateId)) + atapiReport.ATAPI = master; + + foreach(CommonTypes.Metadata.TestedMedia testedMedia in + ctx.TestedMedia.Where(d => d.AtaId == duplicateId)) + { + testedMedia.AtaId = duplicate.Id; + ctx.Update(testedMedia); + } + + if(master.ReadCapabilities is null && slave.ReadCapabilities != null) + master.ReadCapabilities = slave.ReadCapabilities; + + ctx.Ata.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.Ata? ata = await ctx.Ata.FindAsync(id); + + if(ata is not null) + { + ctx.Ata.Remove(ata); + await ctx.SaveChangesAsync(); + } + } +} \ No newline at end of file