Skip to content

Commit

Permalink
NAS-133350: Fix some strict null issues (#11269)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored Jan 2, 2025
1 parent c076bae commit 9de2757
Show file tree
Hide file tree
Showing 114 changed files with 439 additions and 322 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ export class MockEnclosureGenerator {
platform: `TRUENAS-${this.config.controllerModel}`,
system_product: `TRUENAS-${this.config.controllerModel}`,
}
: null,
: undefined,
};
}

private addEnclosure(model: EnclosureModel): void {
const enclosure = enclosureMocks.find((mock) => mock.model === model);

if (!enclosure) {
throw new Error(`Enclosure model ${model} is not supported`);
}

this.enclosures.push(enclosure);
}

Expand Down
7 changes: 4 additions & 3 deletions src/app/directives/ui-search.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export class UiSearchDirective implements OnInit, OnDestroy {
const hierarchyItem = this.config().hierarchy?.[this.config().hierarchy.length - 1] || '';
const isSingleWord = hierarchyItem.trim().split(/\s+/).length === 1;

if (isSingleWord && this.config().synonyms?.length > 0) {
return this.config().synonyms.reduce((best, synonym) => {
const synonyms = this.config().synonyms;
if (isSingleWord && synonyms && Number(synonyms?.length) > 0) {
return synonyms.reduce((best, synonym) => {
const synonymWordCount = synonym.trim().split(/\s+/).length;
const bestWordCount = best.trim().split(/\s+/).length;
return synonymWordCount > bestWordCount ? synonym : best;
Expand Down Expand Up @@ -73,7 +74,7 @@ export class UiSearchDirective implements OnInit, OnDestroy {
private highlightElementAnchor(elementAnchor: string): void {
setTimeout(() => {
const rootNode = this.elementRef.nativeElement.getRootNode() as HTMLElement;
const anchorRef: HTMLElement = rootNode?.querySelector(`#${elementAnchor}`);
const anchorRef: HTMLElement | null = rootNode?.querySelector(`#${elementAnchor}`);

if (anchorRef) {
this.highlightAndClickElement(anchorRef);
Expand Down
2 changes: 1 addition & 1 deletion src/app/interfaces/enclosure.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export interface DashboardEnclosureSlot {
/**
* `drive_bay_number` is not an index and starts from 1
*/
drive_bay_number?: number;
drive_bay_number: number;
descriptor: string;
status: EnclosureStatus;
dev: string | null;
Expand Down
2 changes: 1 addition & 1 deletion src/app/interfaces/mail-config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface MailConfigUpdate {
smtp?: boolean;
fromemail: string;
fromname: string;
oauth: MailOauthConfig;
oauth: MailOauthConfig | null;
outgoingserver?: string;
pass?: string;
port?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ <h4 class="message">
}

@let unreadAlerts = unreadAlerts$ | async;
@if (!unreadAlerts.length) {
@if (!unreadAlerts?.length) {
<div class="no-alerts">
<ix-icon class="icon" name="info"></ix-icon>
<h4 class="message">{{ 'There are no alerts.' | translate }}</h4>
Expand Down Expand Up @@ -86,7 +86,7 @@ <h4 class="message">{{ 'There are no alerts.' | translate }}</h4>
}

@let dismissedAlerts = dismissedAlerts$ | async;
@if (dismissedAlerts.length) {
@if (dismissedAlerts?.length) {
<div class="alert-list dismissed-alerts">
@for (alert of dismissedAlerts; track alert.id; let last = $last) {
<div
Expand Down
10 changes: 5 additions & 5 deletions src/app/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import { adminUiInitialized } from 'app/store/admin-panel/admin.actions';
providedIn: 'root',
})
export class AuthService {
@LocalStorage() private token: string;
protected loggedInUser$ = new BehaviorSubject<LoggedInUser>(null);
@LocalStorage() private token: string | undefined | null;
protected loggedInUser$ = new BehaviorSubject<LoggedInUser | null>(null);

/**
* This is 10 seconds less than 300 seconds which is the default life
Expand All @@ -44,7 +44,7 @@ export class AuthService {

private latestTokenGenerated$ = new ReplaySubject<string | null>(1);
get authToken$(): Observable<string> {
return this.latestTokenGenerated$.asObservable().pipe(filter((token) => !!token));
return this.latestTokenGenerated$.asObservable().pipe(filter<string>((token) => !!token));
}

get hasAuthToken(): boolean {
Expand Down Expand Up @@ -188,10 +188,10 @@ export class AuthService {
);
}

refreshUser(): Observable<void> {
refreshUser(): Observable<undefined> {
this.loggedInUser$.next(null);
return this.getLoggedInUserInformation().pipe(
map(() => null),
map(() => undefined),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ export class ViewChartGaugeComponent implements AfterViewInit, OnChanges {
// Adjust group to compensate
const isFirefox: boolean = navigator.userAgent.toLowerCase().includes('firefox');
const offsetY = isFirefox ? 10 : 0;
const bbox = textGroup.node().getBBox();
const bbox = textGroup.node()?.getBBox();
if (!bbox) {
return;
}

const top = (height / 2) - (bbox.height / 2);
text.attr('x', width / 2)
.attr('y', top + offsetY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export class FormatDateTimePipe implements PipeTransform {

private checkFormatsFromLocalStorage(): void {
['dateFormat', 'timeFormat'].forEach((value) => {
if (this.window.localStorage[value]) {
const storedFormat = this.window.localStorage.getItem(value);
const storedFormat = this.window.localStorage.getItem(value);
if (storedFormat) {
try {
if (format(new Date(), storedFormat)) {
if (value === 'dateFormat') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<h1 mat-dialog-title>{{ (title | translate) || ('Loading' | translate) }}</h1>
<h1 mat-dialog-title>
{{ title ? (title | translate) : ('Loading' | translate) }}
</h1>
<mat-dialog-content class="job-dialog">
@if (showRealtimeLogs && realtimeLogs) {
<div class="logs-container">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export class JobProgressDialogComponent<T> implements OnInit, AfterViewChecked {

readonly JobState = JobState;

protected title: string;
protected description: string;
protected title: string | undefined;
protected description: string | undefined;
private realtimeLogsSubscribed = false;
protected realtimeLogs = '';
protected showMinimizeButton = true;
Expand Down Expand Up @@ -119,7 +119,7 @@ export class JobProgressDialogComponent<T> implements OnInit, AfterViewChecked {
this.showMinimizeButton = this.data?.canMinimize || false;
this.dialogRef.disableClose = !this.showMinimizeButton;

let logsSubscription: Subscription = null;
let logsSubscription: Subscription | null = null;
this.cdr.markForCheck();

this.data.job$.pipe(
Expand Down Expand Up @@ -175,7 +175,7 @@ export class JobProgressDialogComponent<T> implements OnInit, AfterViewChecked {
}

if (this.realtimeLogsSubscribed) {
logsSubscription.unsubscribe();
logsSubscription?.unsubscribe();
}
this.cdr.markForCheck();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import { ErrorHandlerService } from 'app/services/error-handler.service';
],
})
export class ErrorTemplateComponent {
private readonly errorMessageWrapper: Signal<ElementRef<HTMLElement>> = viewChild('errorMessageWrapper', { read: ElementRef });
private readonly errorMdContent: Signal<ElementRef<HTMLElement>> = viewChild('errorMdContent', { read: ElementRef });
private readonly errorBtPanel: Signal<ElementRef<HTMLElement>> = viewChild('errorBtPanel', { read: ElementRef });
private readonly errorBtText: Signal<ElementRef<HTMLElement>> = viewChild('errorBtText', { read: ElementRef });
private readonly errorMessageWrapper: Signal<ElementRef<HTMLElement>> = viewChild.required('errorMessageWrapper', { read: ElementRef });
private readonly errorMdContent: Signal<ElementRef<HTMLElement>> = viewChild.required('errorMdContent', { read: ElementRef });
private readonly errorBtPanel: Signal<ElementRef<HTMLElement> | undefined> = viewChild('errorBtPanel', { read: ElementRef });
private readonly errorBtText: Signal<ElementRef<HTMLElement> | undefined> = viewChild('errorBtText', { read: ElementRef });

readonly title = input<string>();
readonly message = input<string>();
Expand All @@ -51,29 +51,34 @@ export class ErrorTemplateComponent {
toggleOpen(): void {
const messageWrapper = this.errorMessageWrapper().nativeElement;
const content = this.errorMdContent().nativeElement;
const btPanel = this.errorBtPanel().nativeElement;
const txtarea = this.errorBtText().nativeElement;
const btPanel = this.errorBtPanel()?.nativeElement;
const txtarea = this.errorBtText()?.nativeElement;

this.isCloseMoreInfo = !this.isCloseMoreInfo;
if (!this.isCloseMoreInfo) {
messageWrapper.setAttribute('style', 'max-height: 63px; overflow: auto');
btPanel.setAttribute('style', 'width: 750px; max-height: calc(80vh - 240px)');
btPanel?.setAttribute('style', 'width: 750px; max-height: calc(80vh - 240px)');
} else {
content.removeAttribute('style');
btPanel.removeAttribute('style');
btPanel?.removeAttribute('style');
messageWrapper.removeAttribute('style');
txtarea.removeAttribute('style');
txtarea?.removeAttribute('style');
}
}

downloadLogs(): void {
this.api.call('core.job_download_logs', [this.logs().id, `${this.logs().id}.log`])
const logsId = this.logs()?.id;
if (!logsId) {
throw new Error('Missing logs id.');
}

this.api.call('core.job_download_logs', [logsId, `${logsId}.log`])
.pipe(this.errorHandler.catchError(), untilDestroyed(this))
.subscribe((url) => {
const mimetype = 'text/plain';
this.download.streamDownloadFile(url, `${this.logs().id}.log`, mimetype).pipe(untilDestroyed(this)).subscribe({
this.download.streamDownloadFile(url, `${logsId}.log`, mimetype).pipe(untilDestroyed(this)).subscribe({
next: (file) => {
this.download.downloadBlob(file, `${this.logs().id}.log`);
this.download.downloadBlob(file, `${logsId}.log`);
},
error: (error: unknown) => {
this.dialogService.error(this.errorHandler.parseError(error));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ <h3 mat-dialog-title>
</h3>

<div mat-dialog-content>
@if(thisNodeRebootReasons().length) {
@if(thisNodeRebootReasons()?.length) {
<div class="reasons">
<p class="dialog-message">
<span>{{ 'The local node must be rebooted because' | translate }}:</span>
Expand All @@ -14,7 +14,7 @@ <h3 mat-dialog-title>
</div>
}

@if(otherNodeRebootReasons().length) {
@if(otherNodeRebootReasons()?.length) {
<div class="reasons">
<p class="dialog-message">
<span>{{ 'The remote node must be rebooted because' | translate }}:</span>
Expand All @@ -26,7 +26,7 @@ <h3 mat-dialog-title>
}
</div>

@if(thisNodeRebootReasons().length || otherNodeRebootReasons().length) {
@if(thisNodeRebootReasons()?.length || otherNodeRebootReasons()?.length) {
<ix-form-actions mat-dialog-actions class="form-actions">
<ix-checkbox
class="confirm"
Expand All @@ -39,7 +39,7 @@ <h3 mat-dialog-title>
{{ 'Cancel' | translate }}
</button>

@if(thisNodeRebootReasons().length) {
@if(thisNodeRebootReasons()?.length) {
<button
mat-button
color="primary"
Expand All @@ -51,7 +51,7 @@ <h3 mat-dialog-title>
</button>
}

@if(otherNodeRebootReasons().length) {
@if(otherNodeRebootReasons()?.length) {
<button
mat-button
color="primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { map } from 'rxjs';
import { FormActionsComponent } from 'app/modules/forms/ix-forms/components/form-actions/form-actions.component';
import { IxCheckboxComponent } from 'app/modules/forms/ix-forms/components/ix-checkbox/ix-checkbox.component';
import { TestDirective } from 'app/modules/test-id/test.directive';
import { FipsService } from 'app/services/fips.service';
import { AppState } from 'app/store';
import { selectOtherNodeRebootInfo, selectThisNodeRebootInfo } from 'app/store/reboot-info/reboot-info.selectors';
Expand All @@ -28,6 +30,8 @@ import { selectOtherNodeRebootInfo, selectThisNodeRebootInfo } from 'app/store/r
MatDialogModule,
IxCheckboxComponent,
MatButton,
TestDirective,
FormActionsComponent,
],
})
export class RebootRequiredDialogComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h3 mat-dialog-title class="dialog-title">

@let jobs = selectRunningJobs$ | async;
<div class="jobs">
@if (jobs.length) {
@if (jobs?.length) {
<div>
@for (job of jobs; track job) {
<ix-job-item [job]="job"></ix-job-item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('FileTicketComponent', () => {
spectator = createComponent({
props: {
dialogRef,
isLoading: false,
type: FeedbackType.Bug,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { ApiService } from 'app/modules/websocket/api.service';
export class FileTicketComponent {
readonly type = input.required<FeedbackType.Bug | FeedbackType.Suggestion>();
readonly dialogRef = input.required<MatDialogRef<FeedbackDialogComponent>>();
readonly isLoading = input<boolean>();
readonly isLoading = input.required<boolean>();

readonly isLoadingChange = output<boolean>();

Expand Down
13 changes: 9 additions & 4 deletions src/app/modules/feedback/services/feedback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type TicketLicensedData = FileTicketLicensedComponent['form']['value'];
})
export class FeedbackService {
private readonly hostname = 'https://feedback.ui.truenas.com';
private isFeedbackAllowed: boolean;
private isFeedbackAllowed: boolean | undefined;

constructor(
private httpClient: HttpClient,
Expand Down Expand Up @@ -116,6 +116,11 @@ export class FeedbackService {
...options,
}).then((canvas) => {
canvas.toBlob((blob) => {
if (!blob) {
observer.error(new Error('Failed to create a screenshot.'));
return;
}

const file = new File([blob], filename, { type });
observer.next(file);
observer.complete();
Expand Down Expand Up @@ -273,7 +278,7 @@ export class FeedbackService {
}

private prepareTicket(token: string, type: TicketType, data: TicketData): Observable<CreateNewTicket> {
return this.addDebugInfoToMessage(data.message).pipe(
return this.addDebugInfoToMessage(data.message || '').pipe(
map((body) => ({
body,
token,
Expand All @@ -285,7 +290,7 @@ export class FeedbackService {
}

private prepareTicketLicensed(data: TicketLicensedData): Observable<CreateNewTicket> {
return this.addDebugInfoToMessage(data.message).pipe(
return this.addDebugInfoToMessage(data.message || '').pipe(
map((body) => ({
body,
name: data.name,
Expand All @@ -307,7 +312,7 @@ export class FeedbackService {
token?: string,
): Observable<void> {
const takeScreenshot = data.take_screenshot;
const images = data.images;
const images = data.images || [];

// Make requests and map to boolean for successful uploads.
const requests = images.map((attachment) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@ import { CloudCredentialService } from 'app/services/cloud-credential.service';
export class CloudCredentialsSelectComponent extends IxSelectWithNewOption {
readonly label = input<string>();
readonly tooltip = input<string>();
readonly required = input<boolean>();
readonly required = input<boolean>(false);
readonly filterByProviders = input<CloudSyncProviderName[]>();

private cloudCredentialService = inject(CloudCredentialService);

fetchOptions(): Observable<Option[]> {
return this.cloudCredentialService.getCloudSyncCredentials().pipe(
map((options) => {
if (this.filterByProviders()) {
options = options.filter((option) => this.filterByProviders().includes(option.provider.type));
const filterByProviders = this.filterByProviders();
if (filterByProviders) {
options = options.filter((option) => filterByProviders.includes(option.provider.type));
}
return options.map((option) => {
return { label: `${option.name} (${cloudSyncProviderNameMap.get(option.provider.type)})`, value: option.id };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { KeychainCredentialService } from 'app/services/keychain-credential.serv
export class SshCredentialsSelectComponent extends IxSelectWithNewOption {
readonly label = input<string>();
readonly tooltip = input<string>();
readonly required = input<boolean>();
readonly required = input<boolean>(false);

private keychainCredentialsService = inject(KeychainCredentialService);

Expand Down
Loading

0 comments on commit 9de2757

Please sign in to comment.