Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/pages/sharing/smb/smb-form/smb-form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@
mat-button
type="button"
ixTest="toggle-advanced-options"
(click)="this.isAdvancedMode = !this.isAdvancedMode"
(click)="toggleAdvancedMode()"
>
{{ isAdvancedMode ? ('Basic Options' | translate) : ('Advanced Options' | translate) }}
</button>
Expand Down
30 changes: 28 additions & 2 deletions src/app/pages/sharing/smb/smb-form/smb-form.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,13 @@ describe('SmbFormComponent', () => {
form = await loader.getHarness(IxFormHarness);
api = spectator.inject(ApiService);

const advancedButton = await loader.getHarness(MatButtonHarness.with({ text: 'Advanced Options' }));
await advancedButton.click();
const advancedButton = await loader.getHarness(
MatButtonHarness.with({ selector: '[ixTest="toggle-advanced-options"]' }),
);
const advancedButtonText = await advancedButton.getText();
if (advancedButtonText.includes('Advanced Options')) {
await advancedButton.click();
}
}

const commonValues = {
Expand Down Expand Up @@ -563,6 +568,27 @@ describe('SmbFormComponent', () => {
});
});

describe('edit share with legacy audit logging', () => {
beforeEach(async () => {
await setupTest({
purpose: SmbSharePurpose.DefaultShare,
audit: {
enable: true,
watch_list: [],
ignore_list: [],
},
});
});

it('disables save when audit logging has no groups', async () => {
spectator.detectChanges();
await spectator.fixture.whenStable();

const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
expect(await saveButton.isDisabled()).toBe(true);
});
});

describe('edit FCP share', () => {
it('shows aapl_name_mangling checkbox as checked and disabled for FCP share', async () => {
await setupTest({
Expand Down
80 changes: 80 additions & 0 deletions src/app/pages/sharing/smb/smb-form/smb-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,29 @@ export class SmbFormComponent implements OnInit, AfterViewInit {
return this.isNewTimeMachineShare || this.isNewHomeShare || this.wasPathChanged || this.hasAddedAllowDenyHosts;
}

private readonly basicControlNames = new Set([
'purpose',
'path',
'name',
'comment',
'enabled',
'remote_path',
]);

private hasAdvancedErrorsInternal(): boolean {
return Object.entries(this.form.controls).some(([controlName, control]) => {
if (this.basicControlNames.has(controlName)) {
return false;
}

if (control.disabled) {
return false;
}

return control.invalid;
});
}

private isFieldEnabledForPurpose(fieldName: string, purpose: SmbSharePurpose): boolean {
return presetEnabledFields[purpose]?.includes(fieldName as never) ?? false;
}
Expand Down Expand Up @@ -273,6 +296,47 @@ export class SmbFormComponent implements OnInit, AfterViewInit {
};
}

private setupAuditValidation(): void {
const auditGroup = this.form.controls.audit;
const enableControl = auditGroup.controls.enable;

enableControl.valueChanges.pipe(untilDestroyed(this))
.subscribe(() => {
this.updateAuditValidationState();
});

this.updateAuditValidationState();
}

private updateAuditValidationState(): void {
const auditGroup = this.form.controls.audit;
const watchList = auditGroup.controls.watch_list.value ?? [];
const ignoreList = auditGroup.controls.ignore_list.value ?? [];

auditGroup.updateValueAndValidity({ emitEvent: true });

if (auditGroup.controls.enable.value && watchList.length === 0 && ignoreList.length === 0) {
auditGroup.markAllAsTouched();
}
}

private openAdvancedOptionsIfInvalid(): void {
this.form.updateValueAndValidity({ emitEvent: false });

if (this.hasAdvancedErrorsInternal()) {
this.isAdvancedMode = true;
this.updateAuditValidationState();
}
}

protected toggleAdvancedMode(): void {
this.isAdvancedMode = !this.isAdvancedMode;

if (this.isAdvancedMode) {
this.updateAuditValidationState();
}
}

protected form = this.formBuilder.group({
// Common for all share purposes
purpose: [SmbSharePurpose.DefaultShare as SmbSharePurpose | null],
Expand Down Expand Up @@ -381,12 +445,20 @@ export class SmbFormComponent implements OnInit, AfterViewInit {
this.setupMangleWarning();
this.setupPathControl();
this.setupAclControl();
this.setupAuditValidation();
this.openAdvancedOptionsIfInvalid();
}

ngAfterViewInit(): void {
this.form.controls.name.addAsyncValidators([
this.smbValidationService.validate(this.existingSmbShare?.name),
]);

// Ensure audit validation errors are visible after view init (edit mode included).
this.updateAuditValidationState();
if (this.hasAdvancedErrorsInternal()) {
this.isAdvancedMode = true;
}
}

private setupAclControl(): void {
Expand Down Expand Up @@ -629,6 +701,14 @@ export class SmbFormComponent implements OnInit, AfterViewInit {
}

protected submit(): void {
if (this.form.invalid || this.isAsyncValidatorPending) {
this.form.markAllAsTouched();
if (this.hasAdvancedErrorsInternal()) {
this.isAdvancedMode = true;
}
return;
}

const smbShare = { ...this.form.value } as SmbShare;
const purpose = smbShare.purpose;
const presetFields = presetEnabledFields[purpose] ?? [];
Expand Down
Loading