From 4ba552fe1b36e05576263039347c58f4e6c594e8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 9 Jul 2023 01:15:05 -0400 Subject: [PATCH 1/3] Add basic lightning justice page --- backend/src/api/explorer/channels.api.ts | 19 +++++ backend/src/api/explorer/channels.routes.ts | 13 +++ .../transactions-list.component.html | 2 +- .../src/app/interfaces/node-api.interface.ts | 1 + .../justice-list/justice-list.component.html | 79 +++++++++++++++++++ .../justice-list/justice-list.component.scss | 52 ++++++++++++ .../justice-list/justice-list.component.ts | 60 ++++++++++++++ .../app/lightning/lightning-api.service.ts | 8 +- .../src/app/lightning/lightning.module.ts | 3 + .../app/lightning/lightning.routing.module.ts | 5 ++ .../truncate/truncate.component.html | 2 +- .../truncate/truncate.component.scss | 4 + .../components/truncate/truncate.component.ts | 2 + 13 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/lightning/justice-list/justice-list.component.html create mode 100644 frontend/src/app/lightning/justice-list/justice-list.component.scss create mode 100644 frontend/src/app/lightning/justice-list/justice-list.component.ts diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index 00d146770..67d775ea3 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -117,6 +117,25 @@ class ChannelsApi { } } + public async $getPenaltyClosedChannels(): Promise { + try { + const query = ` + SELECT n1.alias AS alias_left, + n2.alias AS alias_right, + channels.* + FROM channels + LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key + LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key + WHERE channels.status = 2 AND channels.closing_reason = 3 + `; + const [rows]: any = await DB.query(query); + return rows; + } catch (e) { + logger.err('$getPenaltyClosedChannels error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } + public async $getUnresolvedClosedChannels(): Promise { try { const query = `SELECT * FROM channels WHERE status = 2 AND closing_reason = 2 AND closing_resolved = 0 AND closing_transaction_id != ''`; diff --git a/backend/src/api/explorer/channels.routes.ts b/backend/src/api/explorer/channels.routes.ts index 2b7f3fa6d..f28ab2a9d 100644 --- a/backend/src/api/explorer/channels.routes.ts +++ b/backend/src/api/explorer/channels.routes.ts @@ -11,6 +11,7 @@ class ChannelsRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/search/:search', this.$searchChannelsById) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/:short_id', this.$getChannel) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels', this.$getChannelsForNode) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/penalties', this.$getPenaltyClosedChannels) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels-geo', this.$getAllChannelsGeo) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels-geo/:publicKey', this.$getAllChannelsGeo) ; @@ -108,6 +109,18 @@ class ChannelsRoutes { } } + private async $getPenaltyClosedChannels(req: Request, res: Response): Promise { + try { + const channels = await channelsApi.$getPenaltyClosedChannels(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(channels); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getAllChannelsGeo(req: Request, res: Response) { try { const style: string = typeof req.query.style === 'string' ? req.query.style : ''; diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index c509a799d..d01453aee 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -73,7 +73,7 @@ {{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
- +
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 68c45b3b2..df4b5de8e 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -266,6 +266,7 @@ export interface IChannel { closing_transaction_id: string; closing_reason: string; updated_at: string; + closing_date?: string; created: string; status: number; node_left: INode, diff --git a/frontend/src/app/lightning/justice-list/justice-list.component.html b/frontend/src/app/lightning/justice-list/justice-list.component.html new file mode 100644 index 000000000..071c6f7f4 --- /dev/null +++ b/frontend/src/app/lightning/justice-list/justice-list.component.html @@ -0,0 +1,79 @@ + + +
+

Penalties

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
ClosedCapacityNodesChannel ID
+ ‎{{ channel.closing_date | date:'yyyy-MM-dd HH:mm' }} + + + + {{ channel.capacity | amountShortener: 1 }} + sats + + + + + + + {{ channel.short_id }} + + +
+ +
+
+
+ +
+ + + + +
+
Opening transaction
+
+ + +
+ +
+
Closing transaction
   +
+ + +
+ +
+ + + loading... + diff --git a/frontend/src/app/lightning/justice-list/justice-list.component.scss b/frontend/src/app/lightning/justice-list/justice-list.component.scss new file mode 100644 index 000000000..3547c447f --- /dev/null +++ b/frontend/src/app/lightning/justice-list/justice-list.component.scss @@ -0,0 +1,52 @@ +.container-xl { + max-width: 1400px; +} +.container-xl.widget { + padding-right: 0px; + padding-left: 0px; + padding-bottom: 0px; +} + +tr, td, th { + border: 0px; + padding-top: 0.65rem !important; + padding-bottom: 0.7rem !important; +} + +.clear-link { + color: white; +} + +.pool { + width: 15%; + @media (max-width: 575px) { + width: 75%; + } + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 160px; +} +.pool-name { + display: inline-block; + vertical-align: text-top; + text-overflow: ellipsis; + overflow: hidden; +} + +.liquidity { + width: 10%; + @media (max-width: 575px) { + width: 25%; + } +} + +.fiat { + width: 15%; + @media (min-width: 768px) and (max-width: 991px) { + display: none !important; + } + @media (max-width: 575px) { + display: none !important; + } +} diff --git a/frontend/src/app/lightning/justice-list/justice-list.component.ts b/frontend/src/app/lightning/justice-list/justice-list.component.ts new file mode 100644 index 000000000..d48cdb7ea --- /dev/null +++ b/frontend/src/app/lightning/justice-list/justice-list.component.ts @@ -0,0 +1,60 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { map, Observable, of, Subject, Subscription, switchMap, tap, zip } from 'rxjs'; +import { IChannel } from '../../interfaces/node-api.interface'; +import { LightningApiService } from '../lightning-api.service'; +import { Transaction } from '../../interfaces/electrs.interface'; +import { ElectrsApiService } from '../../services/electrs-api.service'; + +@Component({ + selector: 'app-justice-list', + templateUrl: './justice-list.component.html', + styleUrls: ['./justice-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class JusticeList implements OnInit, OnDestroy { + justiceChannels$: Observable; + fetchTransactions$: Subject = new Subject(); + transactionsSubscription: Subscription; + transactions: Transaction[]; + expanded: string = null; + loadingTransactions: boolean = true; + + constructor( + private apiService: LightningApiService, + private electrsApiService: ElectrsApiService, + private cd: ChangeDetectorRef, + ) {} + + ngOnInit(): void { + this.justiceChannels$ = this.apiService.getPenaltyClosedChannels$(); + + this.transactionsSubscription = this.fetchTransactions$.pipe( + tap(() => { + this.loadingTransactions = true; + }), + switchMap((channel: IChannel) => { + return zip([ + channel.transaction_id ? this.electrsApiService.getTransaction$(channel.transaction_id) : of(null), + channel.closing_transaction_id ? this.electrsApiService.getTransaction$(channel.closing_transaction_id) : of(null), + ]); + }), + ).subscribe((transactions) => { + this.transactions = transactions; + this.loadingTransactions = false; + this.cd.markForCheck(); + }); + } + + toggleDetails(channel: any): void { + if (this.expanded === channel.short_id) { + this.expanded = null; + } else { + this.expanded = channel.short_id; + this.fetchTransactions$.next(channel); + } + } + + ngOnDestroy(): void { + this.transactionsSubscription.unsubscribe(); + } +} diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 6ea550591..bdcc78f3f 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { StateService } from '../services/state.service'; -import { INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface'; +import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface'; @Injectable({ providedIn: 'root' @@ -84,6 +84,12 @@ export class LightningApiService { ); } + getPenaltyClosedChannels$(): Observable { + return this.httpClient.get( + this.apiBasePath + '/api/v1/lightning/penalties' + ); + } + getOldestNodes$(): Observable { return this.httpClient.get( this.apiBasePath + '/api/v1/lightning/nodes/rankings/age' diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index 5d67433c7..0b824ad78 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -29,6 +29,7 @@ import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels import { NodesRanking } from '../lightning/nodes-ranking/nodes-ranking.component'; import { TopNodesPerChannels } from '../lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component'; import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component'; +import { JusticeList } from '../lightning/justice-list/justice-list.component'; import { OldestNodes } from '../lightning/nodes-ranking/oldest-nodes/oldest-nodes.component'; import { NodesRankingsDashboard } from '../lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component'; import { NodeChannels } from '../lightning/nodes-channels/node-channels.component'; @@ -60,6 +61,7 @@ import { GroupComponent } from './group/group.component'; NodesRanking, TopNodesPerChannels, TopNodesPerCapacity, + JusticeList, OldestNodes, NodesRankingsDashboard, NodeChannels, @@ -97,6 +99,7 @@ import { GroupComponent } from './group/group.component'; NodesRanking, TopNodesPerChannels, TopNodesPerCapacity, + JusticeList, OldestNodes, NodesRankingsDashboard, NodeChannels, diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts index 79c3bc297..8b8041181 100644 --- a/frontend/src/app/lightning/lightning.routing.module.ts +++ b/frontend/src/app/lightning/lightning.routing.module.ts @@ -9,6 +9,7 @@ import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component'; import { NodesRanking } from './nodes-ranking/nodes-ranking.component'; import { NodesRankingsDashboard } from './nodes-rankings-dashboard/nodes-rankings-dashboard.component'; import { GroupComponent } from './group/group.component'; +import { JusticeList } from './justice-list/justice-list.component'; const routes: Routes = [ { @@ -66,6 +67,10 @@ const routes: Routes = [ type: 'oldest' }, }, + { + path: 'penalties', + component: JusticeList, + }, { path: '**', redirectTo: '' diff --git a/frontend/src/app/shared/components/truncate/truncate.component.html b/frontend/src/app/shared/components/truncate/truncate.component.html index 31d1b1b88..94208f3a4 100644 --- a/frontend/src/app/shared/components/truncate/truncate.component.html +++ b/frontend/src/app/shared/components/truncate/truncate.component.html @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/app/shared/components/truncate/truncate.component.scss b/frontend/src/app/shared/components/truncate/truncate.component.scss index ea69e32c3..315ce4e12 100644 --- a/frontend/src/app/shared/components/truncate/truncate.component.scss +++ b/frontend/src/app/shared/components/truncate/truncate.component.scss @@ -23,4 +23,8 @@ flex-shrink: 0; flex-grow: 0; } + + &.inline { + display: inline-flex; + } } \ No newline at end of file diff --git a/frontend/src/app/shared/components/truncate/truncate.component.ts b/frontend/src/app/shared/components/truncate/truncate.component.ts index 9edc6ddb2..0b6d2d8c1 100644 --- a/frontend/src/app/shared/components/truncate/truncate.component.ts +++ b/frontend/src/app/shared/components/truncate/truncate.component.ts @@ -11,6 +11,8 @@ export class TruncateComponent { @Input() link: any = null; @Input() lastChars: number = 4; @Input() maxWidth: number = null; + @Input() inline: boolean = false; + @Input() textAlign: 'start' | 'end' = 'start'; rtl: boolean; constructor( From d74e4b187600b2bf73d2bffd2fac228d1f5885f6 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 18 Jul 2023 17:15:54 +0900 Subject: [PATCH 2/3] Replacing loading text with spinner --- .../app/lightning/justice-list/justice-list.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/lightning/justice-list/justice-list.component.html b/frontend/src/app/lightning/justice-list/justice-list.component.html index 071c6f7f4..482ac9646 100644 --- a/frontend/src/app/lightning/justice-list/justice-list.component.html +++ b/frontend/src/app/lightning/justice-list/justice-list.component.html @@ -75,5 +75,9 @@ - loading... + +
+
+
+
From 67f58a4491ff809e745cf9b95179a96582e853f5 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 18 Jul 2023 17:19:14 +0900 Subject: [PATCH 3/3] Sorting by closing date descending --- backend/src/api/explorer/channels.api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index 67d775ea3..ab29ed2c2 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -127,6 +127,7 @@ class ChannelsApi { LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key WHERE channels.status = 2 AND channels.closing_reason = 3 + ORDER BY closing_date DESC `; const [rows]: any = await DB.query(query); return rows;