diff --git a/release-notes/Release-notes-0.15.9.md b/release-notes/Release-notes-0.15.9.md index 6a72fbb4..d1cf7c8a 100644 --- a/release-notes/Release-notes-0.15.9.md +++ b/release-notes/Release-notes-0.15.9.md @@ -16,6 +16,17 @@ this release should add its entry under the appropriate section below. sync, and the list columns read `peer_connected` directly. Regression tests were added for both channel tables. +- **Core Lightning: fix the channel View Info modal rendering blank for disconnected channels** + ([#1625](https://github.com/Ride-The-Lightning/RTL/pull/1625), fixes + [#1606](https://github.com/Ride-The-Lightning/RTL/issues/1606)). + The channel information modal renders a block-explorer link from `selNode.settings.blockExplorerUrl`, + but the pending/inactive channels table opened the modal without passing `selNode`. With it + undefined, that binding threw during change detection and blanked every field below it — State, + Connected, Private and the balances all showed no value. Because a disconnected channel moves to + the pending/inactive table, this is exactly what was seen on "View Info" for a disconnected + channel. The pending table now passes `selNode` (matching the open table), and the modal guards + the explorer link so a missing `selNode` can no longer blank the dialog. + ## Developer Tooling - **Added a Core Lightning node to the regtest docker fixture** diff --git a/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.html b/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.html index a4f938ce..0fb69f9d 100644 --- a/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.html +++ b/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.html @@ -43,7 +43,7 @@

Funding Transaction ID

{{channel.funding_txid}} - + diff --git a/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.ts b/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.ts index a2053d0a..25c6cd37 100644 --- a/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.ts +++ b/src/app/cln/peers-channels/channels/channel-information-modal/channel-information.component.ts @@ -57,6 +57,7 @@ export class CLNChannelInformationComponent implements OnInit { } onExplorerClicked() { + if (!this.selNode?.settings?.blockExplorerUrl) { return; } window.open(this.selNode.settings.blockExplorerUrl + '/tx/' + this.channel.funding_txid, '_blank'); } diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts index 9d5448ce..dd9acf83 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.spec.ts @@ -1,5 +1,5 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; -import { StoreModule } from '@ngrx/store'; +import { Store, StoreModule } from '@ngrx/store'; import { RootReducer } from '../../../../../store/rtl.reducers'; import { LNDReducer } from '../../../../../lnd/store/lnd.reducers'; @@ -73,6 +73,20 @@ describe('CLNChannelPendingTableComponent', () => { expect(connectedCellText()).toBe('Disconnected'); }); + // Issue #1606: the channel information modal reads selNode.settings.blockExplorerUrl, so the + // pending table must pass selNode when opening it. Without it the modal throws mid-render and + // blanks State/Connected/balances for disconnected channels (which live in this table). + it('should pass selNode when opening the channel information modal', () => { + const store = TestBed.inject(Store); + const dispatchSpy = spyOn(store, 'dispatch'); + const selNode: any = { settings: { blockExplorerUrl: 'https://mempool.space' } }; + component.selNode = selNode; + component.onChannelClick({ short_channel_id: '120x1x0', peer_connected: false } as any, {} as any); + expect(dispatchSpy).toHaveBeenCalled(); + const action: any = dispatchSpy.calls.mostRecent().args[0]; + expect(action.payload.data.selNode).toBe(selNode); + }); + afterEach(() => { TestBed.resetTestingModule(); }); diff --git a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.ts b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.ts index 4b6d8fb0..9781befb 100644 --- a/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.ts +++ b/src/app/cln/peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component.ts @@ -20,6 +20,7 @@ import { openAlert, openConfirmation } from '../../../../../store/rtl.actions'; import { RTLState } from '../../../../../store/rtl.state'; import { closeChannel } from '../../../../store/cln.actions'; import { channels, clnPageSettings, nodeInfoAndBalanceAndNumPeers } from '../../../../store/cln.selector'; +import { rootSelectedNode } from '../../../../../store/rtl.selector'; import { ColumnDefinition, PageSettings, TableSetting } from '../../../../../shared/models/pageSettings'; import { CamelCaseWithReplacePipe } from '../../../../../shared/pipes/app.pipe'; import { MAT_SELECT_CONFIG } from '@angular/material/select'; @@ -62,6 +63,7 @@ export class CLNChannelPendingTableComponent implements OnInit, AfterViewInit, O public errorMessage = ''; public apiCallStatus: ApiCallStatusPayload | null = null; public apiCallStatusEnum = APICallStatusEnum; + public selNode: Node | null = null; private unSubs: Array> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()]; constructor(private logger: LoggerService, private store: Store, private rtlEffects: RTLEffects, private commonService: CommonService, private camelCaseWithReplace: CamelCaseWithReplacePipe) { @@ -109,6 +111,10 @@ export class CLNChannelPendingTableComponent implements OnInit, AfterViewInit, O } this.logger.info(channelsSeletor); }); + this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[4])). + subscribe((nodeSettings) => { + this.selNode = nodeSettings; + }); } ngAfterViewInit() { @@ -133,6 +139,7 @@ export class CLNChannelPendingTableComponent implements OnInit, AfterViewInit, O payload: { data: { channel: selChannel, + selNode: this.selNode, showCopy: true, component: CLNChannelInformationComponent }