-
+
diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html
index 458c9362e..c85f63c78 100644
--- a/frontend/src/app/lightning/node/node.component.html
+++ b/frontend/src/app/lightning/node/node.component.html
@@ -1 +1,66 @@
-
node works!
+
+
+
+
+
+
+
+
+
+
+
+
+ First Seen |
+ {{ node.first_seen | date:'yyyy-MM-dd HH:mm' }} |
+
+
+ Updated At |
+ {{ node.updated_at | date:'yyyy-MM-dd HH:mm' }} |
+
+
+ Color |
+ {{ node.color }} |
+
+
+
+
+
+
+
+
+
+
+
+
Channels
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/lightning/node/node.component.scss b/frontend/src/app/lightning/node/node.component.scss
index e69de29bb..5ff5de482 100644
--- a/frontend/src/app/lightning/node/node.component.scss
+++ b/frontend/src/app/lightning/node/node.component.scss
@@ -0,0 +1,38 @@
+.qr-wrapper {
+ background-color: #FFF;
+ padding: 10px;
+ padding-bottom: 5px;
+ display: inline-block;
+}
+
+.qrcode-col {
+ margin: 20px auto 10px;
+ text-align: center;
+ @media (min-width: 992px){
+ margin: 0px auto 0px;
+ }
+}
+
+.tx-link {
+ display: flex;
+ flex-grow: 1;
+ @media (min-width: 650px) {
+ align-self: end;
+ margin-left: 15px;
+ margin-top: 0px;
+ margin-bottom: -3px;
+ }
+ @media (min-width: 768px) {
+ margin-bottom: 4px;
+ top: 1px;
+ position: relative;
+ }
+ @media (max-width: 768px) {
+ order: 3;
+ }
+}
+
+.title-container {
+ display: flex;
+ flex-direction: row;
+}
diff --git a/frontend/src/app/lightning/node/node.component.ts b/frontend/src/app/lightning/node/node.component.ts
index bfbc3d134..b62198d0d 100644
--- a/frontend/src/app/lightning/node/node.component.ts
+++ b/frontend/src/app/lightning/node/node.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@@ -7,10 +7,12 @@ import { LightningApiService } from '../lightning-api.service';
@Component({
selector: 'app-node',
templateUrl: './node.component.html',
- styleUrls: ['./node.component.scss']
+ styleUrls: ['./node.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodeComponent implements OnInit {
node$: Observable
;
+ publicKey$: Observable;
constructor(
private lightningApiService: LightningApiService,
@@ -21,7 +23,7 @@ export class NodeComponent implements OnInit {
this.node$ = this.activatedRoute.paramMap
.pipe(
switchMap((params: ParamMap) => {
- return this.lightningApiService.getNode$(params.get('id'));
+ return this.lightningApiService.getNode$(params.get('public_key'));
})
);
}
diff --git a/frontend/src/app/shared/components/sats/sats.component.html b/frontend/src/app/shared/components/sats/sats.component.html
new file mode 100644
index 000000000..8358812a9
--- /dev/null
+++ b/frontend/src/app/shared/components/sats/sats.component.html
@@ -0,0 +1,5 @@
+{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number }}
+L-
+tL-
+t-
+s-sats
diff --git a/frontend/src/app/shared/components/sats/sats.component.scss b/frontend/src/app/shared/components/sats/sats.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/shared/components/sats/sats.component.ts b/frontend/src/app/shared/components/sats/sats.component.ts
new file mode 100644
index 000000000..f341b3027
--- /dev/null
+++ b/frontend/src/app/shared/components/sats/sats.component.ts
@@ -0,0 +1,32 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs';
+import { StateService } from '../../../services/state.service';
+
+@Component({
+ selector: 'app-sats',
+ templateUrl: './sats.component.html',
+ styleUrls: ['./sats.component.scss']
+})
+export class SatsComponent implements OnInit {
+ @Input() satoshis: number;
+ @Input() digitsInfo = 0;
+ @Input() addPlus = false;
+
+ network = '';
+ stateSubscription: Subscription;
+
+ constructor(
+ private stateService: StateService,
+ ) { }
+
+ ngOnInit() {
+ this.stateSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
+ }
+
+ ngOnDestroy() {
+ if (this.stateSubscription) {
+ this.stateSubscription.unsubscribe();
+ }
+ }
+
+}
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index a587c6934..7e8914000 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -74,6 +74,7 @@ import { LoadingIndicatorComponent } from '../components/loading-indicator/loadi
import { IndexingProgressComponent } from '../components/indexing-progress/indexing-progress.component';
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
import { ChangeComponent } from '../components/change/change.component';
+import { SatsComponent } from './components/sats/sats.component';
@NgModule({
declarations: [
@@ -142,6 +143,7 @@ import { ChangeComponent } from '../components/change/change.component';
IndexingProgressComponent,
SvgImagesComponent,
ChangeComponent,
+ SatsComponent,
],
imports: [
CommonModule,
@@ -238,6 +240,7 @@ import { ChangeComponent } from '../components/change/change.component';
IndexingProgressComponent,
SvgImagesComponent,
ChangeComponent,
+ SatsComponent
]
})
export class SharedModule {
diff --git a/lightning-backend/src/api/nodes/channels.api.ts b/lightning-backend/src/api/nodes/channels.api.ts
index 6b4905bd7..9f02981c3 100644
--- a/lightning-backend/src/api/nodes/channels.api.ts
+++ b/lightning-backend/src/api/nodes/channels.api.ts
@@ -2,9 +2,20 @@ import logger from '../../logger';
import DB from '../../database';
class ChannelsApi {
+ public async $getChannel(shortId: string): 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.id = ?`;
+ const [rows]: any = await DB.query(query, [shortId]);
+ return rows[0];
+ } catch (e) {
+ logger.err('$getChannel error: ' + (e instanceof Error ? e.message : e));
+ throw e;
+ }
+ }
+
public async $getChannelsForNode(public_key: string): Promise {
try {
- const query = `SELECT * FROM channels WHERE node1_public_key = ? OR node2_public_key = ?`;
+ 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 node1_public_key = ? OR node2_public_key = ?`;
const [rows]: any = await DB.query(query, [public_key, public_key]);
return rows;
} catch (e) {
diff --git a/lightning-backend/src/api/nodes/channels.routes.ts b/lightning-backend/src/api/nodes/channels.routes.ts
index 70bf0bdad..4cb3f8b1d 100644
--- a/lightning-backend/src/api/nodes/channels.routes.ts
+++ b/lightning-backend/src/api/nodes/channels.routes.ts
@@ -3,16 +3,36 @@ import { Express, Request, Response } from 'express';
import channelsApi from './channels.api';
class ChannelsRoutes {
- constructor(app: Express) {
+ constructor() { }
+
+ public initRoutes(app: Express) {
app
- .get(config.MEMPOOL.API_URL_PREFIX + 'channels/:public_key', this.$getChannels)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'channels/:short_id', this.$getChannel)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'channels', this.$getChannels)
;
}
+ private async $getChannel(req: Request, res: Response) {
+ try {
+ const channel = await channelsApi.$getChannel(req.params.short_id);
+ if (!channel) {
+ res.status(404).send('Channel not found');
+ return;
+ }
+ res.json(channel);
+ } catch (e) {
+ res.status(500).send(e instanceof Error ? e.message : e);
+ }
+ }
+
private async $getChannels(req: Request, res: Response) {
try {
- const channels = await channelsApi.$getChannelsForNode(req.params.public_key);
- res.json(channels);
+ if (typeof req.query.public_key !== 'string') {
+ res.status(501).send('Missing parameter: public_key');
+ return;
+ }
+ const channels = await channelsApi.$getChannelsForNode(req.query.public_key);
+ res.json(channels);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
@@ -20,4 +40,4 @@ class ChannelsRoutes {
}
-export default ChannelsRoutes;
+export default new ChannelsRoutes();
diff --git a/lightning-backend/src/api/nodes/nodes.routes.ts b/lightning-backend/src/api/nodes/nodes.routes.ts
index ad254959c..73bef9f26 100644
--- a/lightning-backend/src/api/nodes/nodes.routes.ts
+++ b/lightning-backend/src/api/nodes/nodes.routes.ts
@@ -1,14 +1,15 @@
import config from '../../config';
import { Express, Request, Response } from 'express';
import nodesApi from './nodes.api';
-import channelsApi from './channels.api';
class NodesRoutes {
- constructor(app: Express) {
+ constructor() { }
+
+ public initRoutes(app: Express) {
app
- .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats)
- .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes)
- .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode)
- ;
+ .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode)
+ ;
}
private async $getNode(req: Request, res: Response) {
@@ -47,4 +48,4 @@ class NodesRoutes {
}
}
-export default NodesRoutes;
+export default new NodesRoutes();
diff --git a/lightning-backend/src/index.ts b/lightning-backend/src/index.ts
index 49275f782..614ac8499 100644
--- a/lightning-backend/src/index.ts
+++ b/lightning-backend/src/index.ts
@@ -1,21 +1,14 @@
import config from './config';
-import * as express from 'express';
-import * as http from 'http';
import logger from './logger';
import DB from './database';
-import { Express, Request, Response, NextFunction } from 'express';
import databaseMigration from './database-migration';
import statsUpdater from './tasks/stats-updater.service';
import nodeSyncService from './tasks/node-sync.service';
-import NodesRoutes from './api/nodes/nodes.routes';
-import ChannelsRoutes from './api/nodes/channels.routes';
+import server from './server';
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
class LightningServer {
- private server: http.Server | undefined;
- private app: Express = express();
-
constructor() {
this.init();
}
@@ -27,27 +20,7 @@ class LightningServer {
statsUpdater.startService();
nodeSyncService.startService();
- this.startServer();
- }
-
- startServer() {
- this.app
- .use((req: Request, res: Response, next: NextFunction) => {
- res.setHeader('Access-Control-Allow-Origin', '*');
- next();
- })
- .use(express.urlencoded({ extended: true }))
- .use(express.text())
- ;
-
- this.server = http.createServer(this.app);
-
- this.server.listen(config.MEMPOOL.HTTP_PORT, () => {
- logger.notice(`Mempool Lightning is running on port ${config.MEMPOOL.HTTP_PORT}`);
- });
-
- const nodeRoutes = new NodesRoutes(this.app);
- const channelsRoutes = new ChannelsRoutes(this.app);
+ server.startServer();
}
}
diff --git a/lightning-backend/src/server.ts b/lightning-backend/src/server.ts
new file mode 100644
index 000000000..26608b0c4
--- /dev/null
+++ b/lightning-backend/src/server.ts
@@ -0,0 +1,38 @@
+import { Express, Request, Response, NextFunction } from 'express';
+import * as express from 'express';
+import * as http from 'http';
+import logger from './logger';
+import config from './config';
+import nodesRoutes from './api/nodes/nodes.routes';
+import channelsRoutes from './api/nodes/channels.routes';
+
+class Server {
+ private server: http.Server | undefined;
+ private app: Express = express();
+
+ public startServer() {
+ this.app
+ .use((req: Request, res: Response, next: NextFunction) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ next();
+ })
+ .use(express.urlencoded({ extended: true }))
+ .use(express.text())
+ ;
+
+ this.server = http.createServer(this.app);
+
+ this.server.listen(config.MEMPOOL.HTTP_PORT, () => {
+ logger.notice(`Mempool Lightning is running on port ${config.MEMPOOL.HTTP_PORT}`);
+ });
+
+ this.initRoutes();
+ }
+
+ private initRoutes() {
+ nodesRoutes.initRoutes(this.app);
+ channelsRoutes.initRoutes(this.app);
+ }
+}
+
+export default new Server();
diff --git a/lightning-backend/src/tasks/stats-updater.service.ts b/lightning-backend/src/tasks/stats-updater.service.ts
index c96a3d6b4..0c61922e9 100644
--- a/lightning-backend/src/tasks/stats-updater.service.ts
+++ b/lightning-backend/src/tasks/stats-updater.service.ts
@@ -15,12 +15,13 @@ class LightningStatsUpdater {
setTimeout(() => {
this.$logLightningStats();
- this.$logNodeStatsDaily();
setInterval(() => {
this.$logLightningStats();
this.$logNodeStatsDaily();
}, 1000 * 60 * 60);
}, difference);
+
+ this.$logNodeStatsDaily();
}
private async $logNodeStatsDaily() {
@@ -28,7 +29,7 @@ class LightningStatsUpdater {
try {
const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`);
// Only store once per day
- if (state[0] === currentDate) {
+ if (state[0].string === currentDate) {
return;
}
@@ -42,6 +43,7 @@ class LightningStatsUpdater {
node.channels_count_left, node.channels_count_right]);
}
await DB.query(`UPDATE state SET string = ? WHERE name = 'last_node_stats'`, [currentDate]);
+ logger.debug('Daily node stats has updated.');
} catch (e) {
logger.err('$logNodeStatsDaily() error: ' + (e instanceof Error ? e.message : e));
}
@@ -49,22 +51,26 @@ class LightningStatsUpdater {
private async $logLightningStats() {
try {
- const networkInfo = await lightningApi.$getNetworkInfo();
+ const networkGraph = await lightningApi.$getNetworkGraph();
+ let total_capacity = 0;
+ for (const channel of networkGraph.channels) {
+ if (channel.capacity) {
+ total_capacity += channel.capacity;
+ }
+ }
const query = `INSERT INTO statistics(
added,
channel_count,
node_count,
- total_capacity,
- average_channel_size
+ total_capacity
)
- VALUES (NOW(), ?, ?, ?, ?)`;
+ VALUES (NOW(), ?, ?, ?)`;
await DB.query(query, [
- networkInfo.channel_count,
- networkInfo.node_count,
- networkInfo.total_capacity,
- networkInfo.average_channel_size
+ networkGraph.channels.length,
+ networkGraph.nodes.length,
+ total_capacity,
]);
} catch (e) {
logger.err('$logLightningStats() error: ' + (e instanceof Error ? e.message : e));