Skip to content

Commit

Permalink
Merge branch 'master' of github.com:truenas/webui into NAS-134329
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/assets/i18n/it.json
  • Loading branch information
undsoft committed Mar 4, 2025
2 parents 1b53c33 + f281d7c commit 560f0a7
Show file tree
Hide file tree
Showing 115 changed files with 6,498 additions and 5,632 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,
},
];
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>.'),

};
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
@@ -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');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
Component, ChangeDetectionStrategy, computed,
} from '@angular/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { TestDirective } from 'app/modules/test-id/test.directive';

@Component({
selector: 'ix-use-enterprise-marketing-link',
templateUrl: './use-enterprise-marketing-link.component.html',
styleUrls: ['./use-enterprise-marketing-link.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
TestDirective,
TranslateModule,
],
})
export class UseEnterpriseMarketingLinkComponent {
protected readonly targetUrl = 'https://truenas.com/explore-truenas-enterprise/';

messages = [
this.translate.instant('Optimize Your Storage'),
this.translate.instant('More Performance, More Protection'),
this.translate.instant('Boost Performance & Support'),
this.translate.instant('Unlock High Performance Solutions'),
this.translate.instant('Expert Support When You Need It'),
this.translate.instant('5 Nines of Uptime with HA'),
];

currentMessage = computed(() => this.getTodaysMessage());
currentMessageHref = computed(() => `${this.targetUrl}?m=${this.hashMessage(this.currentMessage())}`);

constructor(
private translate: TranslateService,
) {}

getTodaysMessage(): string {
const today = new Date().toDateString();
const lastShownDate = localStorage.getItem('marketingMessageLastShownDate');
const lastMessageHash = localStorage.getItem('marketingMessageLastHash');

if (lastShownDate !== today) {
const nextMessage = this.getNextMessage(lastMessageHash);

localStorage.setItem('marketingMessageLastShownDate', today);
localStorage.setItem('marketingMessageLastHash', this.hashMessage(nextMessage));

return nextMessage;
}

return this.getCurrentMessage(lastMessageHash);
}

getNextMessage(lastMessageHash: string | null): string {
const lastIndex = this.messages.findIndex((message) => this.hashMessage(message) === lastMessageHash);
const nextIndex = lastIndex >= 0 ? (lastIndex + 1) % this.messages.length : 0;
return this.messages[nextIndex];
}

getCurrentMessage(lastMessageHash: string | null): string {
const currentIndex = this.messages.findIndex((message) => this.hashMessage(message) === lastMessageHash);
return currentIndex >= 0 ? this.messages[currentIndex] : this.messages[0];
}

hashMessage(message: string): string {
return btoa(encodeURIComponent(message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ describe('GroupListComponent', () => {
store$.refreshState();

const table = await loader.getHarness(IxTableHarness);
await table.clickToggle(0);
await table.clickToggle(1);
await table.expandRow(0);
await table.expandRow(1);
expect(spectator.queryAll(GroupDetailsRowComponent)).toHaveLength(1);

await table.clickToggle(1);
await table.expandRow(1);
expect(spectator.queryAll(GroupDetailsRowComponent)).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ describe('UserListComponent', () => {
store$.refreshState();

const table = await loader.getHarness(IxTableHarness);
await table.clickToggle(0);
await table.clickToggle(1);
await table.expandRow(0);
await table.expandRow(1);
expect(spectator.queryAll(UserDetailsRowComponent)).toHaveLength(1);

await table.clickToggle(1);
await table.expandRow(1);
expect(spectator.queryAll(UserDetailsRowComponent)).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ <h3>{{ 'System Information' | translate }}</h3>
></ngx-skeleton-loader>
}
</mat-list-item>

@if (!isEnterprise()) {
<mat-list-item class="use-enterprise">
<ix-use-enterprise-marketing-link></ix-use-enterprise-marketing-link>
</mat-list-item>
}
</mat-list>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
}
}
}

.use-enterprise {
height: calc(var(--mdc-list-list-item-one-line-container-height, 48px) * 2);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IxIconComponent } from 'app/modules/ix-icon/ix-icon.component';
import { selectUpdateJobForActiveNode } from 'app/modules/jobs/store/job.selectors';
import { LocaleService } from 'app/modules/language/locale.service';
import { TestDirective } from 'app/modules/test-id/test.directive';
import { UseEnterpriseMarketingLinkComponent } from 'app/modules/use-enterprise-marketing-link/use-enterprise-marketing-link.component';
import { WidgetResourcesService } from 'app/pages/dashboard/services/widget-resources.service';
import { SlotSize } from 'app/pages/dashboard/types/widget.interface';
import { ProductImageComponent } from 'app/pages/dashboard/widgets/system/common/product-image/product-image.component';
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
CopyButtonComponent,
TranslateModule,
UptimePipe,
UseEnterpriseMarketingLinkComponent,
],
})
export class WidgetSysInfoActiveComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ describe('CloudSyncListComponent', () => {

it('shows confirmation dialog when Run Now button is pressed', async () => {
jest.spyOn(spectator.inject(DialogService), 'confirm');
await table.clickToggle(0);
await table.expandRow(0);

const runNowButton = await loader.getHarness(MatButtonHarness.with({ text: 'Run Now' }));
await runNowButton.click();
Expand All @@ -182,7 +182,7 @@ describe('CloudSyncListComponent', () => {
});

it('shows form to edit an existing CloudSync when Edit button is pressed', async () => {
await table.clickToggle(0);
await table.expandRow(0);

const editButton = await loader.getHarness(MatButtonHarness.with({ text: 'Edit' }));
await editButton.click();
Expand All @@ -201,7 +201,7 @@ describe('CloudSyncListComponent', () => {
it('deletes a Cloud Sync with confirmation when Delete button is pressed', async () => {
jest.spyOn(spectator.inject(DialogService), 'confirm');

await table.clickToggle(0);
await table.expandRow(0);

const deleteButton = await loader.getHarness(MatButtonHarness.with({ text: 'Delete' }));
await deleteButton.click();
Expand All @@ -219,7 +219,7 @@ describe('CloudSyncListComponent', () => {
});

it('shows dialog when Restore button is pressed', async () => {
await table.clickToggle(0);
await table.expandRow(0);

jest.spyOn(spectator.inject(MatDialog), 'open');

Expand All @@ -235,7 +235,7 @@ describe('CloudSyncListComponent', () => {
});

it('shows confirmation dialog when Dry Run button is pressed', async () => {
await table.clickToggle(0);
await table.expandRow(0);

const editButton = await loader.getHarness(MatButtonHarness.with({ text: 'Dry Run' }));
await editButton.click();
Expand Down
Loading

0 comments on commit 560f0a7

Please sign in to comment.