Identify drives by a Scrutiny UUID instead of wwn (#960)

* Generate a UUIDv5 from a random namespace  based on WWN, model name, and serial number
* Migrate sqlite and influxdb data accordingly
* Update frontend API routes and components
* Fixes #923
This commit is contained in:
Aram Akhavan
2026-03-25 20:16:17 -07:00
committed by GitHub
parent e4c40f7e80
commit c3b2eb2b4f
69 changed files with 815 additions and 402 deletions
@@ -28,7 +28,7 @@ describe('DashboardDeviceArchiveDialogComponent', () => {
],
providers: [
{provide: MatDialogRef, useValue: matDialogRefSpy},
{provide: MAT_DIALOG_DATA, useValue: {wwn: 'test-wwn', title: 'my-test-device-title'}},
{provide: MAT_DIALOG_DATA, useValue: {scrutiny_uuid: 'ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c', title: 'my-test-device-title'}},
{provide: DashboardDeviceArchiveDialogService, useValue: dashboardDeviceArchiveDialogServiceSpy}
],
declarations: [DashboardDeviceArchiveDialogComponent]
@@ -56,7 +56,7 @@ describe('DashboardDeviceArchiveDialogComponent', () => {
dashboardDeviceArchiveDialogServiceSpy.archiveDevice.and.returnValue(of({'success': true}));
component.onArchiveClick()
expect(dashboardDeviceArchiveDialogServiceSpy.archiveDevice).toHaveBeenCalledWith('test-wwn');
expect(dashboardDeviceArchiveDialogServiceSpy.archiveDevice).toHaveBeenCalledWith('ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c');
expect(dashboardDeviceArchiveDialogServiceSpy.archiveDevice.calls.count())
.withContext('one call')
.toBe(1);
@@ -11,7 +11,7 @@ export class DashboardDeviceArchiveDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<DashboardDeviceArchiveDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: {wwn: string, title: string},
@Inject(MAT_DIALOG_DATA) public data: {scrutiny_uuid: string, title: string},
private _archiveService: DashboardDeviceArchiveDialogService,
) {
}
@@ -20,7 +20,7 @@ export class DashboardDeviceArchiveDialogComponent implements OnInit {
}
onArchiveClick(): void {
this._archiveService.archiveDevice(this.data.wwn)
this._archiveService.archiveDevice(this.data.scrutiny_uuid)
.subscribe((data) => {
this.dialogRef.close(data);
});
@@ -26,13 +26,13 @@ export class DashboardDeviceArchiveDialogService
// -----------------------------------------------------------------------------------------------------
archiveDevice(wwn: string): Observable<any>
archiveDevice(scrutiny_uid: string): Observable<any>
{
return this._httpClient.post( `${getBasePath()}/api/device/${wwn}/archive`, {});
return this._httpClient.post( `${getBasePath()}/api/device/${scrutiny_uid}/archive`, {});
}
unarchiveDevice(wwn: string): Observable<any>
unarchiveDevice(scrutiny_uid: string): Observable<any>
{
return this._httpClient.post( `${getBasePath()}/api/device/${wwn}/unarchive`, {});
return this._httpClient.post( `${getBasePath()}/api/device/${scrutiny_uid}/unarchive`, {});
}
}
@@ -28,7 +28,7 @@ describe('DashboardDeviceDeleteDialogComponent', () => {
],
providers: [
{provide: MatDialogRef, useValue: matDialogRefSpy},
{provide: MAT_DIALOG_DATA, useValue: {wwn: 'test-wwn', title: 'my-test-device-title'}},
{provide: MAT_DIALOG_DATA, useValue: {scrutiny_uuid: 'ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c', title: 'my-test-device-title'}},
{provide: DashboardDeviceDeleteDialogService, useValue: dashboardDeviceDeleteDialogServiceSpy}
],
declarations: [DashboardDeviceDeleteDialogComponent]
@@ -56,7 +56,7 @@ describe('DashboardDeviceDeleteDialogComponent', () => {
dashboardDeviceDeleteDialogServiceSpy.deleteDevice.and.returnValue(of({'success': true}));
component.onDeleteClick()
expect(dashboardDeviceDeleteDialogServiceSpy.deleteDevice).toHaveBeenCalledWith('test-wwn');
expect(dashboardDeviceDeleteDialogServiceSpy.deleteDevice).toHaveBeenCalledWith('ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c');
expect(dashboardDeviceDeleteDialogServiceSpy.deleteDevice.calls.count())
.withContext('one call')
.toBe(1);
@@ -11,7 +11,7 @@ export class DashboardDeviceDeleteDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<DashboardDeviceDeleteDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: {wwn: string, title: string},
@Inject(MAT_DIALOG_DATA) public data: {scrutiny_uuid: string, title: string},
private _deleteService: DashboardDeviceDeleteDialogService,
) {
}
@@ -20,7 +20,7 @@ export class DashboardDeviceDeleteDialogComponent implements OnInit {
}
onDeleteClick(): void {
this._deleteService.deleteDevice(this.data.wwn)
this._deleteService.deleteDevice(this.data.scrutiny_uuid)
.subscribe((data) => {
this.dialogRef.close(data);
});
@@ -27,8 +27,8 @@ export class DashboardDeviceDeleteDialogService
// -----------------------------------------------------------------------------------------------------
deleteDevice(wwn: string): Observable<any>
deleteDevice(scrutiny_uuid: string): Observable<any>
{
return this._httpClient.delete( `${getBasePath()}/api/device/${wwn}`, {});
return this._httpClient.delete( `${getBasePath()}/api/device/${scrutiny_uuid}`, {});
}
}
@@ -16,7 +16,7 @@
</div>
<div class="flex items-center">
<div class="flex flex-col">
<a [routerLink]="'/device/'+ deviceSummary.device.wwn"
<a [routerLink]="'/device/'+ deviceSummary.device.scrutiny_uuid"
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceSummary.device | deviceTitle:config.dashboard_display}}</a>
<div [ngClass]="classDeviceLastUpdatedOn(deviceSummary)" class="font-medium text-sm" *ngIf="deviceSummary.smart">
Last Updated on {{deviceSummary.smart.collector_date | date:'MMMM dd, yyyy - HH:mm' }}
@@ -30,7 +30,7 @@
<mat-icon [svgIcon]="'more_vert'"></mat-icon>
</button>
<mat-menu #previousStatementMenu="matMenu">
<a mat-menu-item [routerLink]="'/device/'+ deviceSummary.device.wwn">
<a mat-menu-item [routerLink]="'/device/'+ deviceSummary.device.scrutiny_uuid">
<span class="flex items-center">
<mat-icon class="icon-size-20 mr-3"
[svgIcon]="'assessment'"></mat-icon>
@@ -75,22 +75,22 @@ export class DashboardDeviceComponent implements OnInit {
openArchiveDialog(): void {
if(this.deviceSummary.device.archived){
this._archiveService.unarchiveDevice(this.deviceSummary.device.wwn).subscribe((result) => {
this._archiveService.unarchiveDevice(this.deviceSummary.device.scrutiny_uuid).subscribe((result) => {
if(result) {
this.deviceUnarchived.emit(this.deviceSummary.device.wwn)
this.deviceUnarchived.emit(this.deviceSummary.device.scrutiny_uuid)
}
})
return;
}
const dialogRef = this.dialog.open(DashboardDeviceArchiveDialogComponent, {
data: {
wwn: this.deviceSummary.device.wwn,
scrutiny_uuid: this.deviceSummary.device.scrutiny_uuid,
title: DeviceTitlePipe.deviceTitleWithFallback(this.deviceSummary.device, this.config.dashboard_display)
}
});
dialogRef.afterClosed().subscribe(result => {
if(result) {
this.deviceArchived.emit(this.deviceSummary.device.wwn);
this.deviceArchived.emit(this.deviceSummary.device.scrutiny_uuid);
}
})
}
@@ -99,7 +99,7 @@ export class DashboardDeviceComponent implements OnInit {
const dialogRef = this.dialog.open(DashboardDeviceDeleteDialogComponent, {
// width: '250px',
data: {
wwn: this.deviceSummary.device.wwn,
scrutiny_uuid: this.deviceSummary.device.scrutiny_uuid,
title: DeviceTitlePipe.deviceTitleWithFallback(this.deviceSummary.device, this.config.dashboard_display)
}
});
@@ -107,7 +107,7 @@ export class DashboardDeviceComponent implements OnInit {
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed', result);
if (result.success) {
this.deviceDeleted.emit(this.deviceSummary.device.wwn)
this.deviceDeleted.emit(this.deviceSummary.device.scrutiny_uuid)
}
});
}