Unverified Commit b8f7350e authored by RehanY147's avatar RehanY147 Committed by GitHub
Browse files

NAS-115487 / 22.12 / SMB form refactor (#6605)

parent 8b6b0099
base DOCS DOCS-3538 NAS-010101 NAS-110800 NAS-111962-master NAS-112995-22.12 NAS-114179 NAS-114179-2 NAS-115546 NAS-115593-6 NAS-115593-7 NAS-115759-22.12 NAS-116151 NAS-116162 NAS-116334-1 NAS-116334-2 NAS-116393 NAS-116395 NAS-116397 NAS-116397-2 NAS-116397-3 NAS-116398 NAS-116405 NAS-116406 NAS-116410 NAS-116469 NAS-116715 NAS-116715-mobile NAS-116715-v2 NAS-116724 NAS-116915 NAS-116916 NAS-117017 NAS-117019 NAS-117028-22.12-BETA.2 NAS-117060 NAS-117098 NAS-117149-22.12 NAS-117216 NAS-117233 NAS-117239 NAS-117253 NAS-117278 NAS-117317 NAS-117323 NAS-117333 NAS-117401 NAS-117439 NAS-117475 NAS-117476 NAS-117481-test NAS-117520 NAS-117573-v2 NAS-117594 NAS-117628 NAS-117688-bluefin NAS-117700 NAS-117714 NAS-117718 NAS-117734 NAS-117768 NAS-117813 NAS-117823 NAS-117841-2 NAS-117846 NAS-117959 NAS-118036 NAS-118044 NAS-118113 NAS-118165 NAS-118303 NAS-118454-22.12 NAS-118505-22.12 NAS-118545 NAS-118548 NAS-119131 NAS-119140 NAS-119180-22.12.1 NAS-119431 NAS-119556-23.10 NAS-119615-22.12.1 NAS-119668 NAS-119695 NAS-119749-bluefin NAS-119750-22.12.1 NAS-119806 NAS-119812 NAS-119886-22.12.1 NAS-119996 NAS-119996-bluefin NAS-120045 NAS-120047 NAS-120057 NAS-120173-22.12.1 NAS-120181-22.12.1 NAS-120264-22.12.1 NAS-120274 NAS-120296-22.12.1 NAS-120326-22.12.1 NAS-120490_ NAS-120503 NAS-121006-22.12.2 NAS-121124 NAS-121128-22.12.2 NAS-121128-release-22.12.2 NAS-121136 NAS-121177 NAS-121218-22.12.3 NAS-121300 NAS-121316 NAS-121541 NAS-121542 NAS-121686 NAS-121721-22.12.3 NAS-121778 NAS-121827 NAS-121884-22.12.3 NAS-122267-22.12.4 NAS-122372 NAS-122601 NAS-122686 NAS-122706 NAS-122721 NAS-122751-23.10-BETA.1 NAS-122759 NAS-122781 NAS-122794-23.10 NAS-122855 NAS-122870-bluefin NAS-122969 NAS-122993-22.12.4 NAS-123055-22.12.4 NAS-123278 NAS-123295 NAS-123437 NAS-123478-22.12.4 NAS-123484 NAS-123492 NAS-123526-22.12.4 NAS-123651 NAS-123651-23.10-BETA.1 NAS-123666 NAS-123723 NAS-123723-cobia NAS-123762 NAS-123762-23.10 NAS-123778-23.10 NAS-123778-23.10-RC.1 NAS-123778-RC.1 NAS-123801 NAS-123810-23.10 NAS-123813-22.12.4 NAS-123836 NAS-123836-23.10-BETA.1 NAS-123836-24.04 NAS-123861 NAS-123911-23.10-BETA.1 NAS-123928-23.10 NAS-123931-22.12.4 NAS-123945 NAS-124044 NAS-124077 NAS-124131-23.10 NAS-124137 NAS-124183 NAS-124231 NAS-124232 NAS-124237-23.10 NAS-124325-23.10.0 NAS-124335 NAS-124354 NAS-124430 NAS-124454 NAS-124481-23.10.0 NAS-124481-23.10.1 NAS-124555-23.10.0 NAS-124555-23.10.1 NAS-124666 NAS-124707-23.10.0 NAS-124707-23.10.1 NAS-124716 NAS-124846-23.10.0 NAS-124846-23.10.1 NAS-124892 NAS-124895-23.10.1 NAS-124908 NAS-124951 NAS-124964 NAS-124999 NAS-125092 NAS-125213-23.10.1 NAS-125307 NAS-125532 NAS-125568 NAS-125607-validator NAS-125616-23.10.2 NAS-125654 NAS-125703-23.10.2 NAS-125728 NAS-125931 NAS-126699 NAS-126774-24.04-RC.1 NAS-126774-dragonfish NAS-126795 NAS-126795-test NAS-126795-test2 NAS-127001 NAS-127002-24.04-RC.1 NAS-127022 NAS-127041-24.04-BETA.1 NAS-127049-24.04-RC.1 NAS-127297-24.04-RC.1 NAS-127297-24.10 NAS-127369 NAS-127551 NAS-127551-alt NAS-127589-24.04.0 NAS-127593 NAS-127615-24.04.0 NAS-127660 NAS-127794 NAS-127829-24.10 NAS-127854-24.04.0 NAS-128030 NAS-128045 NAS-128071 NAS-128173 NAS-128209 NAS-128287 NAS-128289 TE-1553-dragonfish TE-1628 auto-129 bugfix/NAS-117859-sidebar-menu-fix bugfix/NAS-117941-error-when-removing-pools-and-visit-datasets bugfix/NAS-118171-rsynk-task-local-path bugfix/NAS-118260-boot-env-keep-table-row bugfix/NAS-118282-search-input-fixes bugfix/NAS-118404-dataset-icon-role-double-toooltip bugfix/NAS-118414-warning-modal-icon bugfix/NAS-118415-tree-select-undefined bugfix/NAS-118454-acl-manager-after-dataset-creation-fix bugfix/NAS-118470-multiselext-styles-are-broken bugfix/NAS-118503-datasets-glitch-fix bugfix/NAS-118504-redirect-to-correct-dataset-after-permissions-submit bugfix/NAS-118510-redirect-url-fix-after-manual-change bugfix/NAS-118530-advanced-settings-box-duplicates bugfix/NAS-118541-progress-bar-oberflows bugfix/NAS-118557-replication-task-forbid-custom-retention-policy-cases bugfix/NAS-118600-smb-share-redirect bugfix/NAS-118601-remove-mixed-for-zfs-datasets cpu-pinning dataset-tree-tooltips developer/lyy feature/NAS-117754-font-rendering feature/NAS-117968-tooltips-to-status-icons-on-pools feature/NAS-118058-improve-dashboard-icons-sync-pool-and-storage feature/NAS-118147-html-refactoring feature/NAS-118269-improve-ui feature/NAS-118303 feature/NAS-118333-storage-dashboard-icons-update feature/NAS-118334-screentype-enum feature/NAS-118335-improve-spinners-look feature/NAS-118349-datasets-long-names feature/NAS-118360-handle-clipboard-api-not-available feature/NAS-118412-pool-processing-modal feature/NAS-118466-root-path-mnt feature/NAS-118543-user-password-field feauture/NAS-117474-datasets-table-header-sticky l10n_master llll master metrics-enable patch-1 patch-235 rel-v0.0.1 release/22.12 release/22.12-BETA.1 release/22.12-BETA.2 release/22.12-RC.1 release/22.12.1 release/22.12.2 release/22.12.3 release/22.12.4 release/23.10-BETA.1 release/23.10-RC.1 release/23.10.0 release/23.10.1 release/23.10.1.1 release/23.10.1.2 release/23.10.1.3 release/23.10.2 release/24.04-BETA.1 release/24.04-RC.1 release/24.04.0 renediepenbroek/master revert-6783-NAS-116405 revert-7745-NAS-120274 stable/bluefin stable/cobia stable/dragonfish test-xxxyyy testing-refine-branchout-process testing-refine-branchout-process2 v0.0.2 TS-24.04-RC.1 TS-24.04-BETA.1 TS-23.10.2 TS-23.10.1.3 TS-23.10.1.2 TS-23.10.1.1 TS-23.10.1 TS-23.10.0.1 TS-23.10.0 TS-23.10-RC.1 TS-23.10-BETA.1 TS-22.12.4.2 TS-22.12.4.1 TS-22.12.4 TS-22.12.3.3 TS-22.12.3.2 TS-22.12.3.1 TS-22.12.3 TS-22.12.2 TS-22.12.1 TS-22.12.0 TS-22.12-RC.1 TS-22.12-BETA.2 TS-22.12-BETA.1 TS-22.12-ALPHA.1 TS-12.12.3 DN110M-CS-v2.0
No related merge requests found
Showing with 1144 additions and 690 deletions
+1144 -690
......@@ -220,4 +220,6 @@ export const helptextSharingSmb = {
action: T('I Understand'),
},
restartPt1: T('The following changes to this SMB Share require the SMB Service to be restarted before they can take effect.'),
restartPt2: T('Would you like to restart the SMB Service?'),
};
......@@ -20,7 +20,7 @@ export interface SmbShare {
name: string;
path: string;
path_suffix: string;
purpose: string; // Possibly enum: "DEFAULT_SHARE"
purpose: SmbPresetType;
recyclebin: boolean;
ro: boolean;
shadowcopy: boolean;
......@@ -29,6 +29,15 @@ export interface SmbShare {
vuid: string;
}
export enum SmbPresetType {
NoPresets = 'NO_PRESET',
DefaultShareParameters = 'DEFAULT_SHARE',
MultiUserTimeMachine = 'ENHANCED_TIMEMACHINE',
MultiProtocolShares = 'MULTI_PROTOCOL_NFS',
PrivateSmbDatasets = 'PRIVATE_DATASETS',
SmbWorm = 'WORM_DROPBOX',
}
export interface SmbPreset {
verbose_name: string;
params: Partial<SmbShare>;
......
......@@ -28,7 +28,7 @@
</div>
</mat-panel-description>
</mat-expansion-panel-header>
<app-table [conf]="tableConf"></app-table>
<app-table #tableComponent [conf]="tableConf"></app-table>
</mat-expansion-panel>
<div id="actions-row" [ngClass]="{'actions-row': isExpanded && tableConf.detailsHref}" *ngIf="isExpanded && tableConf.detailsHref">
<div>
......
......@@ -3,7 +3,7 @@ import {
Component, ElementRef, Input, OnInit, ViewChild,
} from '@angular/core';
import { ServiceStatus } from 'app/enums/service-status.enum';
import { AppTableAction, AppTableConfig } from 'app/modules/entity/table/table.component';
import { AppTableAction, AppTableConfig, TableComponent } from 'app/modules/entity/table/table.component';
export interface InputExpandableTableConf extends AppTableConfig {
detailsHref?: string;
......@@ -37,6 +37,8 @@ export class ExpandableTableComponent implements OnInit, AfterViewChecked {
@Input() expandableTableState: ExpandableTableState;
@Input() disabled: boolean;
@ViewChild('tableComponent') tableComponent: TableComponent;
@ViewChild('appTable', { read: ElementRef })
appTable: ElementRef;
......
......@@ -8,6 +8,7 @@
<div class="input-container" [class.disabled]="isDisabled">
<mat-chip-list
[disabled]="isDisabled"
#chipList class="form-chip"
[required]="required"
>
......@@ -23,6 +24,7 @@
</mat-chip>
<input
[placeholder]="placeholder"
[disabled]="isDisabled"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
......
......@@ -14,7 +14,7 @@
[(ngModel)]="value"
[required]="required"
[disabled]="isDisabled"
(ngModelChange)="onChange($event)"
(ngModelChange)="valueChangedCustom($event)"
(blur)="onTouch()"
>
</div>
......
......@@ -137,6 +137,11 @@ export class IxExplorerComponent implements OnInit, ControlValueAccessor {
this.onChange(newValue);
}
valueChangedCustom(value: string): void {
this.value = value;
this.onChange(value);
}
/**
* Provides typing in templates
*/
......
......@@ -23,7 +23,7 @@
[value]="formatted"
(input)="input(ixInput)"
(focus)="focus(ixInput)"
(blur)="blur()"
(blur)="blurred()"
[autocomplete]="autocomplete"
[placeholder]="placeholder"
>
......
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Input,
ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output,
} from '@angular/core';
import { ControlValueAccessor, FormControl, NgControl } from '@angular/forms';
import { UntilDestroy } from '@ngneat/until-destroy';
......@@ -22,6 +22,7 @@ export class IxInputComponent implements ControlValueAccessor {
@Input() readonly: boolean;
@Input() type: string;
@Input() autocomplete = 'off';
@Output() inputBlur: EventEmitter<unknown> = new EventEmitter();
/** If formatted value returned by parseAndFormatInput has non-numeric letters
* and input 'type' is a number, the input will stay empty on the form */
......@@ -118,7 +119,7 @@ export class IxInputComponent implements ControlValueAccessor {
}
}
blur(): void {
blurred(): void {
this.onTouch();
if (this.formatted) {
if (this.parse) {
......@@ -131,6 +132,7 @@ export class IxInputComponent implements ControlValueAccessor {
this.onChange(this.value);
this.cdr.markForCheck();
this.inputBlur.emit();
}
onPasswordToggled(): void {
......
<div class="container" [ngClass]="getContainerClass()">
<app-expandable-table [ngClass]="getWebdavOrder()" [expandableTableState]="webdavExpandableState" [disabled]="true" [conf]="webdavTableConf">
<app-expandable-table #webdavTable [ngClass]="getWebdavOrder()" [expandableTableState]="webdavExpandableState" [disabled]="true" [conf]="webdavTableConf">
<ng-container
headerCustomContent
*ngTemplateOutlet="webdavServiceStatus === ServiceStatus.Loading ? statusLoading : statusButton; context: { status: webdavServiceStatus, count: webdavHasItems }"
......@@ -9,7 +9,7 @@
*ngTemplateOutlet="extraAction; context:{ tableExtraActions: webdavTableConf?.tableExtraActions}"
></ng-container>
</app-expandable-table>
<app-expandable-table [ngClass]="getNfsOrder()" [expandableTableState]="nfsExpandableState" [disabled]="true" [conf]="nfsTableConf">
<app-expandable-table #nfsTable [ngClass]="getNfsOrder()" [expandableTableState]="nfsExpandableState" [disabled]="true" [conf]="nfsTableConf">
<ng-container
headerCustomContent
*ngTemplateOutlet="nfsServiceStatus === ServiceStatus.Loading ? statusLoading : statusButton; context: { status: nfsServiceStatus, count: nfsHasItems }"
......@@ -19,7 +19,7 @@
*ngTemplateOutlet="extraAction; context:{ tableExtraActions: nfsTableConf?.tableExtraActions}"
></ng-container>
</app-expandable-table>
<app-expandable-table [ngClass]="getSmbOrder()" [expandableTableState]="smbExpandableState" [disabled]="true" [conf]="smbTableConf">
<app-expandable-table #smbTable [ngClass]="getSmbOrder()" [expandableTableState]="smbExpandableState" [disabled]="true" [conf]="smbTableConf">
<ng-container
headerCustomContent
*ngTemplateOutlet="smbServiceStatus === ServiceStatus.Loading ? statusLoading : statusButton; context: { status: smbServiceStatus, count: smbHasItems }"
......@@ -29,7 +29,7 @@
*ngTemplateOutlet="extraAction; context:{ tableExtraActions: smbTableConf?.tableExtraActions}"
></ng-container>
</app-expandable-table>
<app-expandable-table [ngClass]="getIscsiOrder()" [expandableTableState]="iscsiExpandableState" [disabled]="true" [conf]="iscsiTableConf">
<app-expandable-table #iscsiTable [ngClass]="getIscsiOrder()" [expandableTableState]="iscsiExpandableState" [disabled]="true" [conf]="iscsiTableConf">
<ng-container
headerCustomContent
*ngTemplateOutlet="iscsiServiceStatus === ServiceStatus.Loading ? statusLoading : statusButton; context: { status: iscsiServiceStatus, count: iscsiHasItems }"
......
import { AfterViewInit, Component, Type } from '@angular/core';
import { Validators } from '@angular/forms';
import {
AfterViewInit, Component, ViewChild,
} from '@angular/core';
import { Router } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
......@@ -15,14 +16,12 @@ import { Service } from 'app/interfaces/service.interface';
import { SmbShare } from 'app/interfaces/smb-share.interface';
import { WebDavShare } from 'app/interfaces/web-dav-share.interface';
import { WebsocketError } from 'app/interfaces/websocket-error.interface';
import { DialogFormConfiguration } from 'app/modules/entity/entity-dialog/dialog-form-configuration.interface';
import { EntityDialogComponent } from 'app/modules/entity/entity-dialog/entity-dialog.component';
import {
ExpandableTableComponent,
ExpandableTableState,
InputExpandableTableConf,
} from 'app/modules/entity/table/expandable-table/expandable-table.component';
import {
TableComponent,
AppTableHeaderAction,
} from 'app/modules/entity/table/table.component';
import { EntityUtils } from 'app/modules/entity/utils';
......@@ -36,7 +35,7 @@ import {
ModalService,
WebSocketService,
} from 'app/services';
import { IxSlideInService } from 'app/services/ix-slide-in.service';
import { IxSlideInService, ResponseOnClose } from 'app/services/ix-slide-in.service';
enum ShareType {
Smb = 'smb',
......@@ -60,6 +59,11 @@ export class SharesDashboardComponent implements AfterViewInit {
smbTableConf: InputExpandableTableConf = this.getTableConfigForShareType(ShareType.Smb);
iscsiTableConf: InputExpandableTableConf = this.getTableConfigForShareType(ShareType.Iscsi);
@ViewChild('webdavTable', { static: false }) webdavTable: ExpandableTableComponent;
@ViewChild('nfsTable', { static: false }) nfsTable: ExpandableTableComponent;
@ViewChild('smbTable', { static: false }) smbTable: ExpandableTableComponent;
@ViewChild('iscsiTable', { static: false }) iscsiTable: ExpandableTableComponent;
webdavHasItems = 0;
nfsHasItems = 0;
smbHasItems = 0;
......@@ -129,6 +133,41 @@ export class SharesDashboardComponent implements AfterViewInit {
if (this.iscsiHasItems) {
this.iscsiExpandableState = ExpandableTableState.Expanded;
}
this.setupTableRefreshOnPanelClose();
}
setupTableRefreshOnPanelClose(): void {
this.slideInService.onClose$.pipe(untilDestroyed(this)).subscribe(({ modalType }: ResponseOnClose) => {
switch (modalType) {
case WebdavFormComponent:
if (!this.webdavTable.tableComponent) {
this.refreshDashboard();
}
this.webdavTable.tableComponent.getData();
break;
case SmbFormComponent:
if (!this.smbTable.tableComponent) {
this.refreshDashboard();
}
this.smbTable.tableComponent.getData();
break;
case NfsFormComponent:
if (!this.nfsTable.tableComponent) {
this.refreshDashboard();
}
this.nfsTable.tableComponent.getData();
break;
case TargetFormComponent:
if (!this.iscsiTable.tableComponent) {
this.refreshDashboard();
}
this.iscsiTable.tableComponent.getData();
break;
default:
this.refreshDashboard();
break;
}
});
}
refreshDashboard(): void {
......@@ -167,10 +206,11 @@ export class SharesDashboardComponent implements AfterViewInit {
],
detailsHref: '/sharing/nfs',
add() {
this.parent.add(this.tableComponent, ShareType.Nfs);
this.parent.slideInService.open(NfsFormComponent);
},
edit(row: NfsShare) {
this.parent.edit(this.tableComponent, ShareType.Nfs, row.id);
const form = this.parent.slideInService.open(NfsFormComponent);
(form as NfsFormComponent).setNfsShareForEdit(row);
},
afterGetData: (data: NfsShare[]) => {
this.nfsHasItems = 0;
......@@ -209,10 +249,11 @@ export class SharesDashboardComponent implements AfterViewInit {
},
],
add() {
this.parent.add(this.tableComponent, ShareType.Iscsi);
this.parent.slideInService.open(TargetFormComponent, { wide: true });
},
edit(row: IscsiTarget) {
this.parent.edit(this.tableComponent, ShareType.Iscsi, row.id);
const targetForm = this.parent.slideInService.open(TargetFormComponent, { wide: true });
targetForm.setTargetForEdit(row);
},
afterGetData: (data: IscsiTarget[]) => {
this.iscsiHasItems = 0;
......@@ -268,11 +309,12 @@ export class SharesDashboardComponent implements AfterViewInit {
},
],
add() {
this.parent.add(this.tableComponent, ShareType.WebDav);
this.parent.slideInService.open(WebdavFormComponent);
},
limitRowsByMaxHeight: true,
edit(row: WebDavShare) {
this.parent.edit(this.tableComponent, ShareType.WebDav, row.id);
const form = this.parent.slideInService.open(WebdavFormComponent);
(form as WebdavFormComponent).setWebdavForEdit(row);
},
afterGetData: (data: WebDavShare[]) => {
this.webdavHasItems = 0;
......@@ -314,10 +356,11 @@ export class SharesDashboardComponent implements AfterViewInit {
],
limitRowsByMaxHeight: true,
add() {
this.parent.add(this.tableComponent, ShareType.Smb);
this.parent.slideInService.open(SmbFormComponent);
},
edit(row: SmbShare) {
this.parent.edit(this.tableComponent, ShareType.Smb, row.id);
const form = this.parent.slideInService.open(SmbFormComponent);
(form as SmbFormComponent).setSmbShareForEdit(row);
},
afterGetData: (data: SmbShare[]) => {
this.smbHasItems = 0;
......@@ -333,71 +376,6 @@ export class SharesDashboardComponent implements AfterViewInit {
}
}
add(tableComponent: TableComponent, share: ShareType, id?: number): void {
let formComponent: Type<NfsFormComponent | SmbFormComponent | WebdavFormComponent | TargetFormComponent>;
switch (share) {
case ShareType.Nfs:
formComponent = NfsFormComponent;
break;
case ShareType.Smb:
formComponent = SmbFormComponent;
break;
case ShareType.WebDav:
formComponent = WebdavFormComponent;
break;
case ShareType.Iscsi:
formComponent = TargetFormComponent;
break;
}
if ([ShareType.WebDav, ShareType.Nfs].includes(share)) {
const form = this.slideInService.open(formComponent);
if (id) {
const row = tableComponent.displayedDataSource.find((row) => row.id === id);
if (share === ShareType.WebDav) {
(form as WebdavFormComponent).setWebdavForEdit(row);
} else if (share === ShareType.Nfs) {
(form as NfsFormComponent).setNfsShareForEdit(row);
}
}
this.slideInService.onClose$.pipe(untilDestroyed(this)).subscribe(() => {
if (!tableComponent) {
this.refreshDashboard();
} else {
tableComponent.getData();
}
});
} else if (share === ShareType.Iscsi) {
const targetForm = this.slideInService.open(TargetFormComponent, { wide: true });
if (id) {
targetForm.setTargetForEdit(tableComponent.displayedDataSource.find((row) => row.id === id));
}
this.slideInService.onClose$.pipe(untilDestroyed(this)).subscribe(() => {
if (!tableComponent) {
this.refreshDashboard();
} else {
tableComponent.getData();
}
}, (err) => {
new EntityUtils().handleWsError(this, err, this.dialog);
});
} else {
this.modalService.openInSlideIn(formComponent, id);
this.modalService.onClose$.pipe(untilDestroyed(this)).subscribe(() => {
if (!tableComponent) {
this.refreshDashboard();
} else {
tableComponent.getData();
}
}, (err) => {
new EntityUtils().handleWsError(this, err, this.dialog);
});
}
}
edit(tableComponent: TableComponent, share: ShareType, id: number): void {
this.add(tableComponent, share, id);
}
getTablesOrder(): string[] {
const order: string[] = [ShareType.Smb, ShareType.Nfs, ShareType.Iscsi, ShareType.WebDav];
// Note: The order of these IFs is important. One can't come before the other
......@@ -470,31 +448,6 @@ export class SharesDashboardComponent implements AfterViewInit {
}
}
showAddDialog(): void {
const conf: DialogFormConfiguration = {
title: this.translate.instant('Add New Share'),
message: this.translate.instant('Select the type of Share you want to add'),
saveButtonText: this.translate.instant('Create'),
fieldConfig: [{
type: 'radio',
name: 'share_type',
options: [
{ label: 'SMB', value: ShareType.Smb },
{ label: 'NFS', value: ShareType.Nfs },
{ label: 'iSCSI Target', value: ShareType.Iscsi },
{ label: 'WebDAV', value: ShareType.WebDav },
],
validation: [Validators.required],
},
],
customSubmit: (dialog: EntityDialogComponent) => {
dialog.dialogRef.close();
this.add(null, dialog.formValue.share_type);
},
};
this.dialog.dialogForm(conf);
}
onSlideToggle(card: ShareType, row: ShareTableRow, param: 'enabled' | 'ro'): void {
let updateCall: keyof ApiDirectory;
switch (card) {
......
......@@ -141,6 +141,7 @@ export class TargetFormComponent {
};
this.isLoading = true;
this.cdr.markForCheck();
let request$: Observable<unknown>;
if (this.isNew) {
request$ = this.ws.call('iscsi.target.create', [params]);
......
......@@ -124,6 +124,7 @@ export class NfsFormComponent implements OnInit {
onSubmit(): void {
this.isLoading = true;
this.cdr.markForCheck();
const nfsShare = this.form.value;
let request$: Observable<unknown>;
if (this.isNew) {
......@@ -140,6 +141,7 @@ export class NfsFormComponent implements OnInit {
.subscribe(
() => {
this.isLoading = false;
this.cdr.markForCheck();
this.slideInService.close();
},
(error) => {
......
......@@ -4,6 +4,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
......@@ -22,6 +23,7 @@ import { TooltipModule } from 'app/modules/tooltip/tooltip.module';
import { AuthorizedAccessFormComponent } from 'app/pages/sharing/iscsi/authorized-access/authorized-access-form/authorized-access-form.component';
import { AuthorizedAccessListComponent } from 'app/pages/sharing/iscsi/authorized-access/authorized-access-list/authorized-access-list.component';
import { TargetGlobalConfigurationComponent } from 'app/pages/sharing/iscsi/target-global-configuration/target-global-configuration.component';
import { RestartSmbDialogComponent } from 'app/pages/sharing/smb/smb-form/restart-smb-dialog/restart-smb-dialog.component';
import { UserService } from 'app/services/user.service';
import { SharesDashboardComponent } from './components/shares-dashboard/shares-dashboard.component';
import { AssociatedTargetFormComponent } from './iscsi/associated-target/associated-target-form/associated-target-form.component';
......@@ -62,6 +64,7 @@ import { WebdavListComponent } from './webdav/webdav-list/webdav-list.component'
MatIconModule,
FlexLayoutModule,
MatListModule,
MatDialogModule,
MatMenuModule,
MatFormFieldModule,
MatTooltipModule,
......@@ -80,6 +83,7 @@ import { WebdavListComponent } from './webdav/webdav-list/webdav-list.component'
WebdavFormComponent,
SmbListComponent,
SmbFormComponent,
RestartSmbDialogComponent,
SmbAclComponent,
IscsiComponent,
IscsiWizardComponent,
......
<mat-dialog-content>
<h3>{{ 'Restart SMB Service' | translate }}</h3>
<div>
<p>{{ helptext.restartPt1 }}</p>
<ul>
<li *ngIf="data.homeshare">{{ homeShareMessage }}</li>
<li *ngIf="data.timemachine">{{ timemachineMessage }}</li>
<li *ngIf="data.hosts">{{ hostsMessage }}</li>
<li *ngIf="data.path">{{ pathMessage }}</li>
</ul>
<p><strong>{{ helptext.restartPt2 }}</strong></p>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button [mat-dialog-close]="false">{{ 'No' | translate }}</button>
<button mat-button [mat-dialog-close]="true" cdkFocusInitial color="primary">{{ 'Restart Service' | translate }}</button>
</mat-dialog-actions>
\ No newline at end of file
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { helptextSharingSmb } from 'app/helptext/sharing';
interface RestartDialogData {
homeshare: boolean;
timemachine: boolean;
hosts: boolean;
path: boolean;
isNew: boolean;
}
@Component({
templateUrl: './restart-smb-dialog.component.html',
})
export class RestartSmbDialogComponent {
readonly helptext = helptextSharingSmb;
constructor(
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: RestartDialogData,
) {}
get homeShareMessage(): string {
return this.data.isNew
? this.translate.instant('Enabled \'Use as Home Share\'')
: this.translate.instant('Updated \'Use as Home Share\'');
}
get timemachineMessage(): string {
return this.data.isNew
? this.translate.instant('Enabled \'Time Machine\'')
: this.translate.instant('Update \'Time Machine\'');
}
get pathMessage(): string {
return this.translate.instant('Share Path updated');
}
get hostsMessage(): string {
return this.data.isNew
? this.translate.instant('\'Hosts Allow\' or \'Hosts Deny\' has been set')
: this.translate.instant('\'Hosts Allow\' or \'Hosts Deny\' has been updated');
}
}
<ix-modal-header [title]="title" [loading]="isLoading"></ix-modal-header>
<mat-card>
<mat-card-content>
<form [formGroup]="form" class="ix-form-container" (submit)="submit()">
<ix-fieldset [title]="'Basic' | translate">
<ix-explorer
formControlName="path"
[required]="true"
[tooltip]="helptextSharingSmb.tooltip_path | translate"
[label]="helptextSharingSmb.placeholder_path | translate"
[nodeProvider]="treeNodeProvider"
></ix-explorer>
<ix-input
formControlName="name"
[label]="helptextSharingSmb.placeholder_name | translate"
[tooltip]="helptextSharingSmb.tooltip_name | translate"
[required]="true"
(inputBlur)="setNameFromPath()"
></ix-input>
<ix-select
formControlName="purpose"
[label]="helptextSharingSmb.placeholder_purpose | translate"
[tooltip]="helptextSharingSmb.tooltip_purpose | translate"
emptyValue=""
[required]="true"
[options]="purposeOptions$"
></ix-select>
<ix-input
formControlName="comment"
[label]="helptextSharingSmb.placeholder_comment | translate"
[tooltip]="helptextSharingSmb.tooltip_comment | translate"
></ix-input>
<ix-checkbox
formControlName="enabled"
[label]="helptextSharingSmb.placeholder_enabled | translate"
[tooltip]="helptextSharingSmb.tooltip_enabled | translate"
></ix-checkbox>
</ix-fieldset>
<ix-fieldset
*ngIf="isAdvancedMode"
[title]="'Access' | translate"
>
<ix-checkbox
formControlName="acl"
[label]="helptextSharingSmb.placeholder_acl | translate"
[tooltip]="helptextSharingSmb.tooltip_acl | translate"
></ix-checkbox>
<ix-checkbox
formControlName="ro"
[label]="helptextSharingSmb.placeholder_ro | translate"
[tooltip]="helptextSharingSmb.tooltip_ro | translate"
></ix-checkbox>
<ix-checkbox
formControlName="browsable"
[label]="helptextSharingSmb.placeholder_browsable | translate"
[tooltip]="helptextSharingSmb.tooltip_browsable | translate"
></ix-checkbox>
<ix-checkbox
formControlName="guestok"
[label]="helptextSharingSmb.placeholder_guestok | translate"
[tooltip]="helptextSharingSmb.tooltip_guestok | translate"
></ix-checkbox>
<ix-checkbox
formControlName="abe"
[label]="helptextSharingSmb.placeholder_abe | translate"
[tooltip]="helptextSharingSmb.tooltip_abe | translate"
></ix-checkbox>
<ix-chips
formControlName="hostsallow"
[label]="helptextSharingSmb.placeholder_hostsallow | translate"
[tooltip]="helptextSharingSmb.tooltip_hostsallow | translate"
></ix-chips>
<ix-chips
formControlName="hostsdeny"
[label]="helptextSharingSmb.placeholder_hostsdeny | translate"
[tooltip]="helptextSharingSmb.tooltip_hostsdeny | translate"
></ix-chips>
</ix-fieldset>
<ix-fieldset
*ngIf="isAdvancedMode"
[title]="'Other Options' | translate"
>
<ix-checkbox
formControlName="home"
[label]="helptextSharingSmb.placeholder_home | translate"
[tooltip]="helptextSharingSmb.tooltip_home | translate"
></ix-checkbox>
<ix-checkbox
formControlName="timemachine"
[label]="helptextSharingSmb.placeholder_timemachine | translate"
[tooltip]="helptextSharingSmb.tooltip_timemachine | translate"
></ix-checkbox>
<ix-checkbox
formControlName="afp"
[label]="helptextSharingSmb.placeholder_afp | translate"
[tooltip]="helptextSharingSmb.tooltip_afp | translate"
></ix-checkbox>
<ix-checkbox
formControlName="shadowcopy"
[label]="helptextSharingSmb.placeholder_shadowcopy | translate"
[tooltip]="helptextSharingSmb.tooltip_shadowcopy | translate"
></ix-checkbox>
<ix-checkbox
formControlName="recyclebin"
[label]="helptextSharingSmb.placeholder_recyclebin | translate"
[tooltip]="helptextSharingSmb.tooltip_recyclebin | translate"
></ix-checkbox>
<ix-checkbox
formControlName="aapl_name_mangling"
[label]="helptextSharingSmb.placeholder_aapl_name_mangling | translate"
[tooltip]="helptextSharingSmb.tooltip_aapl_name_mangling | translate"
></ix-checkbox>
<ix-checkbox
formControlName="streams"
[label]="helptextSharingSmb.placeholder_streams | translate"
[tooltip]="helptextSharingSmb.tooltip_streams | translate"
></ix-checkbox>
<ix-checkbox
formControlName="durablehandle"
[label]="helptextSharingSmb.placeholder_durablehandle | translate"
[tooltip]="helptextSharingSmb.tooltip_durablehandle | translate"
></ix-checkbox>
<ix-checkbox
formControlName="fsrvp"
[label]="helptextSharingSmb.placeholder_fsrvp | translate"
[tooltip]="helptextSharingSmb.tooltip_fsrvp | translate"
></ix-checkbox>
<ix-input
formControlName="path_suffix"
[label]="helptextSharingSmb.placeholder_path_suffix | translate"
[tooltip]="helptextSharingSmb.tooltip_path_suffix | translate"
></ix-input>
<ix-textarea
formControlName="auxsmbconf"
[label]="helptextSharingSmb.placeholder_auxsmbconf | translate"
[tooltip]="helptextSharingSmb.tooltip_auxsmbconf | translate"
></ix-textarea>
</ix-fieldset>
<div class="form-actions">
<button
mat-button
type="submit"
[disabled]="form.invalid || isLoading"
color="primary"
>{{ 'Save' | translate }}</button>
<button
mat-button
type="button"
(click)="this.isAdvancedMode = !this.isAdvancedMode"
>{{ isAdvancedMode ? ('Basic Options' | translate) : ('Advanced Options' | translate) }}</button>
</div>
</form>
</mat-card-content>
</mat-card>
\ No newline at end of file
.form-actions {
margin: 8px 10px;
button {
margin-right: 5px;
}
}
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { of } from 'rxjs';
import { mockCall, mockWebsocket } from 'app/core/testing/utils/mock-websocket.utils';
import { ServiceName } from 'app/enums/service-name.enum';
import { ServiceStatus } from 'app/enums/service-status.enum';
import { helptextSharingSmb } from 'app/helptext/sharing';
import { FileSystemStat } from 'app/interfaces/filesystem-stat.interface';
import { Service } from 'app/interfaces/service.interface';
import { SmbPresets, SmbPresetType, SmbShare } from 'app/interfaces/smb-share.interface';
import { IxCheckboxHarness } from 'app/modules/ix-forms/components/ix-checkbox/ix-checkbox.harness';
import { IxExplorerHarness } from 'app/modules/ix-forms/components/ix-explorer/ix-explorer.harness';
import { IxInputHarness } from 'app/modules/ix-forms/components/ix-input/ix-input.harness';
import { IxSelectHarness } from 'app/modules/ix-forms/components/ix-select/ix-select.harness';
import { IxFormsModule } from 'app/modules/ix-forms/ix-forms.module';
import { IxFormHarness } from 'app/modules/ix-forms/testing/ix-form.harness';
import { RestartSmbDialogComponent } from 'app/pages/sharing/smb/smb-form/restart-smb-dialog/restart-smb-dialog.component';
import { AppLoaderService, DialogService, WebSocketService } from 'app/services';
import { FilesystemService } from 'app/services/filesystem.service';
import { IxSlideInService } from 'app/services/ix-slide-in.service';
import { SmbFormComponent } from './smb-form.component';
describe('SmbFormComponent', () => {
const existingShare: SmbShare = {
id: 1,
purpose: SmbPresetType.MultiUserTimeMachine,
path: '/mnt/pool123/ds222',
path_suffix: '%U',
home: false,
name: 'ds222',
comment: '',
ro: false,
browsable: true,
recyclebin: true,
guestok: true,
hostsallow: ['host1'],
hostsdeny: ['host2'],
auxsmbconf: '',
aapl_name_mangling: false,
abe: true,
acl: true,
durablehandle: true,
streams: true,
timemachine: true,
vuid: 'a7bcb6cb-b2f3-4144-a5bb-e79dc7e282c4',
shadowcopy: true,
fsrvp: false,
enabled: true,
cluster_volname: '',
locked: false,
};
const formLabels: { [key: string]: string } = {
path: 'Path',
name: 'Name',
purpose: 'Purpose',
comment: 'Description',
enabled: 'Enabled',
acl: 'Enable ACL',
ro: 'Export Read Only',
browsable: 'Browsable to Network Clients',
guestok: 'Allow Guest Access',
abe: 'Access Based Share Enumeration',
hostsallow: 'Hosts Allow',
hostsdeny: 'Hosts Deny',
home: 'Use as Home Share',
timemachine: 'Time Machine',
afp: 'Legacy AFP Compatibility',
shadowcopy: 'Enable Shadow Copies',
recyclebin: 'Export Recycle Bin',
aapl_name_mangling: 'Use Apple-style Character Encoding',
streams: 'Enable Alternate Data Streams',
durablehandle: 'Enable SMB2/3 Durable Handles',
fsrvp: 'Enable FSRVP',
path_suffix: 'Path Suffix',
auxsmbconf: 'Auxiliary Parameters',
};
const presets: SmbPresets = {
NO_PRESET: {
verbose_name: 'No presets',
params: {
auxsmbconf: '',
},
},
ENHANCED_TIMEMACHINE: {
verbose_name: 'Multi-user time machine',
params: {
path_suffix: '%U',
timemachine: true,
auxsmbconf: 'zfs_core:zfs_auto_create=true\nzfs_core:base_user_quota=1T',
},
},
PRIVATE_DATASETS: {
verbose_name: 'Private SMB Datasets and Shares',
params: {
path_suffix: '%U',
auxsmbconf: 'zfs_core:zfs_auto_create=true',
},
},
};
let spectator: Spectator<SmbFormComponent>;
let loader: HarnessLoader;
let form: IxFormHarness;
let websocket: WebSocketService;
const createComponent = createComponentFactory({
component: SmbFormComponent,
imports: [
ReactiveFormsModule,
IxFormsModule,
],
providers: [
mockWebsocket([
mockCall('sharing.smb.create'),
mockCall('sharing.smb.update'),
mockCall('sharing.smb.query', [
{ ...existingShare },
]),
mockCall('service.query', [{
service: ServiceName.Cifs,
id: 4,
enable: false,
state: ServiceStatus.Running,
} as Service]),
mockCall('filesystem.stat', {
acl: false,
} as FileSystemStat),
mockCall('service.update'),
mockCall('service.start'),
mockCall('service.restart'),
mockCall('sharing.smb.presets', { ...presets }),
mockCall('filesystem.acl_is_trivial', false),
mockCall('pool.dataset.path_in_locked_datasets', false),
]),
mockProvider(IxSlideInService),
mockProvider(Router),
mockProvider(AppLoaderService),
mockProvider(FilesystemService),
mockProvider(MatDialog, {
open: jest.fn(() => ({
afterClosed: () => of(true),
})),
}),
mockProvider(DialogService, {
confirm: jest.fn(() => of(true)),
info: jest.fn(() => of(true)),
}),
],
});
beforeEach(async () => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
form = await loader.getHarness(IxFormHarness);
websocket = spectator.inject(WebSocketService);
});
it('shows all the fields when Advanced Options button is pressed', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const fields = Object.keys(await form.getControlHarnessesDict());
for (const param in formLabels) {
expect(fields).toContain(formLabels[param]);
}
});
it('sets the correct options array for purpose field', async () => {
const purposeSelect = await loader.getHarness(IxSelectHarness.with({ label: 'Purpose' }));
const optionLabels = await purposeSelect.getOptionLabels();
expect(optionLabels).toEqual([
'No presets',
'Multi-user time machine',
'Private SMB Datasets and Shares',
]);
});
it('should have error for duplicate share name', async () => {
const nameControl = await loader.getHarness(IxInputHarness.with({ label: 'Name' }));
await nameControl.setValue('ds222');
expect(await nameControl.getErrorText()).toEqual('The name "ds222" is already in use.');
});
it('when a preset is selected, the relevant fields should be impacted', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const purposeSelect = await loader.getHarness(IxSelectHarness.with({ label: formLabels.purpose }));
const labels = await purposeSelect.getOptionLabels();
const presetKeys = Object.keys(presets);
const form = await loader.getHarness(IxFormHarness);
const fields = await form.getControlHarnessesDict();
for (let i = 0; i < labels.length; i++) {
await purposeSelect.setValue(labels[i]);
for (const param in presets[presetKeys[i]].params) {
if (param === 'auxsmbconf') {
continue;
}
const expectedValue = presets[presetKeys[i]].params[param as keyof SmbShare];
const value = await fields[formLabels[param]].getValue();
expect(value).toStrictEqual(expectedValue);
expect(await fields[formLabels[param]].isDisabled()).toBeTruthy();
}
}
expect(true).toBeTruthy();
});
it('should show confirmation warning when afp is checked', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const afpCheckbox = await loader.getHarness(IxCheckboxHarness.with({ label: formLabels.afp }));
await afpCheckbox.setValue(true);
expect(spectator.inject(DialogService).confirm).toHaveBeenNthCalledWith(1, {
title: helptextSharingSmb.afpDialog_title,
message: helptextSharingSmb.afpDialog_message,
hideCheckBox: false,
buttonMsg: helptextSharingSmb.afpDialog_button,
hideCancel: false,
});
});
it('shows values of existing share when editing', async () => {
spectator.component.setSmbShareForEdit(existingShare);
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const values = await form.getValues();
const existingShareWithLabels: { [key: string]: any } = {};
for (const key in existingShare) {
if (!formLabels[key]) {
continue;
}
existingShareWithLabels[formLabels[key]] = existingShare[key as keyof SmbShare];
}
existingShareWithLabels[formLabels.purpose] = presets[existingShareWithLabels[formLabels.purpose]].verbose_name;
expect(values).toMatchObject(existingShareWithLabels);
});
it('should show warning if aaple_name_mangling value changes when editing', async () => {
spectator.component.setSmbShareForEdit(existingShare);
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const aaplNameManglingCheckbox = await loader.getHarness(
IxCheckboxHarness.with({ label: formLabels.aapl_name_mangling }),
);
if (existingShare.aapl_name_mangling) {
await aaplNameManglingCheckbox.setValue(false);
} else {
await aaplNameManglingCheckbox.setValue(true);
}
expect(spectator.inject(DialogService).confirm).toHaveBeenNthCalledWith(2, {
title: helptextSharingSmb.manglingDialog.title,
message: helptextSharingSmb.manglingDialog.message,
hideCheckBox: true,
buttonMsg: helptextSharingSmb.manglingDialog.action,
hideCancel: true,
});
});
it('should autofill name from path if name is empty', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const nameControl = await loader.getHarness(IxInputHarness.with({ label: formLabels.name }));
await nameControl.setValue('');
const pathControl = await loader.getHarness(IxExplorerHarness.with({ label: formLabels.path }));
await pathControl.setValue('/mnt/pool2/ds22');
expect(await nameControl.getValue()).toEqual('ds22');
expect(spectator.inject(DialogService).confirm).toHaveBeenNthCalledWith(3, {
title: helptextSharingSmb.stripACLDialog.title,
message: helptextSharingSmb.stripACLDialog.message,
hideCheckBox: true,
buttonMsg: helptextSharingSmb.stripACLDialog.button,
hideCancel: true,
});
});
it('should show strip acl warning if acl is trivial when path changes', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const pathControl = await loader.getHarness(IxExplorerHarness.with({ label: formLabels.path }));
await pathControl.setValue('/mnt/pool2/ds22');
const purposeSelect = await loader.getHarness(IxSelectHarness.with({ label: 'Purpose' }));
await purposeSelect.setValue(presets.NO_PRESET.verbose_name);
const aclCheckbox = await loader.getHarness(IxCheckboxHarness.with({ label: formLabels.acl }));
await (await aclCheckbox.getMatCheckboxHarness()).uncheck();
expect(spectator.inject(DialogService).confirm).toHaveBeenNthCalledWith(4, {
title: helptextSharingSmb.stripACLDialog.title,
message: helptextSharingSmb.stripACLDialog.message,
hideCheckBox: true,
buttonMsg: helptextSharingSmb.stripACLDialog.button,
hideCancel: true,
});
});
it('should show acl warning if acl is unchcekd and dataset is non-trivial', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const purposeSelect = await loader.getHarness(IxSelectHarness.with({ label: 'Purpose' }));
await purposeSelect.setValue(presets.PRIVATE_DATASETS.verbose_name);
const pathControl = await loader.getHarness(IxExplorerHarness.with({ label: formLabels.path }));
await pathControl.setValue('/mnt/pool2/ds22');
const aclCheckbox = await loader.getHarness(IxCheckboxHarness.with({ label: formLabels.acl }));
await (await aclCheckbox.getMatCheckboxHarness()).uncheck();
expect(spectator.inject(DialogService).confirm).toHaveBeenNthCalledWith(5, {
title: helptextSharingSmb.stripACLDialog.title,
message: helptextSharingSmb.stripACLDialog.message,
hideCheckBox: true,
buttonMsg: helptextSharingSmb.stripACLDialog.button,
hideCancel: true,
});
});
it('should submit the form with the correct value', async () => {
const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const attrs: { [key: string]: unknown } = {};
for (const key in existingShare) {
if (formLabels[key]) {
attrs[formLabels[key]] = existingShare[key as keyof SmbShare];
}
}
attrs[formLabels.purpose] = presets[attrs[formLabels.purpose] as string].verbose_name;
attrs[formLabels.name] = 'ds223';
attrs[formLabels.hostsallow] = ['host11'];
attrs[formLabels.hostsdeny] = ['host22'];
await form.fillForm({
...attrs,
});
expect(spectator.inject(DialogService).confirm)
.toHaveBeenNthCalledWith(6, {
title: helptextSharingSmb.stripACLDialog.title,
message: helptextSharingSmb.stripACLDialog.message,
hideCheckBox: true,
buttonMsg: helptextSharingSmb.stripACLDialog.button,
hideCancel: true,
});
const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
await saveButton.click();
expect(websocket.call).toHaveBeenCalledWith('sharing.smb.create', [{
path: '/mnt/pool123/ds222',
name: 'ds223',
purpose: SmbPresetType.MultiUserTimeMachine,
comment: '',
enabled: true,
acl: true,
ro: false,
browsable: true,
guestok: true,
abe: true,
hostsallow: ['host11'],
hostsdeny: ['host22'],
home: false,
afp: false,
shadowcopy: true,
recyclebin: true,
aapl_name_mangling: false,
streams: true,
durablehandle: true,
fsrvp: false,
auxsmbconf: '',
}]);
expect(websocket.call).toHaveBeenCalledWith('service.query');
expect(spectator.inject(MatDialog).open)
.toHaveBeenCalledWith(RestartSmbDialogComponent, {
data: {
timemachine: true,
homeshare: true,
path: false,
hosts: true,
isNew: true,
},
});
expect(websocket.call).toHaveBeenCalledWith('service.restart', [ServiceName.Cifs]);
expect(spectator.inject(DialogService).info).toHaveBeenCalledWith(
helptextSharingSmb.restarted_smb_dialog.title,
helptextSharingSmb.restarted_smb_dialog.message,
);
const sharePath = await (await loader.getHarness(
IxExplorerHarness.with({ label: formLabels.path }),
)).getValue();
expect(websocket.call).toHaveBeenCalledWith('filesystem.stat', [sharePath]);
const homeShare = await (await loader.getHarness(
IxCheckboxHarness.with({ label: formLabels.home }),
)).getValue();
const datasetId = sharePath.replace('/mnt/', '');
const poolName = datasetId.split('/')[0];
expect(spectator.inject(Router).navigate).toHaveBeenCalledWith(['/'].concat(
['storage', 'id', poolName, 'dataset', 'acl', datasetId],
), { queryParams: { homeShare } });
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment