From 6efbd5dbc4196c53c7ffecf6348240bbd942838b Mon Sep 17 00:00:00 2001 From: undsoft Date: Fri, 3 Jan 2025 19:20:06 -0500 Subject: [PATCH] NAS-133394: More strict null fixes --- src/app/interfaces/enclosure.interface.ts | 12 +- .../auth/two-factor-guard.service.spec.ts | 4 +- src/app/modules/empty/empty.service.ts | 2 +- .../ix-combobox/ix-combobox.component.ts | 2 +- .../components/ix-input/ix-input.component.ts | 2 +- .../services/form-error-handler.service.ts | 4 + .../password-validation.ts | 12 +- src/app/modules/jobs/store/job.reducer.ts | 4 +- .../modal-header/modal-header.component.html | 2 +- .../old-modal-header.component.html | 2 +- .../custom-app-form.component.ts | 6 +- .../installed-app-badge.component.html | 2 +- .../google-drive-provider-form.component.ts | 2 +- .../pcloud-provider-form.component.ts | 2 +- .../ssh-keypair-form.component.ts | 12 +- .../acmedns-form/acmedns-form.component.ts | 6 +- .../certificate-options.component.ts | 6 +- .../groups/group-form/group-form.component.ts | 34 ++-- .../privilege-form.component.ts | 18 +- .../api-key-form/api-key-form.component.ts | 35 ++-- .../user-api-keys/user-api-keys.component.ts | 5 +- .../users/user-form/user-form.component.ts | 38 ++-- .../dashboard/dashboard.component.html | 2 +- .../widget-group-slot-form.component.ts | 8 +- .../backup-task-tile.component.html | 2 +- .../widget-storage.component.ts | 8 +- .../widget-sys-info-active.component.html | 8 +- .../widget-sys-info-passive.component.html | 8 +- .../widget-sys-info-passive.component.ts | 2 +- .../widget-system-image.component.html | 8 +- .../widget-system-uptime.component.ts | 2 +- ...up-restore-from-snapshot-form.component.ts | 8 +- .../cloud-backup-form.component.ts | 20 +- .../cloudsync-form.component.ts | 77 ++++---- .../target-section.component.ts | 29 +-- .../transport-section.component.ts | 19 +- .../scrub-task-form.component.ts | 8 +- .../smart-task-form.component.ts | 18 +- .../snapshot-task-form.component.ts | 6 +- .../dataset-capacity-settings.component.ts | 38 ++-- .../space-management-chart.component.ts | 4 +- .../encryption-section.component.ts | 18 +- .../name-and-options-section.component.ts | 20 +- .../other-options-section.component.ts | 10 +- .../dataset-management.component.html | 6 +- .../dataset-quota-add-form.component.ts | 20 +- .../dataset-quota-edit-form.component.ts | 2 +- .../zvol-form/zvol-form.component.ts | 14 +- .../edit-nfs-ace/edit-nfs-ace.component.ts | 6 +- .../edit-posix-ace.component.ts | 10 +- .../select-preset-modal.component.ts | 3 + .../dataset-acl-editor.component.html | 6 +- .../kerberos-keytabs-form.component.ts | 6 +- .../kerberos-realms-form.component.ts | 6 +- .../directory-services.component.ts | 6 +- .../job-logs-row/job-logs-row.component.html | 4 +- src/app/pages/jobs/jobs-list.component.html | 4 +- .../configuration/configuration.component.ts | 8 +- .../ipmi-form/ipmi-form.component.ts | 8 +- .../static-route-form.component.ts | 6 +- .../reporting-exporters-form.component.ts | 6 +- .../reporting-exporters-list.component.html | 2 +- .../line-chart/line-chart.component.ts | 47 ++--- .../components/report/report.component.html | 4 +- .../components/report/report.component.ts | 5 +- .../reports-global-controls.component.ts | 6 +- .../service-ssh/service-ssh.component.ts | 12 +- .../service-ups/service-ups.component.ts | 34 ++-- .../iscsi-card/iscsi-card.component.html | 4 +- .../iscsi-card/iscsi-card.component.ts | 6 +- .../nfs-card/nfs-card.component.html | 2 +- .../smb-card/smb-card.component.html | 2 +- .../smb-card/smb-card.component.ts | 2 +- .../authorized-access-form.component.ts | 8 +- .../extent-wizard-step.component.ts | 2 +- .../protocol-options-wizard-step.component.ts | 2 +- .../target-wizard-step.component.ts | 2 +- .../associated-extents-card.component.html | 4 +- .../target-form/target-form.component.ts | 6 +- .../nfs/nfs-form/nfs-form.component.ts | 20 +- .../smb/smb-form/smb-form.component.ts | 9 +- .../smb/smb-list/smb-list.component.html | 2 +- .../signin-form/signin-form.component.ts | 8 +- .../dashboard-pool.component.html | 10 +- .../pool-card-icon.component.spec.ts | 11 +- .../pool-card-icon.component.ts | 2 +- .../pool-usage-card.component.html | 2 +- .../set-dedup-quota.component.ts | 4 +- .../unused-disk-card.component.ts | 2 +- .../modules/devices/devices.component.html | 4 +- .../disk-bulk-edit.component.ts | 6 +- .../disk-form/disk-form.component.ts | 10 +- .../disk-list/disk-list.component.ts | 2 +- .../manual-selection-vdev.component.html | 2 +- .../manual-selection-vdev.component.ts | 5 +- .../normal-selection.component.html | 2 +- .../audit/audit-form/audit-form.component.ts | 15 +- .../cron/cron-form/cron-form.component.ts | 6 +- .../init-shutdown-form.component.ts | 6 +- .../tunable-form/tunable-form.component.ts | 2 +- .../alert-config-form.component.ts | 4 + .../alert-service/alert-service.component.ts | 2 +- .../snmp-trap-service.component.ts | 2 +- .../bootenv-status.component.html | 2 +- .../disk-details-overview.component.spec.ts | 13 +- .../disk-details-overview.component.ts | 7 +- .../enclosure/services/enclosure.store.ts | 12 +- .../gui/gui-form/gui-form.component.ts | 2 +- .../manual-update-form.component.ts | 6 +- .../pages/system/update/update.component.html | 2 +- .../vm/vm-edit-form/vm-edit-form.component.ts | 2 +- src/assets/i18n/ko.json | 186 +++++++++--------- 112 files changed, 601 insertions(+), 575 deletions(-) diff --git a/src/app/interfaces/enclosure.interface.ts b/src/app/interfaces/enclosure.interface.ts index 166f4df89bd..8f56834e730 100644 --- a/src/app/interfaces/enclosure.interface.ts +++ b/src/app/interfaces/enclosure.interface.ts @@ -80,17 +80,17 @@ export interface DashboardEnclosureSlot { descriptor: string; status: EnclosureStatus; dev: string | null; - supports_identify_light?: boolean; + supports_identify_light: boolean; drive_bay_light_status: DriveBayLightStatus | null; - size?: number | null; - model?: string | null; + size: number | null; + model: string | null; is_top: boolean; is_front: boolean; is_rear: boolean; is_internal: boolean; - serial?: string | null; - type?: DiskType | null; - rotationrate?: number | null; + serial: string | null; + type: DiskType | null; + rotationrate: number | null; pool_info: EnclosureSlotPoolInfo | null; } diff --git a/src/app/modules/auth/two-factor-guard.service.spec.ts b/src/app/modules/auth/two-factor-guard.service.spec.ts index 721b59de6fb..ef42d9237d7 100644 --- a/src/app/modules/auth/two-factor-guard.service.spec.ts +++ b/src/app/modules/auth/two-factor-guard.service.spec.ts @@ -10,8 +10,8 @@ describe('TwoFactorGuardService', () => { let spectator: SpectatorService; const isAuthenticated$ = new BehaviorSubject(false); - const userTwoFactorConfig$ = new BehaviorSubject(null as UserTwoFactorConfig); - const getGlobalTwoFactorConfig = jest.fn(() => of(null as GlobalTwoFactorConfig)); + const userTwoFactorConfig$ = new BehaviorSubject(null); + const getGlobalTwoFactorConfig = jest.fn(() => of(null as GlobalTwoFactorConfig | null)); const hasRole$ = new BehaviorSubject(false); const createService = createServiceFactory({ diff --git a/src/app/modules/empty/empty.service.ts b/src/app/modules/empty/empty.service.ts index c8f43339567..e3154eca5de 100644 --- a/src/app/modules/empty/empty.service.ts +++ b/src/app/modules/empty/empty.service.ts @@ -9,7 +9,7 @@ import { EmptyConfig } from 'app/interfaces/empty-config.interface'; export class EmptyService { constructor(private translate: TranslateService) { } - defaultEmptyConfig(type: EmptyType): EmptyConfig { + defaultEmptyConfig(type?: EmptyType | null): EmptyConfig { switch (type) { case EmptyType.Loading: return { diff --git a/src/app/modules/forms/ix-forms/components/ix-combobox/ix-combobox.component.ts b/src/app/modules/forms/ix-forms/components/ix-combobox/ix-combobox.component.ts index a1d01d34259..5fd868936ae 100644 --- a/src/app/modules/forms/ix-forms/components/ix-combobox/ix-combobox.component.ts +++ b/src/app/modules/forms/ix-forms/components/ix-combobox/ix-combobox.component.ts @@ -74,7 +74,7 @@ export class IxComboboxComponent implements ControlValueAccessor, OnInit { }); private readonly inputElementRef: Signal> = viewChild.required('ixInput', { read: ElementRef }); - private readonly autoCompleteRef = viewChild('auto', { read: MatAutocomplete }); + private readonly autoCompleteRef = viewChild.required('auto', { read: MatAutocomplete }); private readonly autocompleteTrigger = viewChild(MatAutocompleteTrigger); options: Option[] = []; diff --git a/src/app/modules/forms/ix-forms/components/ix-input/ix-input.component.ts b/src/app/modules/forms/ix-forms/components/ix-input/ix-input.component.ts index 7cb8525d857..1c9adee1e70 100644 --- a/src/app/modules/forms/ix-forms/components/ix-input/ix-input.component.ts +++ b/src/app/modules/forms/ix-forms/components/ix-input/ix-input.component.ts @@ -75,7 +75,7 @@ export class IxInputComponent implements ControlValueAccessor, OnInit, OnChanges /** If formatted value returned by parseAndFormatInput has non-numeric letters * and input 'type' is a number, the input will stay empty on the form */ readonly format = input<(value: string | number) => string>(); - readonly parse = input<(value: string | number) => string | number>(); + readonly parse = input<(value: string | number) => string | number | null>(); readonly inputElementRef: Signal> = viewChild.required('ixInput', { read: ElementRef }); diff --git a/src/app/modules/forms/ix-forms/services/form-error-handler.service.ts b/src/app/modules/forms/ix-forms/services/form-error-handler.service.ts index ddde9f6ce8f..cd593d49a5e 100644 --- a/src/app/modules/forms/ix-forms/services/form-error-handler.service.ts +++ b/src/app/modules/forms/ix-forms/services/form-error-handler.service.ts @@ -72,6 +72,10 @@ export class FormErrorHandlerService { const extra = (error as ApiError).extra as string[][]; for (const extraItem of extra) { const field = extraItem[0].split('.').pop(); + if (!field) { + return; + } + const errorMessage = extraItem[1]; const control = this.getFormField(formGroup, field, fieldsMap); diff --git a/src/app/modules/forms/ix-forms/validators/password-validation/password-validation.ts b/src/app/modules/forms/ix-forms/validators/password-validation/password-validation.ts index 193a561f8f4..f9ad068c150 100644 --- a/src/app/modules/forms/ix-forms/validators/password-validation/password-validation.ts +++ b/src/app/modules/forms/ix-forms/validators/password-validation/password-validation.ts @@ -25,19 +25,19 @@ export function matchOthersFgValidator( } } if (errFields.length) { - fg.get(controlName).setErrors({ + subjectControl.setErrors({ matchOther: errMsg ? { message: errMsg } : true, }); return { [controlName]: { matchOther: errMsg ? { message: errMsg } : true }, }; } - let prevErrors = { ...fg.get(controlName).errors }; + let prevErrors = { ...subjectControl.errors }; delete prevErrors.matchOther; if (isEmpty(prevErrors)) { prevErrors = null; } - fg.get(controlName).setErrors(prevErrors); + subjectControl.setErrors(prevErrors); return null; }; } @@ -66,19 +66,19 @@ export function doesNotEqualFgValidator( } } if (errFields.length) { - fg.get(controlName).setErrors({ + subjectControl.setErrors({ matchesOther: errMsg ? { message: errMsg } : true, }); return { [controlName]: { matchesOther: errMsg ? { message: errMsg } : true }, }; } - let prevErrors = { ...fg.get(controlName).errors }; + let prevErrors = { ...subjectControl.errors }; delete prevErrors.matchesOther; if (isEmpty(prevErrors)) { prevErrors = null; } - fg.get(controlName).setErrors(prevErrors); + subjectControl.setErrors(prevErrors); return null; }; } diff --git a/src/app/modules/jobs/store/job.reducer.ts b/src/app/modules/jobs/store/job.reducer.ts index 01fba0807e5..6b3fd7714dc 100644 --- a/src/app/modules/jobs/store/job.reducer.ts +++ b/src/app/modules/jobs/store/job.reducer.ts @@ -11,7 +11,7 @@ import { jobIndicatorPressed } from 'app/store/topbar/topbar.actions'; export interface JobsState extends EntityState { isLoading: boolean; isPanelOpen: boolean; - error: string; + error: string | null; } export const adapter = createEntityAdapter({ @@ -19,7 +19,7 @@ export const adapter = createEntityAdapter({ sortComparer: (a, b) => b.time_started.$date - a.time_started.$date, }); -export const jobsInitialState = adapter.getInitialState({ +export const jobsInitialState: JobsState = adapter.getInitialState({ isLoading: false, isPanelOpen: false, error: null, diff --git a/src/app/modules/slide-ins/components/modal-header/modal-header.component.html b/src/app/modules/slide-ins/components/modal-header/modal-header.component.html index c589048be3d..274c46e8a6d 100644 --- a/src/app/modules/slide-ins/components/modal-header/modal-header.component.html +++ b/src/app/modules/slide-ins/components/modal-header/modal-header.component.html @@ -16,7 +16,7 @@ }

{{ title() | translate }} - @if (requiredRoles()?.length && !(hasRequiredRoles | async)) { + @if (requiredRoles().length && !(hasRequiredRoles | async)) { }

diff --git a/src/app/modules/slide-ins/components/old-modal-header/old-modal-header.component.html b/src/app/modules/slide-ins/components/old-modal-header/old-modal-header.component.html index 788c3fdbf79..62be07d2edd 100644 --- a/src/app/modules/slide-ins/components/old-modal-header/old-modal-header.component.html +++ b/src/app/modules/slide-ins/components/old-modal-header/old-modal-header.component.html @@ -1,7 +1,7 @@

{{ title() | translate }} - @if (requiredRoles()?.length && !(hasRequiredRoles() | async)) { + @if (requiredRoles().length && !(hasRequiredRoles() | async)) { }

diff --git a/src/app/pages/apps/components/custom-app-form/custom-app-form.component.ts b/src/app/pages/apps/components/custom-app-form/custom-app-form.component.ts index a7e57ccbe9b..ca78fe90f99 100644 --- a/src/app/pages/apps/components/custom-app-form/custom-app-form.component.ts +++ b/src/app/pages/apps/components/custom-app-form/custom-app-form.component.ts @@ -137,10 +137,10 @@ export class CustomAppFormComponent implements OnInit { ).subscribe({ next: () => { this.slideInRef.close({ response: true, error: null }); - if (this.isNew()) { - this.router.navigate(['/apps', 'installed']); - } else { + if (this.existingApp) { this.router.navigate(['/apps', 'installed', this.existingApp.metadata.train, this.existingApp.name]); + } else { + this.router.navigate(['/apps', 'installed']); } }, error: (error: unknown) => { diff --git a/src/app/pages/apps/components/installed-app-badge/installed-app-badge.component.html b/src/app/pages/apps/components/installed-app-badge/installed-app-badge.component.html index fe02cdd79e1..4e07dcded79 100644 --- a/src/app/pages/apps/components/installed-app-badge/installed-app-badge.component.html +++ b/src/app/pages/apps/components/installed-app-badge/installed-app-badge.component.html @@ -1,7 +1,7 @@ {{ 'Installed' | translate }} diff --git a/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/google-drive-provider-form/google-drive-provider-form.component.ts b/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/google-drive-provider-form/google-drive-provider-form.component.ts index ac20f742106..8ff7b0a6414 100644 --- a/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/google-drive-provider-form/google-drive-provider-form.component.ts +++ b/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/google-drive-provider-form/google-drive-provider-form.component.ts @@ -29,7 +29,7 @@ import { ], }) export class GoogleDriveProviderFormComponent extends BaseProviderFormComponent implements AfterViewInit { - private readonly oauthComponent = viewChild(OauthProviderComponent); + private readonly oauthComponent = viewChild.required(OauthProviderComponent); form = this.formBuilder.group({ token: ['', Validators.required], diff --git a/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/pcloud-provider-form/pcloud-provider-form.component.ts b/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/pcloud-provider-form/pcloud-provider-form.component.ts index b3d47819562..5e655031cda 100644 --- a/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/pcloud-provider-form/pcloud-provider-form.component.ts +++ b/src/app/pages/credentials/backup-credentials/cloud-credentials-form/provider-forms/pcloud-provider-form/pcloud-provider-form.component.ts @@ -29,7 +29,7 @@ import { ], }) export class PcloudProviderFormComponent extends BaseProviderFormComponent implements AfterViewInit { - private readonly oauthComponent = viewChild(OauthProviderComponent); + private readonly oauthComponent = viewChild.required(OauthProviderComponent); form = this.formBuilder.nonNullable.group({ token: ['', Validators.required], diff --git a/src/app/pages/credentials/backup-credentials/ssh-keypair-form/ssh-keypair-form.component.ts b/src/app/pages/credentials/backup-credentials/ssh-keypair-form/ssh-keypair-form.component.ts index d37545b5fca..6890340a3a1 100644 --- a/src/app/pages/credentials/backup-credentials/ssh-keypair-form/ssh-keypair-form.component.ts +++ b/src/app/pages/credentials/backup-credentials/ssh-keypair-form/ssh-keypair-form.component.ts @@ -156,16 +156,16 @@ export class SshKeypairFormComponent implements OnInit { this.isFormLoading = true; let request$: Observable; - if (this.isNew) { - request$ = this.api.call('keychaincredential.create', [{ - ...commonBody, - type: KeychainCredentialType.SshKeyPair, - }]); - } else { + if (this.editingKeypair) { request$ = this.api.call('keychaincredential.update', [ this.editingKeypair.id, commonBody, ]); + } else { + request$ = this.api.call('keychaincredential.create', [{ + ...commonBody, + type: KeychainCredentialType.SshKeyPair, + }]); } request$.pipe(untilDestroyed(this)).subscribe({ diff --git a/src/app/pages/credentials/certificates-dash/forms/acmedns-form/acmedns-form.component.ts b/src/app/pages/credentials/certificates-dash/forms/acmedns-form/acmedns-form.component.ts index b35e10f0707..0440cf41187 100644 --- a/src/app/pages/credentials/certificates-dash/forms/acmedns-form/acmedns-form.component.ts +++ b/src/app/pages/credentials/certificates-dash/forms/acmedns-form/acmedns-form.component.ts @@ -216,13 +216,13 @@ export class AcmednsFormComponent implements OnInit { this.isLoading = true; let request$: Observable; - if (this.isNew) { - request$ = this.api.call('acme.dns.authenticator.create', [values]); - } else { + if (this.editingAcmedns) { request$ = this.api.call('acme.dns.authenticator.update', [ this.editingAcmedns.id, values, ]); + } else { + request$ = this.api.call('acme.dns.authenticator.create', [values]); } request$.pipe(untilDestroyed(this)).subscribe({ diff --git a/src/app/pages/credentials/certificates-dash/forms/common-steps/certificate-options/certificate-options.component.ts b/src/app/pages/credentials/certificates-dash/forms/common-steps/certificate-options/certificate-options.component.ts index 125e7ba803c..59f049ddbbe 100644 --- a/src/app/pages/credentials/certificates-dash/forms/common-steps/certificate-options/certificate-options.component.ts +++ b/src/app/pages/credentials/certificates-dash/forms/common-steps/certificate-options/certificate-options.component.ts @@ -48,8 +48,8 @@ export class CertificateOptionsComponent implements OnInit, OnChanges, SummaryPr hasSignedBy = input(false); hasLifetime = input(false); - form = this.formBuilder.group({ - signedby: [null as number], + form = this.formBuilder.nonNullable.group({ + signedby: [null as number | null], key_type: [CertificateKeyType.Rsa], key_length: [2048], ec_curve: ['BrainpoolP384R1'], @@ -90,7 +90,7 @@ export class CertificateOptionsComponent implements OnInit, OnChanges, SummaryPr } getSummary(): SummarySection { - const values = this.form.value; + const values = this.form.getRawValue(); const signingAuthority = this.signingAuthorities.find((option) => option.value === values.signedby); const summary: SummarySection = []; diff --git a/src/app/pages/credentials/groups/group-form/group-form.component.ts b/src/app/pages/credentials/groups/group-form/group-form.component.ts index 8cd22feae8d..8025362ec8a 100644 --- a/src/app/pages/credentials/groups/group-form/group-form.component.ts +++ b/src/app/pages/credentials/groups/group-form/group-form.component.ts @@ -131,21 +131,13 @@ export class GroupFormComponent implements OnInit { setupForm(): void { this.setFormRelations(); - if (this.isNew) { - this.api.call('group.get_next_gid').pipe(untilDestroyed(this)).subscribe((nextId) => { - this.form.patchValue({ - gid: nextId, - }); - this.cdr.markForCheck(); - }); - this.setNamesInUseValidator(); - } else { + if (this.editingGroup) { this.form.controls.gid.disable(); this.form.patchValue({ gid: this.editingGroup.gid, name: this.editingGroup.group, - sudo_commands: this.editingGroup.sudo_commands.includes(allCommands) ? [] : this.editingGroup.sudo_commands, - sudo_commands_all: this.editingGroup.sudo_commands.includes(allCommands), + sudo_commands: this.editingGroup.sudo_commands?.includes(allCommands) ? [] : this.editingGroup.sudo_commands, + sudo_commands_all: !!this.editingGroup.sudo_commands?.includes(allCommands), sudo_commands_nopasswd: this.editingGroup.sudo_commands_nopasswd?.includes(allCommands) ? [] : this.editingGroup.sudo_commands_nopasswd, @@ -153,6 +145,14 @@ export class GroupFormComponent implements OnInit { smb: this.editingGroup.smb, }); this.setNamesInUseValidator(this.editingGroup.group); + } else { + this.api.call('group.get_next_gid').pipe(untilDestroyed(this)).subscribe((nextId) => { + this.form.patchValue({ + gid: nextId, + }); + this.cdr.markForCheck(); + }); + this.setNamesInUseValidator(); } } @@ -167,16 +167,16 @@ export class GroupFormComponent implements OnInit { this.isFormLoading = true; let request$: Observable; - if (this.isNew) { - request$ = this.api.call('group.create', [{ - ...commonBody, - gid: values.gid, - }]); - } else { + if (this.editingGroup) { request$ = this.api.call('group.update', [ this.editingGroup.id, commonBody, ]); + } else { + request$ = this.api.call('group.create', [{ + ...commonBody, + gid: values.gid, + }]); } request$.pipe( diff --git a/src/app/pages/credentials/groups/privilege/privilege-form/privilege-form.component.ts b/src/app/pages/credentials/groups/privilege/privilege-form/privilege-form.component.ts index 78b8537861a..aaae85dfc8a 100644 --- a/src/app/pages/credentials/groups/privilege/privilege-form/privilege-form.component.ts +++ b/src/app/pages/credentials/groups/privilege/privilege-form/privilege-form.component.ts @@ -75,7 +75,7 @@ export class PrivilegeFormComponent implements OnInit { protected readonly helptext = helptextPrivilege; protected readonly isEnterprise = toSignal(this.store$.select(selectIsEnterprise)); - protected existingPrivilege: Privilege; + protected existingPrivilege: Privilege | undefined; get isNew(): boolean { return !this.existingPrivilege; @@ -143,7 +143,7 @@ export class PrivilegeFormComponent implements OnInit { ngOnInit(): void { if (this.existingPrivilege) { - this.setPrivilegeForEdit(); + this.setPrivilegeForEdit(this.existingPrivilege); if (this.existingPrivilege.builtin_name) { this.form.controls.name.disable(); this.form.controls.roles.disable(); @@ -151,13 +151,13 @@ export class PrivilegeFormComponent implements OnInit { } } - setPrivilegeForEdit(): void { + setPrivilegeForEdit(existingPrivilege: Privilege): void { this.form.patchValue({ - ...this.existingPrivilege, - local_groups: this.existingPrivilege.local_groups.map( + ...existingPrivilege, + local_groups: existingPrivilege.local_groups.map( (group) => group.group || this.translate.instant('Missing group - {gid}', { gid: group.gid }), ), - ds_groups: this.existingPrivilege.ds_groups.map((group) => group.group), + ds_groups: existingPrivilege.ds_groups.map((group) => group.group), }); this.cdr.markForCheck(); } @@ -171,10 +171,10 @@ export class PrivilegeFormComponent implements OnInit { this.isLoading = true; let request$: Observable; - if (this.isNew) { - request$ = this.api.call('privilege.create', [values]); - } else { + if (this.existingPrivilege) { request$ = this.api.call('privilege.update', [this.existingPrivilege.id, values]); + } else { + request$ = this.api.call('privilege.create', [values]); } request$.pipe( diff --git a/src/app/pages/credentials/users/user-api-keys/components/api-key-form/api-key-form.component.ts b/src/app/pages/credentials/users/user-api-keys/components/api-key-form/api-key-form.component.ts index 497ba13d24f..51b6267b953 100644 --- a/src/app/pages/credentials/users/user-api-keys/components/api-key-form/api-key-form.component.ts +++ b/src/app/pages/credentials/users/user-api-keys/components/api-key-form/api-key-form.component.ts @@ -3,13 +3,13 @@ import { signal, } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Validators, ReactiveFormsModule, NonNullableFormBuilder } from '@angular/forms'; import { MatButton } from '@angular/material/button'; import { MatCard, MatCardContent } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateModule } from '@ngx-translate/core'; -import { map } from 'rxjs'; +import { filter, map } from 'rxjs'; import { RequiresRolesDirective } from 'app/directives/requires-roles/requires-roles.directive'; import { Role } from 'app/enums/role.enum'; import { ParamsBuilder } from 'app/helpers/params-builder/params-builder.class'; @@ -61,7 +61,7 @@ import { }) export class ApiKeyFormComponent implements OnInit { protected readonly minDateToday = new Date(); - protected readonly editingRow = signal(null); + protected readonly editingRow = signal(undefined); protected readonly isNew = computed(() => !this.editingRow()); protected readonly isLoading = signal(false); protected readonly requiredRoles = [Role.ApiKeyWrite, Role.SharingAdmin, Role.ReadonlyAdmin]; @@ -70,7 +70,11 @@ export class ApiKeyFormComponent implements OnInit { () => this.username() === this.form.value.username || this.isFullAdmin(), ); - protected readonly currentUsername$ = this.authService.user$.pipe(map((user) => user.pw_name)); + protected readonly currentUsername$ = this.authService.user$.pipe( + filter((user) => !!user), + map((user) => user.pw_name), + ); + protected readonly username = toSignal(this.currentUsername$); protected readonly tooltips = { name: helptextApiKeys.name.tooltip, @@ -83,7 +87,7 @@ export class ApiKeyFormComponent implements OnInit { protected readonly form = this.fb.group({ name: ['', [Validators.required, Validators.minLength(1), Validators.maxLength(200)]], username: ['', [Validators.required]], - expires_at: [null as Date], + expires_at: [null as Date | null], nonExpiring: [true], reset: [false], }); @@ -105,7 +109,7 @@ export class ApiKeyFormComponent implements OnInit { ]).pipe(map((keys) => keys.map((key) => key.name))); constructor( - private fb: FormBuilder, + private fb: NonNullableFormBuilder, private matDialog: MatDialog, private api: ApiService, private loader: AppLoaderService, @@ -117,17 +121,18 @@ export class ApiKeyFormComponent implements OnInit { } ngOnInit(): void { - if (this.isNew()) { - this.addForbiddenNamesValidator(); - this.setCurrentUsername(); - } else { + const editingRow = this.editingRow(); + if (editingRow) { this.form.patchValue({ - ...this.editingRow(), - expires_at: this.editingRow().expires_at?.$date - ? new Date(this.editingRow().expires_at.$date) + ...editingRow, + expires_at: editingRow.expires_at?.$date + ? new Date(editingRow.expires_at.$date) : null, - nonExpiring: !this.editingRow()?.expires_at?.$date, + nonExpiring: !editingRow.expires_at?.$date, }); + } else { + this.addForbiddenNamesValidator(); + this.setCurrentUsername(); } this.handleNonExpiringChanges(); } @@ -136,7 +141,7 @@ export class ApiKeyFormComponent implements OnInit { this.isLoading.set(true); const { name, username, reset, nonExpiring, - } = this.form.value; + } = this.form.getRawValue(); const expiresAt = nonExpiring ? null : { $date: this.form.value.expires_at.getTime() }; diff --git a/src/app/pages/credentials/users/user-api-keys/user-api-keys.component.ts b/src/app/pages/credentials/users/user-api-keys/user-api-keys.component.ts index 52a834ca8ca..1c3cad18076 100644 --- a/src/app/pages/credentials/users/user-api-keys/user-api-keys.component.ts +++ b/src/app/pages/credentials/users/user-api-keys/user-api-keys.component.ts @@ -117,7 +117,10 @@ export class UserApiKeysComponent implements OnInit { hidden: (row) => of(row.revoked), onClick: (row) => this.openForm(row), disabled: (row) => this.authService.hasRole([Role.FullAdmin]).pipe( - withLatestFrom(this.authService.user$.pipe(map((user) => user.pw_name))), + withLatestFrom(this.authService.user$.pipe( + filter((user) => !!user), + map((user) => user.pw_name), + )), map(([isFullAdmin, username]) => !isFullAdmin && row.username !== username), ), }, diff --git a/src/app/pages/credentials/users/user-form/user-form.component.ts b/src/app/pages/credentials/users/user-form/user-form.component.ts index 98d6a1c3c48..85114093688 100644 --- a/src/app/pages/credentials/users/user-form/user-form.component.ts +++ b/src/app/pages/credentials/users/user-form/user-form.component.ts @@ -104,7 +104,7 @@ export class UserFormComponent implements OnInit { } get isEditingBuiltinUser(): boolean { - return !this.isNewUser && this.editingUser.builtin; + return !this.isNewUser && Boolean(this.editingUser?.builtin); } form = this.fb.group({ @@ -186,14 +186,7 @@ export class UserFormComponent implements OnInit { const homeCreate = this.form.value.home_create; const home = this.form.value.home; const homeMode = this.form.value.home_mode; - if (this.isNewUser) { - if (!homeCreate && home !== defaultHomePath) { - return this.translate.instant( - 'With this configuration, the existing directory {path} will be used as a home directory without creating a new directory for the user.', - { path: '\'' + this.form.value.home + '\'' }, - ); - } - } else { + if (this.editingUser) { if (this.editingUser.immutable || home === defaultHomePath) { return ''; } @@ -209,6 +202,11 @@ export class UserFormComponent implements OnInit { { path: '\'' + this.form.value.home + '\'' }, ); } + } else if (!homeCreate && home !== defaultHomePath) { + return this.translate.instant( + 'With this configuration, the existing directory {path} will be used as a home directory without creating a new directory for the user.', + { path: '\'' + this.form.value.home + '\'' }, + ); } return ''; } @@ -310,10 +308,10 @@ export class UserFormComponent implements OnInit { this.form.controls.sudo_commands_nopasswd.disabledWhile(this.form.controls.sudo_commands_nopasswd_all.value$), ); - if (this.isNewUser) { - this.setupNewUserForm(); - } else { + if (this.editingUser) { this.setupEditUserForm(this.editingUser); + } else { + this.setupNewUserForm(); } } @@ -356,14 +354,7 @@ export class UserFormComponent implements OnInit { let request$: Observable; let nextRequest$: Observable; - if (this.isNewUser) { - request$ = this.api.call('user.create', [{ - ...body, - group_create: values.group_create, - password: values.password, - uid: values.uid, - }]); - } else { + if (this.editingUser) { const passwordNotEmpty = values.password !== '' && values.password_conf !== ''; if (passwordNotEmpty && !values.password_disabled) { body.password = values.password; @@ -376,6 +367,13 @@ export class UserFormComponent implements OnInit { } else { request$ = this.api.call('user.update', [this.editingUser.id, body]); } + } else { + request$ = this.api.call('user.create', [{ + ...body, + group_create: values.group_create, + password: values.password, + uid: values.uid, + }]); } request$.pipe( diff --git a/src/app/pages/dashboard/components/dashboard/dashboard.component.html b/src/app/pages/dashboard/components/dashboard/dashboard.component.html index 427dccaae90..742a868f96d 100644 --- a/src/app/pages/dashboard/components/dashboard/dashboard.component.html +++ b/src/app/pages/dashboard/components/dashboard/dashboard.component.html @@ -1,4 +1,4 @@ - + @if (!isEditing()) { diff --git a/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts b/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts index aa503517719..b9d020a655f 100644 --- a/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts +++ b/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts @@ -79,7 +79,7 @@ export class IscsiCardComponent implements OnInit { Role.SharingWrite, ]; - targets = signal(null); + targets = signal(null); protected readonly searchableElements = iscsiCardElements; @@ -98,9 +98,7 @@ export class IscsiCardComponent implements OnInit { title: this.translate.instant('Mode'), propertyName: 'mode', hidden: true, - getValue: (row) => (iscsiTargetModeNames.has(row.mode) - ? this.translate.instant(iscsiTargetModeNames.get(row.mode)) - : row.mode || '-'), + getValue: (row) => this.translate.instant(iscsiTargetModeNames.get(row.mode) || row.mode) || '-', }), actionsColumn({ actions: [ diff --git a/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.html b/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.html index db4c53e705d..a810dc527d1 100644 --- a/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.html +++ b/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.html @@ -10,7 +10,7 @@

diff --git a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.html b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.html index 146d40576d9..4b71c159cf6 100644 --- a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.html +++ b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.html @@ -10,7 +10,7 @@

diff --git a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts index b7f71077361..fe3437277fe 100644 --- a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts +++ b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts @@ -123,7 +123,7 @@ export class SmbCardComponent implements OnInit { { iconName: iconMarker('edit'), tooltip: this.translate.instant('Edit'), - disabled: (row) => this.loadingMap$.pipe(map((ids) => ids.get(row.id))), + disabled: (row) => this.loadingMap$.pipe(map((ids) => Boolean(ids.get(row.id)))), onClick: (row) => this.openForm(row), }, { diff --git a/src/app/pages/sharing/iscsi/authorized-access/authorized-access-form/authorized-access-form.component.ts b/src/app/pages/sharing/iscsi/authorized-access/authorized-access-form/authorized-access-form.component.ts index 56a84a0d495..15cf305ad44 100644 --- a/src/app/pages/sharing/iscsi/authorized-access/authorized-access-form/authorized-access-form.component.ts +++ b/src/app/pages/sharing/iscsi/authorized-access/authorized-access-form/authorized-access-form.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit, } from '@angular/core'; -import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Validators, ReactiveFormsModule, NonNullableFormBuilder } from '@angular/forms'; import { MatButton } from '@angular/material/button'; import { MatCard, MatCardContent } from '@angular/material/card'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -64,7 +64,7 @@ export class AuthorizedAccessFormComponent implements OnInit { } form = this.formBuilder.group({ - tag: [null as number, [Validators.required, Validators.min(0)]], + tag: [null as number | null, [Validators.required, Validators.min(0)]], user: ['', Validators.required], secret: ['', [ Validators.minLength(12), @@ -134,7 +134,7 @@ export class AuthorizedAccessFormComponent implements OnInit { constructor( private translate: TranslateService, - private formBuilder: FormBuilder, + private formBuilder: NonNullableFormBuilder, private errorHandler: FormErrorHandlerService, private cdr: ChangeDetectorRef, private api: ApiService, @@ -187,7 +187,7 @@ export class AuthorizedAccessFormComponent implements OnInit { } onSubmit(): void { - const values = this.form.value; + const values = this.form.getRawValue(); const payload = { tag: values.tag, user: values.user, diff --git a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/extent-wizard-step/extent-wizard-step.component.ts b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/extent-wizard-step/extent-wizard-step.component.ts index 866082a8d85..60c4d17ae90 100644 --- a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/extent-wizard-step/extent-wizard-step.component.ts +++ b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/extent-wizard-step/extent-wizard-step.component.ts @@ -33,7 +33,7 @@ import { IscsiService } from 'app/services/iscsi.service'; ], }) export class ExtentWizardStepComponent implements OnInit { - readonly form = input(); + readonly form = input.required(); readonly helptextSharingIscsi = helptextSharingIscsi; readonly fileNodeProvider = this.filesystemService.getFilesystemNodeProvider(); diff --git a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/protocol-options-wizard-step/protocol-options-wizard-step.component.ts b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/protocol-options-wizard-step/protocol-options-wizard-step.component.ts index 70fea6402b7..c52058a9ff6 100644 --- a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/protocol-options-wizard-step/protocol-options-wizard-step.component.ts +++ b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/protocol-options-wizard-step/protocol-options-wizard-step.component.ts @@ -35,7 +35,7 @@ import { IscsiService } from 'app/services/iscsi.service'; ], }) export class ProtocolOptionsWizardStepComponent implements OnInit { - form = input(); + form = input.required(); isFibreChannelMode = input(false); readonly helptextSharingIscsi = helptextSharingIscsi; diff --git a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/target-wizard-step/target-wizard-step.component.ts b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/target-wizard-step/target-wizard-step.component.ts index 300d3ee1bd2..db20ad88fe1 100644 --- a/src/app/pages/sharing/iscsi/iscsi-wizard/steps/target-wizard-step/target-wizard-step.component.ts +++ b/src/app/pages/sharing/iscsi/iscsi-wizard/steps/target-wizard-step/target-wizard-step.component.ts @@ -28,7 +28,7 @@ import { IscsiService } from 'app/services/iscsi.service'; ], }) export class TargetWizardStepComponent { - form = input(); + form = input.required(); readonly helptextSharingIscsi = helptextSharingIscsi; diff --git a/src/app/pages/sharing/iscsi/target/all-targets/target-details/associated-extents-card/associated-extents-card.component.html b/src/app/pages/sharing/iscsi/target/all-targets/target-details/associated-extents-card/associated-extents-card.component.html index 027ba6db32c..bf094042ff0 100644 --- a/src/app/pages/sharing/iscsi/target/all-targets/target-details/associated-extents-card/associated-extents-card.component.html +++ b/src/app/pages/sharing/iscsi/target/all-targets/target-details/associated-extents-card/associated-extents-card.component.html @@ -9,13 +9,13 @@

} @else {