Skip to content

Commit

Permalink
NAS-132335 / 25.04 / Adding disks to containers (#11022)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored Nov 15, 2024
1 parent e7fee9c commit 92ec97a
Show file tree
Hide file tree
Showing 119 changed files with 1,429 additions and 876 deletions.
2 changes: 1 addition & 1 deletion src/app/enums/virtualization.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export enum VirtualizationStatus {
Stopped = 'STOPPED',
}

export const virtualizationStatusMap = new Map<VirtualizationStatus, string>([
export const virtualizationStatusLabels = new Map<VirtualizationStatus, string>([
[VirtualizationStatus.Running, T('Running')],
[VirtualizationStatus.Stopped, T('Stopped')],
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
<div class="header">
<h3 class="title">
<div class="mobile-prefix">
<ix-mobile-back-button
(onClose)="onCloseMobileDetails()"
></ix-mobile-back-button>
{{ 'Details for' | translate }}
</div>

<span class="prefix">
{{ 'Details for' | translate }}
</span>

<span class="name">
{{ instance().name }}
</span>
</h3>
</div>

<div class="cards">
<div class="scroll-window">
<ix-instance-general-info [instance]="instance()"></ix-instance-general-info>

<ix-instance-devices></ix-instance-devices>

<ix-instance-disks></ix-instance-disks>

<ix-instance-proxies></ix-instance-proxies>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
:host {
display: block;
width: 100%;
width: 100%;

.header {
color: var(--fg1);
Expand All @@ -16,6 +15,7 @@
}

.title {
color: var(--fg2);
margin-bottom: 12px;
margin-top: 20px;
min-height: 36px;
Expand All @@ -34,7 +34,7 @@
}

.prefix {
display: flex;
display: inline;

@media (max-width: $breakpoint-hidden) {
display: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { MockComponents } from 'ng-mocks';
import { VirtualizationInstance } from 'app/interfaces/virtualization.interface';
import {
InstanceDetailsComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-details.component';
import {
InstanceDevicesComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-devices/instance-devices.component';
import {
InstanceDisksComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-disks/instance-disks.component';
import {
InstanceGeneralInfoComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-general-info/instance-general-info.component';
import {
InstanceProxiesComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-proxies/instance-proxies.component';
import { VirtualizationInstancesStore } from 'app/pages/virtualization/stores/virtualization-instances.store';

describe('InstanceDetailsComponent', () => {
let spectator: Spectator<InstanceDetailsComponent>;
const createComponent = createComponentFactory({
component: InstanceDetailsComponent,
imports: [
MockComponents(
InstanceGeneralInfoComponent,
InstanceDevicesComponent,
InstanceDisksComponent,
InstanceProxiesComponent,
),
],
providers: [
mockProvider(VirtualizationInstancesStore),
],
});

beforeEach(() => {
spectator = createComponent({
props: {
instance: {
name: 'my-instance',
} as VirtualizationInstance,
},
});
});

it('shows name of the selected instance', () => {
expect(spectator.query('.title')).toHaveText('Details for');
expect(spectator.query('.title')).toHaveText('my-instance');
});

it('shows details sub-components related to selected instance', () => {
expect(spectator.query(InstanceGeneralInfoComponent)).toExist();
expect(spectator.query(InstanceDevicesComponent)).toExist();
expect(spectator.query(InstanceDisksComponent)).toExist();
expect(spectator.query(InstanceProxiesComponent)).toExist();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import {
} from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { VirtualizationInstance } from 'app/interfaces/virtualization.interface';
import { MobileBackButtonComponent } from 'app/modules/buttons/mobile-back-button/mobile-back-button.component';
import { DatasetIconComponent } from 'app/pages/datasets/components/dataset-icon/dataset-icon.component';
import {
InstanceDevicesComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-devices/instance-devices.component';
import {
InstanceDisksComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-disks/instance-disks.component';
import {
InstanceGeneralInfoComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-general-info/instance-general-info.component';
Expand All @@ -25,14 +30,14 @@ import { VirtualizationInstancesStore } from 'app/pages/virtualization/stores/vi
InstanceDevicesComponent,
InstanceGeneralInfoComponent,
InstanceProxiesComponent,
InstanceDisksComponent,
DatasetIconComponent,
MobileBackButtonComponent,
],
})
export class InstanceDetailsComponent {
instance = input.required<VirtualizationInstance>();

protected readonly devices = this.instancesStore.selectedInstanceDevices;
protected readonly isLoadingDevices = this.instancesStore.isLoadingDevices;

constructor(
private instancesStore: VirtualizationInstancesStore,
) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,11 @@ <h3 mat-card-title>
@if (isLoadingDevices()) {
<ngx-skeleton-loader></ngx-skeleton-loader>
} @else {
@for (device of shownDevices(); track device) {
@for (device of shownDevices(); track device.name) {
<div class="device">
<span>{{ getDeviceDescription(device) }}</span>
<button
mat-icon-button
class="delete-button"
[attr.aria-label]="'Delete device' | translate"
[ixTest]="['delete-device', $index]"
(click)="deleteProxyPressed(device)"
>
<ix-icon name="mdi-close"></ix-icon>
</button>

<ix-delete-device-button [device]="device"></ix-delete-device-button>
</div>
} @empty {
{{ 'No devices added.' | translate }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,3 @@
display: flex;
justify-content: space-between;
}

.delete-button {
opacity: 0.8;

&:focus,
&:hover {
opacity: 1;
}

ix-icon {
height: 20px;
width: 20px;
}
}
Original file line number Diff line number Diff line change
@@ -1,72 +1,61 @@
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { mockCall, mockApi } from 'app/core/testing/utils/mock-api.utils';
import { MockComponent } from 'ng-mocks';
import { VirtualizationDeviceType } from 'app/enums/virtualization.enum';
import { VirtualizationProxy, VirtualizationUsb } from 'app/interfaces/virtualization.interface';
import { DialogService } from 'app/modules/dialog/dialog.service';
import { IxIconHarness } from 'app/modules/ix-icon/ix-icon.harness';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import {
InstanceDevicesComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-devices/instance-devices.component';
import {
DeleteDeviceButtonComponent,
} from 'app/pages/virtualization/components/common/delete-device-button/delete-device-button.component';
import { VirtualizationInstancesStore } from 'app/pages/virtualization/stores/virtualization-instances.store';
import { ApiService } from 'app/services/api.service';

describe('InstanceDevicesComponent', () => {
let spectator: Spectator<InstanceDevicesComponent>;
let loader: HarnessLoader;
const devices = [
{
dev_type: VirtualizationDeviceType.Usb,
name: 'usb1',
} as VirtualizationUsb,
{
dev_type: VirtualizationDeviceType.Gpu,
name: 'gpu1',
},
{
name: 'proxy2',
} as VirtualizationProxy,
];

const createComponent = createComponentFactory({
component: InstanceDevicesComponent,
imports: [
MockComponent(DeleteDeviceButtonComponent),
],
providers: [
mockProvider(DialogService, {
confirm: jest.fn(() => of(true)),
}),
mockApi([
mockCall('virt.instance.device_delete'),
]),
mockProvider(SnackbarService),
mockProvider(VirtualizationInstancesStore, {
isLoadingDevices: () => false,
selectedInstance: () => ({ id: 'my-instance' }),
selectedInstanceDevices: () => [
{
dev_type: VirtualizationDeviceType.Usb,
name: 'usb1',
} as VirtualizationUsb,
{
dev_type: VirtualizationDeviceType.Gpu,
name: 'gpu1',
},
{
name: 'proxy2',
} as VirtualizationProxy,
],
selectedInstanceDevices: () => devices,
loadDevices: jest.fn(),
}),
],
});

beforeEach(() => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
});

it('shows a list of USB or GPU devices', () => {
const devices = spectator.queryAll('.device');
const deviceRows = spectator.queryAll('.device');

expect(devices).toHaveLength(2);
expect(devices[0]).toHaveText('usb1');
expect(devices[1]).toHaveText('gpu1');
expect(deviceRows).toHaveLength(2);
expect(deviceRows[0]).toHaveText('usb1');
expect(deviceRows[1]).toHaveText('gpu1');
});

it('deletes a device with confirmation and reloads the list when delete icon is pressed', async () => {
const deleteIcon = await loader.getHarness(IxIconHarness.with({ name: 'mdi-close' }));
await deleteIcon.click();

expect(spectator.inject(DialogService).confirm).toHaveBeenCalled();
expect(spectator.inject(ApiService).call).toHaveBeenCalledWith('virt.instance.device_delete', ['my-instance', 'usb1']);
expect(spectator.inject(VirtualizationInstancesStore).loadDevices).toHaveBeenCalled();
it('renders a button to delete the device', () => {
const deleteButtons = spectator.queryAll(DeleteDeviceButtonComponent);
expect(deleteButtons).toHaveLength(2);
expect(deleteButtons[0].device).toBe(devices[0]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,19 @@ import {
} from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatCard, MatCardContent, MatCardHeader } from '@angular/material/card';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { UntilDestroy } from '@ngneat/until-destroy';
import { TranslateModule } from '@ngx-translate/core';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import {
EMPTY, Observable, switchMap, tap,
} from 'rxjs';
import { VirtualizationDeviceType, virtualizationDeviceTypeLabels } from 'app/enums/virtualization.enum';
import {
VirtualizationDevice,
} from 'app/interfaces/virtualization.interface';
import { DialogService } from 'app/modules/dialog/dialog.service';
import { IxIconComponent } from 'app/modules/ix-icon/ix-icon.component';
import { AppLoaderService } from 'app/modules/loader/app-loader.service';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import { TestDirective } from 'app/modules/test-id/test.directive';
import {
DeleteDeviceButtonComponent,
} from 'app/pages/virtualization/components/common/delete-device-button/delete-device-button.component';
import { VirtualizationInstancesStore } from 'app/pages/virtualization/stores/virtualization-instances.store';
import { ApiService } from 'app/services/api.service';
import { ErrorHandlerService } from 'app/services/error-handler.service';

@UntilDestroy()
@Component({
Expand All @@ -38,6 +33,7 @@ import { ErrorHandlerService } from 'app/services/error-handler.service';
MatIconButton,
TestDirective,
IxIconComponent,
DeleteDeviceButtonComponent,
],
})
export class InstanceDevicesComponent {
Expand All @@ -51,12 +47,6 @@ export class InstanceDevicesComponent {

constructor(
private instanceStore: VirtualizationInstancesStore,
private dialog: DialogService,
private translate: TranslateService,
private snackbar: SnackbarService,
private ws: ApiService,
private loader: AppLoaderService,
private errorHandler: ErrorHandlerService,
) {}

protected getDeviceDescription(device: VirtualizationDevice): string {
Expand All @@ -69,33 +59,4 @@ export class InstanceDevicesComponent {

return `${type}: ${description}`;
}

protected deleteProxyPressed(device: VirtualizationDevice): void {
this.dialog.confirm({
message: this.translate.instant('Are you sure you want to delete this device?'),
title: this.translate.instant('Delete Device'),
})
.pipe(
switchMap((confirmed) => {
if (!confirmed) {
return EMPTY;
}

return this.deleteDevice(device);
}),
untilDestroyed(this),
)
.subscribe();
}

private deleteDevice(proxy: VirtualizationDevice): Observable<unknown> {
return this.ws.call('virt.instance.device_delete', [this.instanceStore.selectedInstance().id, proxy.name]).pipe(
this.loader.withLoader(),
this.errorHandler.catchError(),
tap(() => {
this.snackbar.success(this.translate.instant('Device deleted'));
this.instanceStore.loadDevices();
}),
);
}
}
Loading

0 comments on commit 92ec97a

Please sign in to comment.