Skip to content

Commit

Permalink
NAS-133236 / 25.04 / Add VM support for instances (#11258)
Browse files Browse the repository at this point in the history
  • Loading branch information
denysbutenko authored Jan 6, 2025
1 parent 2ec11c8 commit bb49427
Show file tree
Hide file tree
Showing 108 changed files with 702 additions and 212 deletions.
20 changes: 20 additions & 0 deletions src/app/enums/virtualization.enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { marker as T } from '@biesbjerg/ngx-translate-extract-marker';
import { iconMarker } from 'app/modules/ix-icon/icon-marker.util';

export enum VirtualizationType {
Container = 'CONTAINER',
Expand All @@ -10,6 +11,21 @@ export const virtualizationTypeLabels = new Map<VirtualizationType, string>([
[VirtualizationType.Vm, T('VM')],
]);

export const virtualizationTypeIcons = [
{
value: VirtualizationType.Container,
icon: iconMarker('mdi-linux'),
label: T('Container'),
description: T('Linux Only'),
},
{
value: VirtualizationType.Vm,
icon: iconMarker('mdi-laptop'),
label: T('VM'),
description: T('Any OS'),
},
];

export enum VirtualizationStatus {
Running = 'RUNNING',
Stopped = 'STOPPED',
Expand Down Expand Up @@ -82,3 +98,7 @@ export const virtualizationNicTypeLabels = new Map<VirtualizationNicType, string
[VirtualizationNicType.Bridged, T('Bridged Adaptors')],
[VirtualizationNicType.Macvlan, T('MAC VLAN')],
]);

export enum VirtualizationSource {
Image = 'IMAGE',
}
4 changes: 2 additions & 2 deletions src/app/interfaces/api/api-call-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ProductType } from 'app/enums/product-type.enum';
import { RdmaProtocolName, ServiceName } from 'app/enums/service-name.enum';
import { SmbInfoLevel } from 'app/enums/smb-info-level.enum';
import { TransportMode } from 'app/enums/transport-mode.enum';
import { VirtualizationGpuType, VirtualizationNicType, VirtualizationType } from 'app/enums/virtualization.enum';
import { VirtualizationGpuType, VirtualizationNicType } from 'app/enums/virtualization.enum';
import {
Acl,
AclQueryParams,
Expand Down Expand Up @@ -864,7 +864,7 @@ export interface ApiCallDirectory {

'virt.device.disk_choices': { params: []; response: Choices };
'virt.device.gpu_choices': {
params: [instanceType: VirtualizationType, gpuType: VirtualizationGpuType];
params: [gpuType: VirtualizationGpuType];
response: AvailableGpus;
};
'virt.device.usb_choices': { params: []; response: Record<string, AvailableUsb> };
Expand Down
11 changes: 11 additions & 0 deletions src/app/interfaces/virtualization.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
VirtualizationNicType,
VirtualizationProxyProtocol,
VirtualizationRemote,
VirtualizationSource,
VirtualizationStatus,
VirtualizationType,
} from 'app/enums/virtualization.enum';
Expand Down Expand Up @@ -49,11 +50,20 @@ export interface CreateVirtualizationInstance {
image: string;
remote: VirtualizationRemote;
instance_type: VirtualizationType;
source_type?: VirtualizationSource;
environment?: Record<string, string>;
autostart?: boolean;
cpu: string;
/**
* Value must be greater or equal to 33554432
*/
memory: number;
devices: VirtualizationDevice[];
enable_vnc?: boolean;
/**
* Value must be greater or equal to 5900 and lesser or equal to 65535
*/
vnc_port?: number | null;
}

export interface UpdateVirtualizationInstance {
Expand Down Expand Up @@ -148,6 +158,7 @@ export interface VirtualizationImage {
os: string;
release: string;
variant: string;
instance_types: VirtualizationType[];
}

export interface VirtualizationStopParams {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface IconGroupOption {
icon: MarkedIcon;
label: string;
value: string;
description?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,37 @@
></ix-label>
}

<div class="icon-group" role="radiogroup" [attr.aria-label]="label()">
<div
class="icon-group"
role="radiogroup"
[attr.aria-label]="label()"
[class.with-labels]="showLabels()"
>
@for (option of options(); track option) {
<button
mat-icon-button
role="radio"
type="button"
[disabled]="isDisabled"
[ixTest]="[controlDirective.name, option.label]"
[attr.aria-label]="option.label | translate"
[attr.data-value]="option.value"
[attr.aria-checked]="value === option.value"
[class.selected]="value === option.value"
(click)="onValueChanged(option.value)"
(blur)="onTouch()"
>
<ix-icon [name]="option.icon"></ix-icon>
</button>
<div>
<button
mat-icon-button
role="radio"
type="button"
[disabled]="isDisabled"
[ixTest]="[controlDirective.name, option.label]"
[attr.aria-label]="option.label | translate"
[attr.data-value]="option.value"
[attr.aria-checked]="value === option.value"
[class.selected]="value === option.value"
(click)="onValueChanged(option.value)"
(blur)="onTouch()"
>
<ix-icon [name]="option.icon"></ix-icon>
</button>

@if (showLabels()) {
<h5 class="title">{{ option.label | translate }}</h5>
@if (option.description) {
<small class="description">{{ option.description | translate }}</small>
}
}
</div>
} @empty {
<span>{{ 'No options are passed' | translate }}</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,51 @@
.selected {
color: var(--primary);
}

.title,
.description {
display: block;
margin: 0;
text-align: center;
}

.title {
font-size: 14px;
margin-bottom: 2px;
margin-top: 8px;
}

.description {
color: var(--fg2);
}

.with-labels {
gap: 16px;

::ng-deep .mdc-icon-button {
border: 2px solid var(--lines);
border-radius: 0;
height: 100px !important;
line-height: 100px;
width: 100px !important;

.mdc-icon-button__ripple,
.mat-ripple {
border-radius: 0 !important;
height: 100px !important;
width: 100px !important;
}

.ix-icon,
.ix-icon svg {
font-size: 40px;
height: 40px;
line-height: 1;
width: 40px;
}

&.selected {
border-color: var(--primary);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('IxIconGroupComponent', () => {
[tooltip]="tooltip"
[required]="required"
[formControl]="formControl"
[showLabels]="true"
></ix-icon-group>`,
{
hostProps: {
Expand Down Expand Up @@ -85,6 +86,14 @@ describe('IxIconGroupComponent', () => {
formControl.setValue('edit');
expect(await iconGroupHarness.getValue()).toBe('edit');
});
it('shows labels when `showLabels` is set to true', async () => {
const icons = await iconGroupHarness.getIcons();
expect(icons).toHaveLength(2);

const labels = spectator.queryAll('h5.title').map((el) => el.textContent);
expect(labels[0]).toBe('Edit');
expect(labels[1]).toBe('Delete');
});
});

it('updates form control value when user presses the button', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import { TestDirective } from 'app/modules/test-id/test.directive';
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
IxLabelComponent,
MatIconButton,
IxIconComponent,
IxErrorsComponent,
IxIconComponent,
IxLabelComponent,
ReactiveFormsModule,
TranslateModule,
TestDirective,
TranslateModule,
MatIconButton,
],
hostDirectives: [
{ ...registeredDirectiveConfig },
Expand All @@ -37,6 +37,7 @@ export class IxIconGroupComponent implements ControlValueAccessor {
readonly label = input<string>();
readonly tooltip = input<string>();
readonly required = input<boolean>(false);
readonly showLabels = input<boolean>(false);

protected isDisabled = false;
protected value: IconGroupOption['value'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

@if (dataset().vms?.length) {
<ix-icon
name="computer"
name="mdi-laptop"
[matTooltip]="'This dataset is used by: {vms}' | translate: { vms: vmNames() }"
></ix-icon>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('DatasetRolesCellComponent', () => {
it('shows "VM" icon and tooltip when dataset has vms', async () => {
await setupTest({ name: 'root', vms: [{ name: 'vm1', path: '' }, { name: 'vm1', path: '' }, { name: 'vm2', path: '' }] } as DatasetDetails, false);

expect(await ixIcon.getName()).toBe('computer');
expect(await ixIcon.getName()).toBe('mdi-laptop');
expect(spectator.query(MatTooltip)!.message).toBe('This dataset is used by: vm1, vm2');
});

Expand All @@ -71,7 +71,7 @@ describe('DatasetRolesCellComponent', () => {
it('shows "VM" icon when dataset has VMs', async () => {
await setupTest({ name: 'root', vms: [{}] } as DatasetDetails, false);

expect(await ixIcon.getName()).toBe('computer');
expect(await ixIcon.getName()).toBe('mdi-laptop');
});

it('shows "SMB Share" icon for dataset', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h3 mat-card-title>{{ 'Roles' | translate }}</h3>
}
@if (dataset().vms?.length) {
<div class="details-item">
<ix-icon class="icon" name="computer"></ix-icon>
<ix-icon class="icon" name="mdi-laptop"></ix-icon>
<div class="label">{{ 'VM' | translate }}:</div>
<div class="vms value">
{{ 'This dataset is used by: {vms}' | translate: { vms: vmNames() } }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { pickBy } from 'lodash-es';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { VirtualizationDeviceType, VirtualizationGpuType, VirtualizationType } from 'app/enums/virtualization.enum';
import { VirtualizationDeviceType, VirtualizationGpuType } from 'app/enums/virtualization.enum';
import {
AvailableUsb,
VirtualizationDevice,
Expand Down Expand Up @@ -42,7 +42,7 @@ import { ErrorHandlerService } from 'app/services/error-handler.service';
export class AddDeviceMenuComponent {
private readonly usbChoices = toSignal(this.api.call('virt.device.usb_choices'), { initialValue: {} });
// TODO: Stop hardcoding params
private readonly gpuChoices = toSignal(this.api.call('virt.device.gpu_choices', [VirtualizationType.Container, VirtualizationGpuType.Physical]), { initialValue: {} });
private readonly gpuChoices = toSignal(this.api.call('virt.device.gpu_choices', [VirtualizationGpuType.Physical]), { initialValue: {} });

protected readonly isLoadingDevices = this.deviceStore.isLoading;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
[required]="true"
></ix-input>

<ix-icon-group
formControlName="instance_type"
[label]="'Virtualization Method' | translate"
[required]="true"
[options]="virtualizationTypeIcons"
[showLabels]="true"
></ix-icon-group>

<div class="image-field">
<ix-input
class="input"
Expand Down Expand Up @@ -224,34 +232,32 @@
}
</ix-form-section>

@if ((usbDevices$ | async); as usbDevices) {
@if (usbDevices.length > 0) {
<ix-form-section
@let usbDevices = usbDevices$ | async;
@if (usbDevices?.length > 0) {
<ix-form-section
[label]="'USB Devices' | translate"
[help]="'USB Devices' | translate"
>
<ix-checkbox-list
formControlName="usb_devices"
[label]="'USB Devices' | translate"
[help]="'USB Devices' | translate"
>
<ix-checkbox-list
[formControlName]="'usb_devices'"
[label]="'USB Devices' | translate"
[options]="usbDevices$"
></ix-checkbox-list>
</ix-form-section>
}
[options]="usbDevices$"
></ix-checkbox-list>
</ix-form-section>
}

@if ((gpuDevices$ | async); as gpuDevices) {
@if (gpuDevices.length > 0) {
<ix-form-section
@let gpuDevices = gpuDevices$ | async;
@if (gpuDevices?.length > 0) {
<ix-form-section
[label]="'GPU Devices' | translate"
[help]="'GPU Devices' | translate"
>
<ix-checkbox-list
formControlName="gpu_devices"
[label]="'GPU Devices' | translate"
[help]="'GPU Devices' | translate"
>
<ix-checkbox-list
formControlName="gpu_devices"
[label]="'GPU Devices' | translate"
[options]="gpuDevices$"
></ix-checkbox-list>
</ix-form-section>
}
[options]="gpuDevices$"
></ix-checkbox-list>
</ix-form-section>
}

<div class="actions">
Expand Down
Loading

0 comments on commit bb49427

Please sign in to comment.