Skip to content

Commit

Permalink
Merge branch 'master' of github.com:truenas/webui into NAS-132542
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/assets/i18n/zh-hans.json
  • Loading branch information
undsoft committed Nov 15, 2024
2 parents 59897be + 35ff82e commit eaeff40
Show file tree
Hide file tree
Showing 140 changed files with 1,651 additions and 934 deletions.
2 changes: 1 addition & 1 deletion src/app/helptext/topbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const helptextTopbar = {
tc_connect: T('Connecting to TrueCommand'),
tc_status: T('Status of TrueCommand'),
update: T('Update in Progress'),
upgrade_waiting: T('Upgrade Waiting to Finish'),
reboot_info: T('Reboot Required'),
pending_network_changes: T('Pending Network Changes'),
directory_services_monitor: T('Directory Services Monitor'),
resilvering: T('Resilvering'),
Expand Down
4 changes: 3 additions & 1 deletion src/app/interfaces/api/api-call-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ import {
import { Privilege, PrivilegeRole, PrivilegeUpdate } from 'app/interfaces/privilege.interface';
import { Process } from 'app/interfaces/process.interface';
import { QueryParams } from 'app/interfaces/query-api.interface';
import { FailoverRebootInfo, SystemRebootInfo } from 'app/interfaces/reboot-info.interface';
import { ReplicationConfigUpdate } from 'app/interfaces/replication-config-update.interface';
import { ReplicationConfig } from 'app/interfaces/replication-config.interface';
import {
Expand Down Expand Up @@ -442,11 +443,11 @@ export interface ApiCallDirectory {
'failover.get_ips': { params: void; response: string[] };
'failover.licensed': { params: void; response: boolean };
'failover.node': { params: void; response: string };
'failover.reboot.info': { params: void; response: FailoverRebootInfo };
'failover.status': { params: void; response: FailoverStatus };
'failover.sync_from_peer': { params: void; response: void };
'failover.sync_to_peer': { params: [{ reboot?: boolean }]; response: void };
'failover.update': { params: [FailoverUpdate]; response: FailoverConfig };
'failover.upgrade_pending': { params: void; response: boolean };

// Filesystem
'filesystem.acltemplate.by_path': { params: [AclTemplateByPathParams]; response: AclTemplateByPath[] };
Expand Down Expand Up @@ -781,6 +782,7 @@ export interface ApiCallDirectory {
'system.product_type': { params: void; response: ProductType };
'system.security.config': { params: void; response: SystemSecurityConfig };
'system.security.info.fips_available': { params: void; response: boolean };
'system.reboot.info': { params: void; response: SystemRebootInfo };

// Systemdataset
'systemdataset.config': { params: void; response: SystemDatasetConfig };
Expand Down
3 changes: 3 additions & 0 deletions src/app/interfaces/api/api-event-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FailoverDisabledReasonEvent } from 'app/interfaces/failover-disabled-re
import { Group } from 'app/interfaces/group.interface';
import { Job } from 'app/interfaces/job.interface';
import { Pool } from 'app/interfaces/pool.interface';
import { FailoverRebootInfo, SystemRebootInfo } from 'app/interfaces/reboot-info.interface';
import { ReportingRealtimeUpdate } from 'app/interfaces/reporting.interface';
import { PoolScan } from 'app/interfaces/resilver-job.interface';
import { Service } from 'app/interfaces/service.interface';
Expand All @@ -30,13 +31,15 @@ export interface ApiEventDirectory {
'disk.query': { response: Disk };
'docker.state': { response: DockerStatusData };
'failover.disabled.reasons': { response: FailoverDisabledReasonEvent };
'failover.reboot.info': { response: FailoverRebootInfo };
'failover.status': { response: { status: FailoverStatus } };
'group.query': { response: Group };
'pool.query': { response: Pool };
'virt.global.config': { response: VirtualizationGlobalConfig };
'reporting.realtime': { response: ReportingRealtimeUpdate };
'service.query': { response: Service };
'smart.test.progress': { response: SmartTestProgressUpdate };
'system.reboot.info': { response: SystemRebootInfo };
'truecommand.config': { response: TrueCommandConfig };
'user.query': { response: User };
'virt.instance.query': { response: VirtualizationInstance };
Expand Down
1 change: 0 additions & 1 deletion src/app/interfaces/api/api-job-directory.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export interface ApiJobDirectory {
// Failover
'failover.reboot.other_node': { params: void; response: void };
'failover.upgrade': { params: [FailoverUpgradeParams]; response: boolean };
'failover.upgrade_finish': { params: void; response: boolean };

// Filesystem
'filesystem.put': { params: FilesystemPutParams; response: boolean };
Expand Down
14 changes: 14 additions & 0 deletions src/app/interfaces/reboot-info.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface SystemRebootInfo {
boot_id: string;
reboot_required_reasons: RebootRequiredReasons[];
}

export interface FailoverRebootInfo {
this_node: SystemRebootInfo;
other_node: SystemRebootInfo | null;
}

export interface RebootRequiredReasons {
code: string;
reason: string;
}
23 changes: 16 additions & 7 deletions src/app/interfaces/terminal.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ export interface TerminalConfiguration {
connectionData: TerminalConnectionData;
}

export interface TerminalConnectionData {
vmId?: number;
podInfo?: {
chartReleaseName: string;
containerId: string;
export type TerminalConnectionData =
// VMs
| {
vm_id: number;
}
// Virtualization instances
| {
virt_instance_id: string;
}
// Apps
| {
app_name: string;
container_id: string;
command: string;
};
}
}
// No params
| Record<string, never>;
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ describe('AlertsPanelComponent', () => {
reasons: [],
},
isHaLicensed: true,
isUpgradePending: false,
},
},
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<h3 mat-dialog-title>
{{ 'Reboot Required' | translate }}
</h3>

<div mat-dialog-content>
@if(thisNodeRebootReasons().length) {
<div class="reasons">
<p class="dialog-message">
<span>{{ 'The local node must be rebooted because' | translate }}:</span>
</p>
@for (reason of thisNodeRebootReasons(); track reason) {
<li> {{ reason.reason }} </li>
}
</div>
}

@if(otherNodeRebootReasons().length) {
<div class="reasons">
<p class="dialog-message">
<span>{{ 'The remote node must be rebooted because' | translate }}:</span>
</p>
@for (reason of otherNodeRebootReasons(); track reason) {
<li> {{ reason.reason }} </li>
}
</div>
}
</div>

@if(thisNodeRebootReasons().length || otherNodeRebootReasons().length) {
<ix-form-actions mat-dialog-actions class="form-actions">
<ix-checkbox
class="confirm"
[formControl]="form.controls.confirm"
[label]="'Confirm' | translate"
[required]="true"
></ix-checkbox>

<button mat-button type="button" matDialogClose ixTest="cancel">
{{ 'Cancel' | translate }}
</button>

@if(thisNodeRebootReasons().length) {
<button
mat-button
color="primary"
ixTest="reboot-local"
[disabled]="form.invalid"
(click)="rebootLocalNode()"
>
{{ 'Reboot Local' | translate }}
</button>
}

@if(otherNodeRebootReasons().length) {
<button
mat-button
color="primary"
ixTest="reboot-remote"
[disabled]="form.invalid"
(click)="rebootRemoteNode()"
>
{{ 'Reboot Remote' | translate }}
</button>
}
</ix-form-actions>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.dialog-message {
margin-bottom: 5px;
margin-top: 0;
padding: 0;
}

.reasons {
padding: 0 12px 12px;
}

.confirm {
margin-right: auto;
padding-right: 10px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { provideMockStore } from '@ngrx/store/testing';
import { SystemRebootInfo } from 'app/interfaces/reboot-info.interface';
import { RebootRequiredDialogComponent } from 'app/modules/dialog/components/reboot-required-dialog/reboot-required-dialog.component';
import {
selectOtherNodeRebootInfo,
selectThisNodeRebootInfo,
} from 'app/store/reboot-info/reboot-info.selectors';

const fakeThisNodeRebootInfo: SystemRebootInfo = {
boot_id: 'this-boot-id',
reboot_required_reasons: [
{ code: 'FIPS', reason: 'Test Reason 1' },
{ code: 'FIPS', reason: 'Test Reason 2' },
],
};

const fakeOtherNodeRebootInfo: SystemRebootInfo = {
boot_id: 'other-boot-id',
reboot_required_reasons: [
{ code: 'FIPS', reason: 'Test Reason 3' },
{ code: 'FIPS', reason: 'Test Reason 4' },
],
};

describe('RebootRequiredDialogComponent', () => {
let spectator: Spectator<RebootRequiredDialogComponent>;
const createComponent = createComponentFactory({
component: RebootRequiredDialogComponent,
providers: [
provideMockStore({
selectors: [
{
selector: selectThisNodeRebootInfo,
value: fakeThisNodeRebootInfo,
},
{
selector: selectOtherNodeRebootInfo,
value: fakeOtherNodeRebootInfo,
},
],
}),
],
});

beforeEach(() => {
spectator = createComponent();
});

it('shows reasons', () => {
expect(
spectator.queryAll('.reasons li').map((item) => item.textContent.trim()),
).toEqual([
'Test Reason 1',
'Test Reason 2',
'Test Reason 3',
'Test Reason 4',
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { CdkScrollable } from '@angular/cdk/scrolling';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { FormBuilder } from '@ngneat/reactive-forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { map } from 'rxjs';
import { RebootRequiredReasons } from 'app/interfaces/reboot-info.interface';
import { IxCheckboxComponent } from 'app/modules/forms/ix-forms/components/ix-checkbox/ix-checkbox.component';
import { FipsService } from 'app/services/fips.service';
import { AppState } from 'app/store';
import { selectOtherNodeRebootInfo, selectThisNodeRebootInfo } from 'app/store/reboot-info/reboot-info.selectors';

@UntilDestroy()
@Component({
selector: 'ix-reboot-required-dialog',
templateUrl: './reboot-required-dialog.component.html',
styleUrls: ['./reboot-required-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CdkScrollable,
TranslateModule,
ReactiveFormsModule,
MatDialogModule,
IxCheckboxComponent,
MatButton,
],
})
export class RebootRequiredDialogComponent {
thisNodeRebootReasons = toSignal(this.store$.select(selectThisNodeRebootInfo).pipe(
map((info) => info?.reboot_required_reasons || []),
));

otherNodeRebootReasons = toSignal(this.store$.select(selectOtherNodeRebootInfo).pipe(
map((info) => info?.reboot_required_reasons || []),
));

form = this.fb.group({
confirm: [false, Validators.requiredTrue],
});

constructor(
private store$: Store<AppState>,
private fips: FipsService,
private fb: FormBuilder,
) {}

typeReasons(reasons: unknown): RebootRequiredReasons[] {
return reasons as RebootRequiredReasons[];
}

rebootLocalNode(): void {
this.fips.restart();
}

rebootRemoteNode(): void {
this.fips.restartRemote().pipe(untilDestroyed(this)).subscribe();
}
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit eaeff40

Please sign in to comment.