Skip to content

Commit 5114f10

Browse files
authored
Subscribe to blocks and peers events on the ui (#267)
* Subscribe to blocks and peers events on the ui * Add disposable to ui event subscription * Add notifications for wallet sent coins and refresh UI on such notifications
1 parent 1beb281 commit 5114f10

5 files changed

Lines changed: 268 additions & 109 deletions

File tree

Lines changed: 147 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
@page "/"
22

3+
@implements IDisposable
4+
35
@using Blockcore.Utilities.Extensions
46
@using Blockcore.Networks
57
@using Blockcore.UI.BlazorModal
68
@using Blockcore.Consensus.Chain
9+
@using Blockcore.EventBus
10+
@using Blockcore.EventBus.CoreEvents
11+
@using Blockcore.EventBus.CoreEvents.Peer
12+
@using Blockcore.Signals
713

814
@inject IFullNode FullNode
915
@inject Network Network
@@ -12,142 +18,145 @@
1218
@inject Blockcore.Interfaces.IInitialBlockDownloadState InitialBlockDownloadState
1319
@inject NavigationManager NavigationManager
1420
@inject ModalService ModalService
21+
@inject ISignals Signals
1522

1623
@{
17-
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
18-
<h1 class="h2"><strong>@this.Network.CoinTicker.ToUpper() Network</strong></h1>
19-
<div class="btn-toolbar mb-2 mb-md-0">
20-
<button class="btn btn-sm btn-primary mr-1" @onclick="AddNode"><span class="oi oi-plus" aria-hidden="true"></span> Add Node</button>
21-
<button class="btn btn-sm btn-primary mr-1" @onclick="() => { NavigateToLogs(); }"><span class="oi oi-list" aria-hidden="true"></span> View Logs</button>
22-
<button class="btn btn-sm btn-danger" @onclick="() => { Shutdown(); }"><span class="oi oi-power-standby" aria-hidden="true"></span> Shutdown</button>
24+
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
25+
<h1 class="h2"><strong>@this.Network.CoinTicker.ToUpper() Network</strong></h1>
26+
<div class="btn-toolbar mb-2 mb-md-0">
27+
<button class="btn btn-sm btn-primary mr-1" @onclick="AddNode"><span class="oi oi-plus" aria-hidden="true"></span> Add Node</button>
28+
<button class="btn btn-sm btn-primary mr-1" @onclick="() => { NavigateToLogs(); }"><span class="oi oi-list" aria-hidden="true"></span> View Logs</button>
29+
<button class="btn btn-sm btn-danger" @onclick="() => { Shutdown(); }"><span class="oi oi-power-standby" aria-hidden="true"></span> Shutdown</button>
30+
</div>
2331
</div>
24-
</div>
25-
<div class="row mb-3">
26-
<div class="col-xl-3 col-sm-6 ">
27-
<div class="card">
28-
<div class="card-body">
29-
<div class="row">
30-
<div class="col-9">
31-
<div class="d-flex align-items-center align-self-start">
32-
<h3 class="text-success mb-0">@this.Network.CoinTicker.ToUpper()</h3>
33-
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
32+
<div class="row mb-3">
33+
<div class="col-xl-3 col-sm-6 ">
34+
<div class="card">
35+
<div class="card-body">
36+
<div class="row">
37+
<div class="col-9">
38+
<div class="d-flex align-items-center align-self-start">
39+
<h3 class="text-success mb-0">@this.Network.CoinTicker.ToUpper()</h3>
40+
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
41+
</div>
3442
</div>
35-
</div>
36-
<div class="col-3">
37-
<div class="icon icon-box-success ">
38-
<span class="mdi mdi-arrow-top-right icon-item"></span>
43+
<div class="col-3">
44+
<div class="icon icon-box-success ">
45+
<span class="mdi mdi-arrow-top-right icon-item"></span>
46+
</div>
3947
</div>
4048
</div>
49+
<h6 class="text-muted font-weight-normal">Network</h6>
4150
</div>
42-
<h6 class="text-muted font-weight-normal">Network</h6>
4351
</div>
4452
</div>
45-
</div>
46-
<div class="col-xl-3 col-sm-6 ">
47-
<div class="card">
48-
<div class="card-body">
49-
<div class="row">
50-
<div class="col-9">
51-
<div class="d-flex align-items-center align-self-start">
52-
<h3 class="text-success mb-0">@this.ConnectionManager.ConnectedPeers.Count()</h3>
53-
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
53+
<div class="col-xl-3 col-sm-6 ">
54+
<div class="card">
55+
<div class="card-body">
56+
<div class="row">
57+
<div class="col-9">
58+
<div class="d-flex align-items-center align-self-start">
59+
<h3 class="text-success mb-0">@this.ConnectionManager.ConnectedPeers.Count()</h3>
60+
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
61+
</div>
5462
</div>
55-
</div>
56-
<div class="col-3">
57-
<div class="icon icon-box-success ">
58-
<span class="mdi mdi-arrow-top-right icon-item"></span>
63+
<div class="col-3">
64+
<div class="icon icon-box-success ">
65+
<span class="mdi mdi-arrow-top-right icon-item"></span>
66+
</div>
5967
</div>
6068
</div>
69+
<h6 class="text-muted font-weight-normal">Peers</h6>
6170
</div>
62-
<h6 class="text-muted font-weight-normal">Peers</h6>
6371
</div>
6472
</div>
65-
</div>
66-
<div class="col-xl-3 col-sm-6 ">
67-
<div class="card">
68-
<div class="card-body">
69-
<div class="row">
70-
<div class="col-9">
71-
<div class="d-flex align-items-center align-self-start">
72-
@if (this.InitialBlockDownloadState.IsInitialBlockDownload())
73-
{
74-
<h3 class="oi oi-circle-x text-danger" aria-hidden="true"></h3>
75-
}
76-
else
77-
{
78-
<h3 class="oi oi-circle-check text-success" aria-hidden="true"></h3>
79-
}
80-
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
73+
<div class="col-xl-3 col-sm-6 ">
74+
<div class="card">
75+
<div class="card-body">
76+
<div class="row">
77+
<div class="col-9">
78+
<div class="d-flex align-items-center align-self-start">
79+
@if (this.InitialBlockDownloadState.IsInitialBlockDownload())
80+
{
81+
<h3 class="oi oi-circle-x text-danger" aria-hidden="true"></h3>
82+
}
83+
else
84+
{
85+
<h3 class="oi oi-circle-check text-success" aria-hidden="true"></h3>
86+
}
87+
<p class="text-success ml-2 mb-0 font-weight-medium"></p>
88+
</div>
8189
</div>
82-
</div>
83-
<div class="col-3">
84-
<div class="icon icon-box-success">
85-
<span class="mdi mdi-arrow-top-right icon-item"></span>
90+
<div class="col-3">
91+
<div class="icon icon-box-success">
92+
<span class="mdi mdi-arrow-top-right icon-item"></span>
93+
</div>
8694
</div>
8795
</div>
88-
</div>
89-
@if (this.InitialBlockDownloadState.IsInitialBlockDownload())
96+
@if (this.InitialBlockDownloadState.IsInitialBlockDownload())
97+
{
98+
<h6 class="text-danger font-weight-normal">Chain Syncing</h6>
99+
}
100+
else
90101
{
91-
<h6 class="text-danger font-weight-normal">Chain Syncing</h6>
92-
} else {
93102
<h6 class="text-muted font-weight-normal">Chain Synced</h6>
94103
}
104+
</div>
95105
</div>
96106
</div>
97-
</div>
98-
<div class="col-xl-3 col-sm-6 ">
99-
<div class="card">
100-
<div class="card-body">
101-
<div class="row">
102-
<div class="col-9">
103-
<div class="d-flex align-items-center align-self-start">
104-
<h3 class="text-success mb-0">@this.ChainIndexer.Tip.Height</h3>
105-
<p class="text-danger ml-2 mb-0 font-weight-medium"></p>
107+
<div class="col-xl-3 col-sm-6 ">
108+
<div class="card">
109+
<div class="card-body">
110+
<div class="row">
111+
<div class="col-9">
112+
<div class="d-flex align-items-center align-self-start">
113+
<h3 class="text-success mb-0">@this.ChainIndexer.Tip.Height</h3>
114+
<p class="text-danger ml-2 mb-0 font-weight-medium"></p>
115+
</div>
106116
</div>
107-
</div>
108-
<div class="col-3">
109-
<div class="icon icon-box-danger">
110-
<span class="mdi mdi-arrow-bottom-left icon-item"></span>
117+
<div class="col-3">
118+
<div class="icon icon-box-danger">
119+
<span class="mdi mdi-arrow-bottom-left icon-item"></span>
120+
</div>
111121
</div>
112122
</div>
123+
<h6 class="text-muted font-weight-normal">Block Height</h6>
113124
</div>
114-
<h6 class="text-muted font-weight-normal">Block Height</h6>
115125
</div>
116126
</div>
117127
</div>
118-
</div>
119-
<div class="row">
120-
<div class="col-xl-12 col-sm-12 ">
121-
<div class="card">
122-
<div class="mx-3 mt-3">
123-
<h4 class="card-title">Peers</h4>
124-
<div class="table-responsive small">
125-
<table class="table table-border-bottom table-striped table-sm table-hover">
126-
<thead class="thead">
127-
<tr>
128-
<th class="text-primary"><strong>IP ADDRESS</strong></th>
129-
<th class="text-primary"><strong>CONNECTION</strong></th>
130-
<th class="text-primary"><strong>AGENT</strong></th>
131-
<th class="text-primary"><strong>VERSION</strong></th>
132-
</tr>
133-
</thead>
134-
<tbody>
135-
@foreach (var peer in this.ConnectionManager.ConnectedPeers)
136-
{
128+
<div class="row">
129+
<div class="col-xl-12 col-sm-12 ">
130+
<div class="card">
131+
<div class="mx-3 mt-3">
132+
<h4 class="card-title">Peers</h4>
133+
<div class="table-responsive small">
134+
<table class="table table-border-bottom table-striped table-sm table-hover">
135+
<thead class="thead">
137136
<tr>
138-
<td class="align-middle"><h5 class="oi oi-monitor" aria-hidden="true"></h5> @peer.RemoteSocketEndpoint.ToString()</td>
139-
<td class="align-middle">@(peer.Inbound ? "Inbound" : "Outbound") </td>
140-
<td class="align-middle">@peer.PeerVersion?.UserAgent</td>
141-
<td class="align-middle">@peer.PeerVersion?.Version</td>
137+
<th class="text-primary"><strong>IP ADDRESS</strong></th>
138+
<th class="text-primary"><strong>CONNECTION</strong></th>
139+
<th class="text-primary"><strong>AGENT</strong></th>
140+
<th class="text-primary"><strong>VERSION</strong></th>
142141
</tr>
143-
}
144-
</tbody>
145-
</table>
142+
</thead>
143+
<tbody>
144+
@foreach (var peer in this.ConnectionManager.ConnectedPeers)
145+
{
146+
<tr>
147+
<td class="align-middle"><h5 class="oi oi-monitor" aria-hidden="true"></h5> @peer.RemoteSocketEndpoint.ToString()</td>
148+
<td class="align-middle">@(peer.Inbound ? "Inbound" : "Outbound") </td>
149+
<td class="align-middle">@peer.PeerVersion?.UserAgent</td>
150+
<td class="align-middle">@peer.PeerVersion?.Version</td>
151+
</tr>
152+
}
153+
</tbody>
154+
</table>
155+
</div>
146156
</div>
147157
</div>
148158
</div>
149159
</div>
150-
</div>
151160
}
152161
@code
153162
{
@@ -163,4 +172,42 @@
163172
{
164173
ModalService.Show("Add Node", typeof(Modal.ModalAddNode));
165174
}
175+
176+
List<SubscriptionToken> subscriptionTokens;
177+
178+
protected override void OnAfterRender(bool firstRender)
179+
{
180+
if (firstRender && this.Signals != null)
181+
{
182+
this.subscriptionTokens = new List<SubscriptionToken>()
183+
{
184+
this.Signals.Subscribe<BlockConnected>(this.ReloadEvent),
185+
this.Signals.Subscribe<PeerConnected>(this.ReloadEvent),
186+
this.Signals.Subscribe<PeerDisconnected>(this.ReloadEvent)
187+
};
188+
}
189+
}
190+
191+
DateTime lastRefresh = DateTime.UtcNow;
192+
193+
private void ReloadEvent(object _)
194+
{
195+
if ((DateTime.UtcNow - lastRefresh).Seconds > 3)
196+
{
197+
lastRefresh = DateTime.UtcNow;
198+
199+
InvokeAsync(this.StateHasChanged);
200+
}
201+
}
202+
203+
public void Dispose()
204+
{
205+
if (subscriptionTokens != null)
206+
{
207+
foreach (var subscriptionToken in subscriptionTokens)
208+
{
209+
subscriptionToken.Dispose();
210+
}
211+
}
212+
}
166213
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Blockcore.Consensus.TransactionInfo;
5+
using Blockcore.EventBus;
6+
using NBitcoin;
7+
8+
namespace Blockcore.Features.Wallet.Events
9+
{
10+
/// <summary>
11+
/// Event that is executed when a transactions output is spent in the wallet.
12+
/// </summary>
13+
/// <seealso cref="Blockcore.EventBus.EventBase" />
14+
public class TransactionSpent : EventBase
15+
{
16+
public Transaction SpentTransaction { get; }
17+
18+
public OutPoint SpentOutPoint { get; }
19+
20+
public TransactionSpent(Transaction spentTransaction, OutPoint spentOutPoint)
21+
{
22+
this.SpentTransaction = spentTransaction;
23+
this.SpentOutPoint = spentOutPoint;
24+
}
25+
}
26+
}

src/Features/Blockcore.Features.Wallet/UI/Pages/WalletView.razor

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
@page "/walletview/{walletname}/{accountname}"
2+
@implements IDisposable
23

34
@using Blockcore.Features.Wallet.Interfaces
45
@using NBitcoin;
56
@using Blockcore.Features.Wallet.Api.Controllers
67
@using Blockcore.Features.Wallet.Api.Models
8+
@using Blockcore.Features.Wallet.Events
79
@using Blockcore.Networks
10+
@using Blockcore.Signals
811
@using Blockcore.UI.BlazorModal
12+
@using Blockcore.EventBus
913

1014
@inject NavigationManager NavigationManager
1115
@inject IWalletManager WalletManager
1216
@inject Network Network
1317
@inject ModalService ModalService
18+
@inject ISignals Signals
1419

1520
@{
1621
var accountBalance = this.WalletManager.GetBalances(walletname, accountname).Single();
@@ -127,4 +132,41 @@
127132
Console.WriteLine(selection);
128133
NavigateToWallet(selection, "account 0");
129134
}
135+
136+
List<SubscriptionToken> subscriptionTokens;
137+
138+
protected override void OnAfterRender(bool firstRender)
139+
{
140+
if (firstRender && this.Signals != null)
141+
{
142+
this.subscriptionTokens = new List<SubscriptionToken>()
143+
{
144+
this.Signals.Subscribe<TransactionFound>(this.ReloadEvent),
145+
this.Signals.Subscribe<TransactionSpent>(this.ReloadEvent),
146+
};
147+
}
148+
}
149+
150+
DateTime lastRefresh = DateTime.UtcNow;
151+
152+
private void ReloadEvent(object _)
153+
{
154+
if ((DateTime.UtcNow - lastRefresh).Seconds > 1)
155+
{
156+
lastRefresh = DateTime.UtcNow;
157+
158+
InvokeAsync(this.StateHasChanged);
159+
}
160+
}
161+
162+
public void Dispose()
163+
{
164+
if (subscriptionTokens != null)
165+
{
166+
foreach (var subscriptionToken in subscriptionTokens)
167+
{
168+
subscriptionToken.Dispose();
169+
}
170+
}
171+
}
130172
}

0 commit comments

Comments
 (0)