Skip to content

Commit

Permalink
NAS-132301 / 25.04 / Adapt changes for boot environments (#11012)
Browse files Browse the repository at this point in the history
* NAS-132301: Adapt changes for boot environments

* NAS-132301: Adapt changes for boot environments

* NAS-132301: Adapt changes for boot environments

* NAS-132301: Adapt changes for boot environments

* NAS-132301: Adapt changes for boot environments

* NAS-132301: Update tests

* NAS-132301: Remove legacy eslint settings

* NAS-132301: Update tests
  • Loading branch information
denysbutenko authored Nov 15, 2024
1 parent cd50b8d commit 1f6c5cd
Show file tree
Hide file tree
Showing 108 changed files with 228 additions and 775 deletions.
4 changes: 0 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"vsicons.presets.angular": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.preferences.importModuleSpecifier": "non-relative",
"eslint.options": {
"extensions": [".ts", ".html"]
},
"eslint.validate": [
"typescript",
"html"
Expand Down
6 changes: 0 additions & 6 deletions src/app/enums/boot-environment-action.enum.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/app/enums/boot-environment-active.enum.ts

This file was deleted.

19 changes: 7 additions & 12 deletions src/app/interfaces/api/api-call-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ import { AuditConfig, AuditEntry, AuditQueryParams } from 'app/interfaces/audit/
import { AuthSession } from 'app/interfaces/auth-session.interface';
import { LoginExOtpTokenQuery, LoginExQuery, LoginExResponse } from 'app/interfaces/auth.interface';
import { AvailableApp } from 'app/interfaces/available-app.interface';
import {
Bootenv,
CreateBootenvParams,
SetBootenvAttributeParams,
UpdateBootenvParams,
} from 'app/interfaces/bootenv.interface';
import { BootenvCloneParams, BootEnvironment, BootenvKeepParams } from 'app/interfaces/boot-environment.interface';
import {
CatalogConfig, CatalogApp,
CatalogUpdate, GetItemDetailsParams,
Expand Down Expand Up @@ -350,12 +345,12 @@ export interface ApiCallDirectory {
'boot.get_state': { params: void; response: PoolInstance };
'boot.set_scrub_interval': { params: [number]; response: number };

// Bootenv
'bootenv.activate': { params: [string]; response: boolean };
'bootenv.create': { params: CreateBootenvParams; response: string };
'bootenv.query': { params: QueryParams<Bootenv>; response: Bootenv[] };
'bootenv.set_attribute': { params: SetBootenvAttributeParams; response: boolean };
'bootenv.update': { params: UpdateBootenvParams; response: string };
// Boot Environment
'boot.environment.query': { params: QueryParams<BootEnvironment>; response: BootEnvironment[] };
'boot.environment.activate': { params: [{ id: string }]; response: unknown };
'boot.environment.destroy': { params: [{ id: string }]; response: unknown };
'boot.environment.clone': { params: BootenvCloneParams; response: unknown };
'boot.environment.keep': { params: BootenvKeepParams; response: unknown };

// Catalog
'catalog.get_app_details': { params: [name: string, params: GetItemDetailsParams]; response: CatalogApp };
Expand Down
2 changes: 2 additions & 0 deletions src/app/interfaces/api/api-event-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DockerStatusData } from 'app/enums/docker-config.interface';
import { FailoverStatus } from 'app/enums/failover-status.enum';
import { Alert } from 'app/interfaces/alert.interface';
import { App, AppStats } from 'app/interfaces/app.interface';
import { BootEnvironment } from 'app/interfaces/boot-environment.interface';
import { ContainerImage } from 'app/interfaces/container-image.interface';
import { DirectoryServicesState } from 'app/interfaces/directory-services-state.interface';
import { Disk } from 'app/interfaces/disk.interface';
Expand Down Expand Up @@ -43,4 +44,5 @@ export interface ApiEventDirectory {
'vm.query': { response: VirtualMachine };
'zfs.pool.scan': { response: PoolScan };
'zfs.snapshot.query': { response: ZfsSnapshot };
'boot.environment.query': { response: BootEnvironment };
}
59 changes: 59 additions & 0 deletions src/app/interfaces/boot-environment.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ApiTimestamp } from 'app/interfaces/api-date.interface';

export interface BootEnvironment {
/**
* The name of the boot environment.
*/
id: string;

/**
* The name of the dataset representing the boot environment.
*/
dataset: string;

/**
* If `true`, represents the `currently` running boot environment.
*/
active: boolean;

/**
* If `true`, represents the boot environment that will become the running boot environment at `next boot`.
*/
activated: boolean;

/**
* Represents when the boot environment was created.
*/
created: ApiTimestamp;

/**
* An integer representing the number of bytes used by the boot environment.
*/
used_bytes: number;

/**
* A human-readable string representing the total number of space used by the boot environment.
*/
used: string;

/**
* When set to `false`, will be automatically deleted during an upgrade procedure
* if there isn’t enough room on the boot drive to apply said update.
*/
keep: boolean;

/**
* If `true` indicates whether the boot environment may be `activated`.
*/
can_activate: boolean;
}

export type BootenvCloneParams = [{
id: string;
target: string;
}];

export type BootenvKeepParams = [{
id: string;
value: boolean;
}];
36 changes: 0 additions & 36 deletions src/app/interfaces/bootenv.interface.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<h1 matDialogTitle>{{ 'Delete' | translate }}</h1>
<form class="ix-form-container" [formGroup]="form" (submit)="onSubmit()">
@if (!isJobCompleted) {
@if (!isJobCompleted()) {
<p>
<strong>
{{ 'The following { n, plural, one {boot environment} other {# boot environments} } will be deleted. Are you sure you want to proceed?' | translate: { n: bulkItems.size } }}
Expand All @@ -23,17 +23,15 @@ <h1 matDialogTitle>{{ 'Delete' | translate }}</h1>

<div class="bulk-list">
@for (bulkItem of bulkItems | keyvalue; track trackByKey($index, bulkItem)) {
<ix-bulk-list-item
[item]="bulkItem.value"
>
<ix-bulk-list-item [item]="bulkItem.value">
<span>{{ bulkItem.value.item.id }}</span>
</ix-bulk-list-item>
}
</div>

<div class="form-actions">
<div class="form-actions-cell">
@if (!isJobCompleted) {
@if (!isJobCompleted()) {
<ix-checkbox
formControlName="confirm"
[label]="'Confirm' | translate"
Expand All @@ -44,7 +42,7 @@ <h1 matDialogTitle>{{ 'Delete' | translate }}</h1>

<div class="form-actions-cell">
<ng-container *ixRequiresRoles="requiredRoles">
@if (!isJobCompleted) {
@if (!isJobCompleted()) {
<button
mat-button
type="submit"
Expand All @@ -56,7 +54,7 @@ <h1 matDialogTitle>{{ 'Delete' | translate }}</h1>
</button>
}
</ng-container>
@if (!isJobCompleted) {
@if (!isJobCompleted()) {
<button
mat-button
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ describe('BootPoolDeleteDialogComponent', () => {

it('deletes selected boot environments when form is submitted', async () => {
const jobArguments = [
'bootenv.do_delete',
'boot.environment.destroy',
[
['CLONE'],
['22.12-MASTER-20220808-020013'],
[{ id: '25.04.0-MASTER-20241031-104807' }],
[{ id: '25.04.0-MASTER-20241105-224807' }],
],
];
spectator.inject(MockApiService).mockJob('core.bulk', fakeSuccessfulJob(mockSuccessBulkResponse, jobArguments));
Expand All @@ -92,10 +92,10 @@ describe('BootPoolDeleteDialogComponent', () => {

it('checks deleting failures of boot environments when form is submitted', async () => {
const jobArguments: CoreBulkQuery = [
'bootenv.do_delete',
'boot.environment.destroy',
[
['CLONE'],
['22.12-MASTER-20220808-020013'],
[{ id: '25.04.0-MASTER-20241031-104807' }],
[{ id: '25.04.0-MASTER-20241105-224807' }],
],
];
spectator.inject(MockApiService).mockJob('core.bulk', fakeSuccessfulJob(mockFailedBulkResponse, jobArguments));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KeyValue, KeyValuePipe } from '@angular/common';
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, TrackByFunction,
ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, signal, TrackByFunction,
} from '@angular/core';
import { Validators, FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButton } from '@angular/material/button';
Expand All @@ -12,7 +12,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { filter } from 'rxjs/operators';
import { RequiresRolesDirective } from 'app/directives/requires-roles/requires-roles.directive';
import { Role } from 'app/enums/role.enum';
import { Bootenv } from 'app/interfaces/bootenv.interface';
import { BootEnvironment } from 'app/interfaces/boot-environment.interface';
import { CoreBulkResponse } from 'app/interfaces/core-bulk.interface';
import { Job } from 'app/interfaces/job.interface';
import { IxCheckboxComponent } from 'app/modules/forms/ix-forms/components/ix-checkbox/ix-checkbox.component';
Expand Down Expand Up @@ -48,8 +48,8 @@ export class BootPoolDeleteDialogComponent {
confirm: [false, [Validators.requiredTrue]],
});

isJobCompleted = false;
bulkItems = new Map<string, BulkListItem<Bootenv>>();
isJobCompleted = signal(false);
bulkItems = new Map<string, BulkListItem<BootEnvironment>>();

get successCount(): number {
return [...this.bulkItems.values()].filter((item) => item.state === BulkListItemState.Success).length;
Expand All @@ -59,22 +59,22 @@ export class BootPoolDeleteDialogComponent {
return [...this.bulkItems.values()].filter((item) => item.state === BulkListItemState.Error).length;
}

readonly trackByKey: TrackByFunction<KeyValue<string, BulkListItem<Bootenv>>> = (_, entry) => entry.key;
readonly trackByKey: TrackByFunction<KeyValue<string, BulkListItem<BootEnvironment>>> = (_, entry) => entry.key;

constructor(
private fb: FormBuilder,
private ws: ApiService,
private cdr: ChangeDetectorRef,
private dialogRef: MatDialogRef<BootPoolDeleteDialogComponent>,
@Inject(MAT_DIALOG_DATA) public bootenvs: Bootenv[],
@Inject(MAT_DIALOG_DATA) public bootenvs: BootEnvironment[],
) {
this.bootenvs.forEach((bootenv) => {
this.bulkItems.set(bootenv.id, { state: BulkListItemState.Initial, item: bootenv });
});
}

getSelectedNames(selectedBootenvs: Bootenv[]): string[][] {
return selectedBootenvs.map((bootenv) => [bootenv.id]);
getSelectedNames(selectedBootenvs: BootEnvironment[]): { id: string }[][] {
return selectedBootenvs.map(({ id }) => [{ id }]);
}

onSubmit(): void {
Expand All @@ -84,12 +84,12 @@ export class BootPoolDeleteDialogComponent {
this.bulkItems.set(bootenv.id, { state: BulkListItemState.Running, item: bootenv });
});

this.ws.job('core.bulk', ['bootenv.do_delete', bootenvsToDelete]).pipe(
filter((job: Job<CoreBulkResponse<void>[], string[][]>) => !!job.result),
this.ws.job('core.bulk', ['boot.environment.destroy', bootenvsToDelete]).pipe(
filter((job: Job<CoreBulkResponse<void>[], { id: string }[][]>) => !!job.result?.length),
untilDestroyed(this),
).subscribe((response) => {
response.arguments[1].forEach((params, index: number) => {
const [bootenvId] = params.toString().split(',');
response.arguments[1].flat().forEach((params, index: number) => {
const bootenvId = params.id;
const bulkItem = this.bulkItems.get(bootenvId);
if (bulkItem) {
const item = response.result[index];
Expand All @@ -113,8 +113,7 @@ export class BootPoolDeleteDialogComponent {
}
}
});
this.isJobCompleted = true;
this.cdr.markForCheck();
this.isJobCompleted.set(true);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<ix-modal-header [requiredRoles]="requiredRoles" [title]="title" [loading]="isFormLoading"></ix-modal-header>
<ix-modal-header
[requiredRoles]="requiredRoles"
[title]="'Clone Boot Environment' | translate"
[loading]="isLoading()"
></ix-modal-header>

<mat-card>
<mat-card-content>
Expand All @@ -9,21 +13,18 @@
>
<ix-fieldset [title]="'Settings' | translate">
<ix-input
formControlName="name"
formControlName="target"
[label]="'Name' | translate"
[required]="true"
[tooltip]="tooltips.name"
></ix-input>

@if (operation === Operations.Clone) {
<ix-input
formControlName="source"
[label]="'Source' | translate"
[required]="true"
[tooltip]="tooltips.source"
[class]="Operations.Clone"
></ix-input>
}
<ix-input
formControlName="source"
[label]="'Source' | translate"
[required]="true"
[tooltip]="tooltips.source"
></ix-input>
</ix-fieldset>

<ix-form-actions>
Expand All @@ -33,7 +34,7 @@
type="submit"
color="primary"
ixTest="save"
[disabled]="formGroup.invalid || isFormLoading"
[disabled]="formGroup.invalid || isLoading()"
>
{{ 'Save' | translate }}
</button>
Expand Down
Loading

0 comments on commit 1f6c5cd

Please sign in to comment.