Skip to content

Commit

Permalink
NAS-133745: VNC change allowed when instance is running (#11377)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored Jan 24, 2025
1 parent f3b22ed commit 0176b03
Show file tree
Hide file tree
Showing 93 changed files with 204 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ describe('AddDeviceMenuComponent', () => {
await menu.open();

const menuItems = await menu.getItems();
expect(menuItems).toHaveLength(2);
expect(menuItems).toHaveLength(3);
expect(await menuItems[0].getText()).toContain('Card Reader');
expect(await menuItems[1].getText()).toContain('MAD Galeon 5000');
expect(await menuItems[2].getText()).toContain('Add Trusted Platform Module');
});

it('adds a usb device when it is selected', async () => {
Expand Down Expand Up @@ -109,21 +110,18 @@ describe('AddDeviceMenuComponent', () => {
});

describe('TPM', () => {
it('allows TPM to be added to VMs if it has not been added before', async () => {
it('allows TPM to be added if it has not been added before', async () => {
const menu = await loader.getHarness(MatMenuHarness.with({ triggerText: 'Add' }));
await menu.open();

const menuItems = await menu.getItems({ text: 'Add Trusted Platform Module' });
expect(menuItems).toHaveLength(0);

selectedInstance.set({
id: 'my-instance',
status: VirtualizationStatus.Stopped,
type: VirtualizationType.Vm,
});

const newMenuItems = await menu.getItems({ text: 'Add Trusted Platform Module' });
expect(newMenuItems).toHaveLength(1);
const menuItems = await menu.getItems({ text: 'Add Trusted Platform Module' });
expect(menuItems).toHaveLength(1);
});

it('adds a TPM module when the corresponding option is selected', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
VirtualizationDeviceType,
VirtualizationGpuType,
VirtualizationStatus,
VirtualizationType,
} from 'app/enums/virtualization.enum';
import {
AvailableUsb,
Expand Down Expand Up @@ -75,8 +74,7 @@ export class AddDeviceMenuComponent {
});

protected canAddTpm = computed(() => {
return this.deviceStore.selectedInstance().type === VirtualizationType.Vm
&& !this.deviceStore.devices().some((device) => device.dev_type === VirtualizationDeviceType.Tpm);
return !this.deviceStore.devices().some((device) => device.dev_type === VirtualizationDeviceType.Tpm);
});

protected canAddTpmNow = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@
></ix-checkbox>
</ix-fieldset>

@if (isVmInstanceType) {
<ix-fieldset [title]="'CPU & Memory' | translate">
<ix-input
formControlName="cpu"
[label]="'CPU Configuration' | translate"
[hint]="containersHelptext.cpuHint | translate"
></ix-input>

<ix-input
formControlName="memory"
[label]="'Memory Size' | translate"
[format]="formatter.memorySizeFormatting"
[parse]="formatter.memorySizeParsing"
[hint]="containersHelptext.memoryHint | translate"
></ix-input>
</ix-fieldset>

@if (isVm) {
<ix-fieldset [title]="'VNC' | translate">

<ix-checkbox
formControlName="enable_vnc"
[label]="'Enable VNC' | translate"
[matTooltip]="isStopped ? '' : ('Instance must be stopped to update VNC.' | translate)"
></ix-checkbox>

@if (form.value.enable_vnc) {
Expand All @@ -28,22 +45,6 @@
</ix-fieldset>
}

<ix-fieldset [title]="'CPU & Memory' | translate">
<ix-input
formControlName="cpu"
[label]="'CPU Configuration' | translate"
[hint]="containersHelptext.cpuHint | translate"
></ix-input>

<ix-input
formControlName="memory"
[label]="'Memory Size' | translate"
[format]="formatter.memorySizeFormatting"
[parse]="formatter.memorySizeParsing"
[hint]="containersHelptext.memoryHint | translate"
></ix-input>
</ix-fieldset>

<ix-fieldset [title]="'Environment' | translate">
<ix-list
formArrayName="environmentVariables"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import {
createComponentFactory,
mockProvider,
Spectator,
} from '@ngneat/spectator/jest';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { GiB } from 'app/constants/bytes.constant';
import { fakeSuccessfulJob } from 'app/core/testing/utils/fake-job.utils';
import { mockJob, mockApi } from 'app/core/testing/utils/mock-api.utils';
import { mockApi, mockJob } from 'app/core/testing/utils/mock-api.utils';
import { mockAuth } from 'app/core/testing/utils/mock-auth.utils';
import { VirtualizationType } from 'app/enums/virtualization.enum';
import { VirtualizationStatus, VirtualizationType } from 'app/enums/virtualization.enum';
import { Job } from 'app/interfaces/job.interface';
import { VirtualizationInstance } from 'app/interfaces/virtualization.interface';
import { DialogService } from 'app/modules/dialog/dialog.service';
import { IxFormHarness } from 'app/modules/forms/ix-forms/testing/ix-form.harness';
import { SlideInRef } from 'app/modules/slide-ins/slide-in-ref';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import { ApiService } from 'app/modules/websocket/api.service';
import { InstanceEditFormComponent } from 'app/pages/virtualization/components/all-instances/instance-details/instance-general-info/instance-edit-form/instance-edit-form.component';
import {
InstanceEditFormComponent,
} from 'app/pages/virtualization/components/all-instances/instance-details/instance-general-info/instance-edit-form/instance-edit-form.component';

describe('InstanceEditFormComponent', () => {
let spectator: Spectator<InstanceEditFormComponent>;
Expand All @@ -37,6 +35,7 @@ describe('InstanceEditFormComponent', () => {
type: VirtualizationType.Vm,
vnc_enabled: true,
vnc_port: 9001,
status: VirtualizationStatus.Stopped,
} as VirtualizationInstance;

const createComponent = createComponentFactory({
Expand Down Expand Up @@ -73,53 +72,74 @@ describe('InstanceEditFormComponent', () => {
],
});

beforeEach(async () => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
form = await loader.getHarness(IxFormHarness);
});

it('loads instance data in edit mode and populates the form', async () => {
expect(await form.getValues()).toMatchObject({
Autostart: false,
'CPU Configuration': '1-3',
'Memory Size': '2 GiB',
'VNC Port': '9001',
describe('normal form operations', () => {
beforeEach(async () => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
form = await loader.getHarness(IxFormHarness);
});
});

it('updates an instance when form is submitted', async () => {
await form.fillForm({
Autostart: true,
'CPU Configuration': '2-5',
'Memory Size': '1 GiB',
'VNC Port': 9000,
it('loads instance data in edit mode and populates the form', async () => {
expect(await form.getValues()).toMatchObject({
Autostart: false,
'CPU Configuration': '1-3',
'Memory Size': '2 GiB',
'VNC Port': '9001',
});
});

const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
await saveButton.click();
it('updates an instance when form is submitted', async () => {
await form.fillForm({
Autostart: true,
'CPU Configuration': '2-5',
'Memory Size': '1 GiB',
'VNC Port': 9000,
});

expect(spectator.inject(ApiService).job).toHaveBeenCalledWith('virt.instance.update', ['test', {
autostart: true,
cpu: '2-5',
memory: GiB,
environment: {},
enable_vnc: true,
vnc_port: 9000,
}]);
expect(spectator.inject(DialogService).jobDialog).toHaveBeenCalled();
expect(spectator.inject(SnackbarService).success).toHaveBeenCalled();
expect(spectator.inject(SlideInRef).close).toHaveBeenCalledWith({
response: {
id: 'updated_instance',
const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
await saveButton.click();

expect(spectator.inject(ApiService).job).toHaveBeenCalledWith('virt.instance.update', ['test', {
autostart: true,
cpu: '2-5',
memory: GiB,
environment: {},
enable_vnc: true,
vnc_port: 9000,
},
error: false,
}]);
expect(spectator.inject(DialogService).jobDialog).toHaveBeenCalled();
expect(spectator.inject(SnackbarService).success).toHaveBeenCalled();
expect(spectator.inject(SlideInRef).close).toHaveBeenCalledWith({
response: {
id: 'updated_instance',
autostart: true,
cpu: '2-5',
memory: GiB,
environment: {},
enable_vnc: true,
vnc_port: 9000,
},
error: false,
});
});
});

it('marks Enable VNC as disabled when instance is not stopped', async () => {
spectator = createComponent({
providers: [
mockProvider(SlideInRef, {
getData: () => ({
...mockInstance,
status: VirtualizationStatus.Running,
}),
requireConfirmationWhen: jest.fn(),
close: jest.fn(),
}),
],
});
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
form = await loader.getHarness(IxFormHarness);

expect(await (await form.getControl('Enable VNC')).isDisabled()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
FormBuilder, ReactiveFormsModule, Validators,
} from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { of } from 'rxjs';
import { Role } from 'app/enums/role.enum';
import { VirtualizationType } from 'app/enums/virtualization.enum';
import { VirtualizationStatus, VirtualizationType } from 'app/enums/virtualization.enum';
import { containersHelptext } from 'app/helptext/virtualization/containers';
import {
InstanceEnvVariablesFormGroup,
Expand Down Expand Up @@ -48,6 +49,7 @@ import { defaultVncPort } from 'app/pages/virtualization/virtualization.constant
IxFieldsetComponent,
IxListComponent,
IxListItemComponent,
MatTooltip,
],
templateUrl: './instance-edit-form.component.html',
styleUrls: ['./instance-edit-form.component.scss'],
Expand All @@ -60,10 +62,16 @@ export class InstanceEditFormComponent {
title: string;
editingInstance: VirtualizationInstance;

get isVmInstanceType(): boolean {
protected readonly containersHelptext = containersHelptext;

get isVm(): boolean {
return this.editingInstance.type === VirtualizationType.Vm;
}

get isStopped(): boolean {
return this.editingInstance.status === VirtualizationStatus.Stopped;
}

protected readonly form = this.formBuilder.nonNullable.group({
autostart: [false],
cpu: ['', [cpuValidator()]],
Expand All @@ -87,16 +95,8 @@ export class InstanceEditFormComponent {
return of(this.form.dirty);
});

this.form.controls.vnc_port.disable();
this.form.controls.enable_vnc.valueChanges.pipe(untilDestroyed(this)).subscribe((vncEnabled) => {
if (vncEnabled) {
this.form.controls.vnc_port.enable();
} else {
this.form.controls.vnc_port.disable();
}
});

this.editingInstance = this.slideInRef.getData();

this.title = this.translate.instant('Edit Instance: {name}', { name: this.editingInstance.name });
this.form.patchValue({
cpu: this.editingInstance.cpu,
Expand All @@ -106,6 +106,8 @@ export class InstanceEditFormComponent {
vnc_port: this.editingInstance.vnc_port,
});

this.setVncControls();

Object.keys(this.editingInstance.environment || {}).forEach((key) => {
this.addEnvironmentVariable(key, this.editingInstance.environment[key]);
});
Expand Down Expand Up @@ -145,7 +147,7 @@ export class InstanceEditFormComponent {
}

private getSubmissionPayload(): UpdateVirtualizationInstance {
const values = this.form.value;
const values = this.form.getRawValue();

return {
environment: this.environmentVariablesPayload,
Expand All @@ -169,5 +171,18 @@ export class InstanceEditFormComponent {
}, {});
}

protected readonly containersHelptext = containersHelptext;
private setVncControls(): void {
this.form.controls.vnc_port.disable();
this.form.controls.enable_vnc.valueChanges.pipe(untilDestroyed(this)).subscribe((vncEnabled) => {
if (vncEnabled) {
this.form.controls.vnc_port.enable();
} else {
this.form.controls.vnc_port.disable();
}
});

if (!this.isStopped) {
this.form.controls.enable_vnc.disable();
}
}
}
1 change: 1 addition & 0 deletions src/assets/i18n/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@
"Instance created": "",
"Instance is not running": "",
"Instance must be stopped to add TPM.": "",
"Instance must be stopped to update VNC.": "",
"Instance restarted": "",
"Instance started": "",
"Instance stopped": "",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@
"Instance created": "",
"Instance is not running": "",
"Instance must be stopped to add TPM.": "",
"Instance must be stopped to update VNC.": "",
"Instance restarted": "",
"Instance started": "",
"Instance stopped": "",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@
"Instance created": "",
"Instance is not running": "",
"Instance must be stopped to add TPM.": "",
"Instance must be stopped to update VNC.": "",
"Instance restarted": "",
"Instance started": "",
"Instance stopped": "",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/az.json
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@
"Instance created": "",
"Instance is not running": "",
"Instance must be stopped to add TPM.": "",
"Instance must be stopped to update VNC.": "",
"Instance restarted": "",
"Instance started": "",
"Instance stopped": "",
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/be.json
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@
"Instance created": "",
"Instance is not running": "",
"Instance must be stopped to add TPM.": "",
"Instance must be stopped to update VNC.": "",
"Instance restarted": "",
"Instance started": "",
"Instance stopped": "",
Expand Down
Loading

0 comments on commit 0176b03

Please sign in to comment.