Skip to content

Commit

Permalink
Merge branch 'origin/master' into 134371/NAS-134371
Browse files Browse the repository at this point in the history
  • Loading branch information
bvasilenko committed Mar 6, 2025
2 parents b222c29 + 6f6b127 commit f191efb
Show file tree
Hide file tree
Showing 195 changed files with 8,257 additions and 6,606 deletions.
6 changes: 3 additions & 3 deletions src/app/enums/alert-service-type.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export enum AlertServiceType {
Slack = 'Slack',
SnmpTrap = 'SNMPTrap',
Telegram = 'Telegram',
VictorOps = 'VictorOps',
SplunkOnCall = 'VictorOps',
}

export const alertServiceNames = [
Expand Down Expand Up @@ -40,7 +40,7 @@ export const alertServiceNames = [
label: 'Telegram',
value: AlertServiceType.Telegram,
}, {
label: 'VictorOps',
value: AlertServiceType.VictorOps,
label: 'Splunk On-Call',
value: AlertServiceType.SplunkOnCall,
},
];
12 changes: 12 additions & 0 deletions src/app/enums/virtualization.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ export const virtualizationTypeLabels = new Map<VirtualizationType, string>([
[VirtualizationType.Vm, T('VM')],
]);

export enum DiskIoBus {
Nvme = 'NVME',
VirtioBlk = 'VIRTIO-BLK',
VirtioScsi = 'VIRTIO-SCSI',
}

export const diskIoBusLabels = new Map<DiskIoBus, string>([
[DiskIoBus.Nvme, 'NVMe'],
[DiskIoBus.VirtioBlk, 'Virtio-BLK'],
[DiskIoBus.VirtioScsi, 'Virtio-SCSI'],
]);

export const virtualizationTypeIcons = [
{
value: VirtualizationType.Container,
Expand Down
8 changes: 4 additions & 4 deletions src/app/helptext/system/alert-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ export const helptextAlertService = {
<a href="https://api.telegram.org/bot(BOT_TOKEN)/getUpdates" \
target="_blank">https://api.telegram.org/bot(BOT_TOKEN)/getUpdates</a>.'),

VictorOps_api_key_tooltip: T('Enter or paste the <a\
SplunkOnCall_api_key_tooltip: T('Enter or paste the <a\
href="https://help.victorops.com/knowledge-base/api/"\
target="_blank">VictorOps API key</a>.'),
target="_blank">Splunk On-Call API key</a>.'),

VictorOps_routing_key_tooltip: T('Enter or paste the <a\
SplunkOnCall_routing_key_tooltip: T('Enter or paste the <a\
href="https://portal.victorops.com/public/api-docs.html#/Routing32Keys"\
target="_blank">VictorOps routing key</a>.'),
target="_blank">Splunk On-Call routing key</a>.'),

};
5 changes: 5 additions & 0 deletions src/app/helptext/virtualization/containers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Choose a VM for full OS isolation, kernel independence, and running diverse OS t
host_port_placeholder: T('Host Port'),
host_port_tooltip: T('Specify the host port to be mapped to the container\'s port.'),

io_bus_tooltip: T('Choose the disk I/O bus type that best suits your system’s needs:\
<br /><br /> • NVMe – Ideal for high-performance storage with faster read and write speeds.\
<br /><br /> • Virtio-BLK – Efficient for virtualized environments, offering direct block device access with lower overhead.\
<br /><br /> • Virtio-SCSI – Flexible and scalable, supporting advanced features like hot-swapping and multiple devices.'),

instance_protocol_placeholder: T('Instance Protocol'),
instance_protocol_tooltip: T('Select the protocol for the instance\'s network connection.'),
instance_port_placeholder: T('Instance Port'),
Expand Down
3 changes: 0 additions & 3 deletions src/app/interfaces/bind-ip.interface.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/app/interfaces/dataset.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export interface DatasetDetails {
origin: ZfsProperty<string>;
sync: ZfsProperty<string>;
compression: ZfsProperty<string>;
compressratio: ZfsProperty<string>;
deduplication: ZfsProperty<string>;
refquota_critical?: ZfsProperty<string, number>;
refquota_warning?: ZfsProperty<string, number>;
Expand Down
3 changes: 1 addition & 2 deletions src/app/interfaces/smb-config.interface.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { SmbEncryption } from 'app/enums/smb-encryption.enum';
import { BindIp } from 'app/interfaces/bind-ip.interface';

export interface SmbConfig {
aapl_extensions: boolean;
admin_group: string | null;
bindip: BindIp[];
bindip: string[];
cifs_SID: string;
description: string;
dirmask: string;
Expand Down
5 changes: 5 additions & 0 deletions src/app/interfaces/virtualization.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FormControl, FormGroup } from '@angular/forms';
import { NetworkInterfaceAliasType } from 'app/enums/network-interface.enum';
import {
DiskIoBus,
VirtualizationDeviceType,
VirtualizationGlobalState,
VirtualizationGpuType,
Expand Down Expand Up @@ -41,6 +42,7 @@ export interface VirtualizationInstance {
vnc_port: number | null;
vnc_password: string | null;
secure_boot: boolean;
root_disk_io_bus: DiskIoBus;
root_disk_size: number | null;
userns_idmap: UserNsIdmap | null;
}
Expand All @@ -61,6 +63,7 @@ export interface CreateVirtualizationInstance {
* Value in GBs.
*/
root_disk_size?: number;
root_disk_io_bus?: DiskIoBus;
source_type?: VirtualizationSource;
environment?: Record<string, string>;
autostart?: boolean;
Expand Down Expand Up @@ -90,6 +93,7 @@ export interface UpdateVirtualizationInstance {
enable_vnc?: boolean;
vnc_port?: number | null;
secure_boot?: boolean;
root_disk_io_bus?: DiskIoBus;
vnc_password?: string | null;
root_disk_size?: number;
}
Expand All @@ -111,6 +115,7 @@ export interface VirtualizationDisk {
source: string | null;
destination: string | null;
product_id: string;
io_bus: DiskIoBus;
boot_priority?: number;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { provideMockStore } from '@ngrx/store/testing';
import { mockCall, mockJob, mockApi } from 'app/core/testing/utils/mock-api.utils';
import { of } from 'rxjs';
import { mockJob, mockApi } from 'app/core/testing/utils/mock-api.utils';
import { ControllerType } from 'app/enums/controller-type.enum';
import { JobState } from 'app/enums/job-state.enum';
import { ApiJobMethod } from 'app/interfaces/api/api-job-directory.interface';
Expand All @@ -27,10 +28,9 @@ describe('ExportButtonComponent', () => {
providers: [
mockApi([
mockJob(jobMethod, { result: '/path/data.csv', state: JobState.Success } as Job<string>),
mockCall('core.download', [33456, '/_download/33456?auth_token=1234567890']),
]),
mockProvider(DownloadService, {
downloadUrl: jest.fn(),
coreDownload: jest.fn(() => of(undefined)),
}),
provideMockStore({
selectors: [
Expand Down Expand Up @@ -62,12 +62,12 @@ describe('ExportButtonComponent', () => {
'query-filters': [],
'query-options': {},
}]);
expect(spectator.inject(ApiService).call).toHaveBeenCalledWith('core.download', [jobMethod, [{}], '/path/data.csv']);
expect(spectator.inject(DownloadService).downloadUrl).toHaveBeenLastCalledWith(
'/_download/33456?auth_token=1234567890',
'data.csv',
'text/csv',
);
expect(spectator.inject(DownloadService).coreDownload).toHaveBeenLastCalledWith({
arguments: [{}],
fileName: 'data.csv',
method: 'audit.export',
mimeType: 'text/csv',
});
});

it('downloads a file when Export As CSV button is pressed with options', async () => {
Expand All @@ -92,11 +92,11 @@ describe('ExportButtonComponent', () => {
'query-options': { order_by: ['-service'] },
remote_controller: true,
}]);
expect(spectator.inject(ApiService).call).toHaveBeenCalledWith('core.download', [jobMethod, [{}], '/path/data.csv']);
expect(spectator.inject(DownloadService).downloadUrl).toHaveBeenLastCalledWith(
'/_download/33456?auth_token=1234567890',
'data.csv',
'text/csv',
);
expect(spectator.inject(DownloadService).coreDownload).toHaveBeenLastCalledWith({
arguments: [{}],
fileName: 'data.csv',
method: 'audit.export',
mimeType: 'text/csv',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,13 @@ export class ExportButtonComponent<T, M extends ApiJobMethod> {
customArguments.report_name = url;
}

return this.api.call('core.download', [downloadMethod, [customArguments], url]);
return this.download.coreDownload({
method: downloadMethod,
arguments: [customArguments],
fileName: `${this.filename()}.${this.fileType()}`,
mimeType: this.fileMimeType(),
});
}),
switchMap(([, url]) => this.download.downloadUrl(url, `${this.filename()}.${this.fileType()}`, this.fileMimeType())),
catchError((error: unknown) => {
this.isLoading = false;
this.cdr.markForCheck();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ContentContainerComponentHarness } from '@angular/cdk/testing';
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';

export class IxCellCheckboxHarness extends ContentContainerComponentHarness {
static readonly hostSelector = 'ix-cell-checkbox';

async check(): Promise<void> {
const checkbox = await this.getHarness(MatCheckboxHarness);
await checkbox.check();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export class IxTableHarness extends ContentContainerComponentHarness {
return toggles[row];
}

async selectRows(rows: number[]): Promise<void> {
for (const rowIndex of rows) {
await (await this.getRows())[rowIndex].check();
}
}

async getCellTexts(): Promise<string[][]> {
const cells = await this.getCells();
const texts = await parallel(() => cells.map((cell) => cell.getText()));
Expand All @@ -132,7 +138,7 @@ export class IxTableHarness extends ContentContainerComponentHarness {
(await this.getRowElement(row)).click();
}

async clickToggle(row: number): Promise<void> {
async expandRow(row: number): Promise<void> {
(await this.getToggle(row)).click();
}
}
8 changes: 8 additions & 0 deletions src/app/modules/ix-table/components/ix-table/row.harness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ContentContainerComponentHarness } from '@angular/cdk/testing';
import {
IxCellCheckboxHarness,
} from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.harness';

export class IxRowHarness extends ContentContainerComponentHarness {
static readonly hostSelector = '.row';

async check(): Promise<void> {
const checkbox = await this.getHarness(IxCellCheckboxHarness);
await checkbox.check();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
ChangeDetectionStrategy, Component, computed,
input,
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { Store } from '@ngrx/store';
import { getCopyrightHtml } from 'app/helpers/copyright-text.helper';
import { TestDirective } from 'app/modules/test-id/test.directive';
import { AppState } from 'app/store';
import { selectCopyrightHtml, selectIsEnterprise } from 'app/store/system-info/system-info.selectors';
Expand All @@ -16,7 +18,10 @@ import { selectCopyrightHtml, selectIsEnterprise } from 'app/store/system-info/s
imports: [TestDirective],
})
export class CopyrightLineComponent {
readonly copyrightText = toSignal(this.store$.select(selectCopyrightHtml));
skipType = input(false);

readonly copyrightHtml = toSignal(this.store$.select(selectCopyrightHtml));
readonly copyrightText = computed(() => (this.skipType() ? getCopyrightHtml() : this.copyrightHtml()));

readonly isEnterprise = toSignal(this.store$.select(selectIsEnterprise));
readonly targetHref = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<a
ixTest="use-enterprise-marketing-link"
target="_blank"
[href]="currentMessageHref()"
>
<span class="text-above">
{{ currentMessage() }}
</span>

<span class="text-below">
{{ 'Explore TrueNAS Enterprise' | translate }}
</span>
</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import 'scss-imports/variables';

:host {
margin: 0 auto 5px;
}

a {
cursor: pointer;
display: block;
margin-bottom: 2px;
text-align: center;
text-decoration: none;

span {
color: var(--primary-txt);
display: block;

&.text-above {
color: var(--primary);
margin-bottom: 2px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { UseEnterpriseMarketingLinkComponent } from './use-enterprise-marketing-link.component';

const lastShownDate = 'marketingMessageLastShownDate';
const lastMessageHash = 'marketingMessageLastHash';

describe('UseEnterpriseMarketingLinkComponent', () => {
let spectator: Spectator<UseEnterpriseMarketingLinkComponent>;

const createComponent = createComponentFactory({
component: UseEnterpriseMarketingLinkComponent,
});

beforeEach(() => {
spectator = createComponent();
jest.spyOn(global.Date.prototype, 'toDateString').mockReturnValue('2025-02-26');
localStorage.clear();
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should display the first message by default', () => {
const message = spectator.component.currentMessage();
expect(message).toBe('Optimize Your Storage');
});

it('should rotate to the next message on a new day', () => {
localStorage.setItem(lastShownDate, '2025-02-25');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('Optimize Your Storage'));

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('More Performance, More Protection');
});

it('should loop to the first message after the last message', () => {
localStorage.setItem(lastShownDate, '2025-02-25');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('5 Nines of Uptime with HA'));

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('Optimize Your Storage');
});

it('should update localStorage with new date and hash', () => {
spectator.component.getTodaysMessage();

expect(localStorage.getItem(lastShownDate)).toBe('2025-02-26');
expect(localStorage.getItem(lastMessageHash)).toBe(spectator.component.hashMessage('Optimize Your Storage'));
});

it('should maintain consistent message even if array order changes', () => {
const originalHash = spectator.component.hashMessage('Boost Performance & Support');
localStorage.setItem('marketingMessageLastShownDate', '2025-02-25');
localStorage.setItem('marketingMessageLastHash', originalHash);

spectator.component.messages = [
'Boost Performance & Support',
'Optimize Your Storage',
'More Performance, More Protection',
];

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('Optimize Your Storage');
});

it('should return the first message if hash is not found', () => {
localStorage.setItem(lastShownDate, '2025-02-26');
localStorage.setItem(lastMessageHash, 'unknownHash');

const currentMessage = spectator.component.getTodaysMessage();
expect(currentMessage).toBe('Optimize Your Storage');
});

it('should not change message within the same day', () => {
localStorage.setItem(lastShownDate, '2025-02-26');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('Optimize Your Storage'));

const currentMessage = spectator.component.getTodaysMessage();
expect(currentMessage).toBe('Optimize Your Storage');
});
});
Loading

0 comments on commit f191efb

Please sign in to comment.