Qt: improve channel details window
This commit is contained in:
@@ -208,7 +208,7 @@ class ChannelDetailsPopup(Popup):
|
|||||||
self.funding_txid = chan.funding_outpoint.txid
|
self.funding_txid = chan.funding_outpoint.txid
|
||||||
self.short_id = format_short_channel_id(chan.short_channel_id)
|
self.short_id = format_short_channel_id(chan.short_channel_id)
|
||||||
self.capacity = self.app.format_amount_and_units(chan.constraints.capacity)
|
self.capacity = self.app.format_amount_and_units(chan.constraints.capacity)
|
||||||
self.state = self.app.wallet.lnworker.get_channel_status(chan)
|
self.state = chan.get_state_for_GUI()
|
||||||
self.local_ctn = chan.get_latest_ctn(LOCAL)
|
self.local_ctn = chan.get_latest_ctn(LOCAL)
|
||||||
self.remote_ctn = chan.get_latest_ctn(REMOTE)
|
self.remote_ctn = chan.get_latest_ctn(REMOTE)
|
||||||
self.local_csv = chan.config[LOCAL].to_self_delay
|
self.local_csv = chan.config[LOCAL].to_self_delay
|
||||||
@@ -297,7 +297,7 @@ class LightningChannelsDialog(Factory.Popup):
|
|||||||
|
|
||||||
def update_item(self, item):
|
def update_item(self, item):
|
||||||
chan = item._chan
|
chan = item._chan
|
||||||
item.status = self.app.wallet.lnworker.get_channel_status(chan)
|
item.status = chan.get_state_for_GUI()
|
||||||
item.short_channel_id = format_short_channel_id(chan.short_channel_id)
|
item.short_channel_id = format_short_channel_id(chan.short_channel_id)
|
||||||
l, r = self.format_fields(chan)
|
l, r = self.format_fields(chan)
|
||||||
item.local_balance = _('Local') + ':' + l
|
item.local_balance = _('Local') + ':' + l
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ from typing import TYPE_CHECKING
|
|||||||
import PyQt5.QtGui as QtGui
|
import PyQt5.QtGui as QtGui
|
||||||
import PyQt5.QtWidgets as QtWidgets
|
import PyQt5.QtWidgets as QtWidgets
|
||||||
import PyQt5.QtCore as QtCore
|
import PyQt5.QtCore as QtCore
|
||||||
|
from PyQt5.QtWidgets import QLabel, QLineEdit
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import bh2u, format_time
|
from electrum.util import bh2u, format_time
|
||||||
from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction
|
from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction
|
||||||
from electrum.lnchannel import htlcsum
|
from electrum.lnchannel import htlcsum, Channel
|
||||||
from electrum.lnaddr import LnAddr, lndecode
|
from electrum.lnaddr import LnAddr, lndecode
|
||||||
from electrum.bitcoin import COIN
|
from electrum.bitcoin import COIN
|
||||||
|
|
||||||
from .util import Buttons, CloseButton
|
from .util import Buttons, CloseButton, ButtonsLineEdit
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .main_window import ElectrumWindow
|
from .main_window import ElectrumWindow
|
||||||
@@ -34,7 +35,7 @@ class LinkedLabel(QtWidgets.QLabel):
|
|||||||
class ChannelDetailsDialog(QtWidgets.QDialog):
|
class ChannelDetailsDialog(QtWidgets.QDialog):
|
||||||
def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem:
|
def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem:
|
||||||
it = HTLCItem(_('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id))
|
it = HTLCItem(_('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id))
|
||||||
it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format(i.amount_msat))])
|
it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format_msat(i.amount_msat))])
|
||||||
it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))])
|
it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))])
|
||||||
it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(bh2u(i.payment_hash))])
|
it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(bh2u(i.payment_hash))])
|
||||||
return it
|
return it
|
||||||
@@ -76,7 +77,14 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
|||||||
dest_mapping[payment_hash] = len(dest_mapping)
|
dest_mapping[payment_hash] = len(dest_mapping)
|
||||||
|
|
||||||
ln_payment_completed = QtCore.pyqtSignal(str, bytes, bytes)
|
ln_payment_completed = QtCore.pyqtSignal(str, bytes, bytes)
|
||||||
|
ln_payment_failed = QtCore.pyqtSignal(str, bytes, bytes)
|
||||||
htlc_added = QtCore.pyqtSignal(str, UpdateAddHtlc, LnAddr, Direction)
|
htlc_added = QtCore.pyqtSignal(str, UpdateAddHtlc, LnAddr, Direction)
|
||||||
|
state_changed = QtCore.pyqtSignal(str, Channel)
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(str, Channel)
|
||||||
|
def do_state_changed(self, chan):
|
||||||
|
if chan == self.chan:
|
||||||
|
self.update()
|
||||||
|
|
||||||
@QtCore.pyqtSlot(str, UpdateAddHtlc, LnAddr, Direction)
|
@QtCore.pyqtSlot(str, UpdateAddHtlc, LnAddr, Direction)
|
||||||
def do_htlc_added(self, evtname, htlc, lnaddr, direction):
|
def do_htlc_added(self, evtname, htlc, lnaddr, direction):
|
||||||
@@ -89,11 +97,20 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
|||||||
if chan_id != self.chan.channel_id:
|
if chan_id != self.chan.channel_id:
|
||||||
return
|
return
|
||||||
self.move('inflight', 'settled', payment_hash)
|
self.move('inflight', 'settled', payment_hash)
|
||||||
self.update_sent_received()
|
self.update()
|
||||||
|
|
||||||
def update_sent_received(self):
|
@QtCore.pyqtSlot(str, bytes, bytes)
|
||||||
self.sent_label.setText(str(self.chan.total_msat(Direction.SENT)))
|
def do_ln_payment_failed(self, evtname, payment_hash, chan_id):
|
||||||
self.received_label.setText(str(self.chan.total_msat(Direction.RECEIVED)))
|
if chan_id != self.chan.channel_id:
|
||||||
|
return
|
||||||
|
self.move('inflight', 'failed', payment_hash)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.can_send_label.setText(self.format_msat(self.chan.available_to_spend(LOCAL)))
|
||||||
|
self.can_receive_label.setText(self.format_msat(self.chan.available_to_spend(REMOTE)))
|
||||||
|
self.sent_label.setText(self.format_msat(self.chan.total_msat(Direction.SENT)))
|
||||||
|
self.received_label.setText(self.format_msat(self.chan.total_msat(Direction.RECEIVED)))
|
||||||
|
|
||||||
@QtCore.pyqtSlot(str)
|
@QtCore.pyqtSlot(str)
|
||||||
def show_tx(self, link_text: str):
|
def show_tx(self, link_text: str):
|
||||||
@@ -106,15 +123,19 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
|||||||
# initialize instance fields
|
# initialize instance fields
|
||||||
self.window = window
|
self.window = window
|
||||||
chan = self.chan = window.wallet.lnworker.channels[chan_id]
|
chan = self.chan = window.wallet.lnworker.channels[chan_id]
|
||||||
self.format = lambda msat: window.format_amount_and_units(msat / 1000)
|
self.format_msat = lambda msat: window.format_amount_and_units(msat / 1000)
|
||||||
|
|
||||||
# connect signals with slots
|
# connect signals with slots
|
||||||
self.ln_payment_completed.connect(self.do_ln_payment_completed)
|
self.ln_payment_completed.connect(self.do_ln_payment_completed)
|
||||||
|
self.ln_payment_failed.connect(self.do_ln_payment_failed)
|
||||||
|
self.state_changed.connect(self.do_state_changed)
|
||||||
self.htlc_added.connect(self.do_htlc_added)
|
self.htlc_added.connect(self.do_htlc_added)
|
||||||
|
|
||||||
# register callbacks for updating
|
# register callbacks for updating
|
||||||
window.network.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed'])
|
window.network.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed'])
|
||||||
|
window.network.register_callback(self.ln_payment_failed.emit, ['ln_payment_failed'])
|
||||||
window.network.register_callback(self.htlc_added.emit, ['htlc_added'])
|
window.network.register_callback(self.htlc_added.emit, ['htlc_added'])
|
||||||
|
window.network.register_callback(self.state_changed.emit, ['channel'])
|
||||||
|
|
||||||
# set attributes of QDialog
|
# set attributes of QDialog
|
||||||
self.setWindowTitle(_('Channel Details'))
|
self.setWindowTitle(_('Channel Details'))
|
||||||
@@ -122,32 +143,44 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
# add layouts
|
# add layouts
|
||||||
vbox = QtWidgets.QVBoxLayout(self)
|
vbox = QtWidgets.QVBoxLayout(self)
|
||||||
form_layout = QtWidgets.QFormLayout(None)
|
vbox.addWidget(QLabel(_('Remote Node ID:')))
|
||||||
vbox.addLayout(form_layout)
|
remote_id_e = ButtonsLineEdit(bh2u(chan.node_id))
|
||||||
|
remote_id_e.addCopyButton(self.window.app)
|
||||||
# add form content
|
vbox.addWidget(remote_id_e)
|
||||||
form_layout.addRow(_('Node ID:'), SelectableLabel(bh2u(chan.node_id)))
|
|
||||||
form_layout.addRow(_('Channel ID:'), SelectableLabel(bh2u(chan.channel_id)))
|
|
||||||
funding_label_text = f'<a href=click_destination>{chan.funding_outpoint.txid}</a>:{chan.funding_outpoint.output_index}'
|
funding_label_text = f'<a href=click_destination>{chan.funding_outpoint.txid}</a>:{chan.funding_outpoint.output_index}'
|
||||||
form_layout.addRow(_('Funding Outpoint:'), LinkedLabel(funding_label_text, self.show_tx))
|
vbox.addWidget(QLabel(_('Funding Outpoint:')))
|
||||||
form_layout.addRow(_('Short Channel ID:'), SelectableLabel(format_short_channel_id(chan.short_channel_id)))
|
vbox.addWidget(LinkedLabel(funding_label_text, self.show_tx))
|
||||||
|
|
||||||
|
form_layout = QtWidgets.QFormLayout(None)
|
||||||
|
# add form content
|
||||||
|
form_layout.addRow(_('Channel ID:'), SelectableLabel(chan.get_id_for_log()))
|
||||||
|
form_layout.addRow(_('State:'), SelectableLabel(chan.get_state_for_GUI()))
|
||||||
|
self.initiator = 'Local' if chan.constraints.is_initiator else 'Remote'
|
||||||
|
form_layout.addRow(_('Initiator:'), SelectableLabel(self.initiator))
|
||||||
|
self.capacity = self.window.format_amount_and_units(chan.constraints.capacity)
|
||||||
|
form_layout.addRow(_('Capacity:'), SelectableLabel(self.capacity))
|
||||||
|
self.can_send_label = SelectableLabel()
|
||||||
|
self.can_receive_label = SelectableLabel()
|
||||||
|
form_layout.addRow(_('Can send:'), self.can_send_label)
|
||||||
|
form_layout.addRow(_('Can receive:'), self.can_receive_label)
|
||||||
self.received_label = SelectableLabel()
|
self.received_label = SelectableLabel()
|
||||||
form_layout.addRow(_('Received (mSAT):'), self.received_label)
|
form_layout.addRow(_('Received:'), self.received_label)
|
||||||
self.sent_label = SelectableLabel()
|
self.sent_label = SelectableLabel()
|
||||||
form_layout.addRow(_('Sent (mSAT):'), self.sent_label)
|
form_layout.addRow(_('Sent:'), self.sent_label)
|
||||||
self.htlc_minimum_msat = SelectableLabel(str(chan.config[REMOTE].htlc_minimum_msat))
|
#self.htlc_minimum_msat = SelectableLabel(str(chan.config[REMOTE].htlc_minimum_msat))
|
||||||
form_layout.addRow(_('Minimum HTLC value accepted by peer (mSAT):'), self.htlc_minimum_msat)
|
#form_layout.addRow(_('Minimum HTLC value accepted by peer (mSAT):'), self.htlc_minimum_msat)
|
||||||
self.max_htlcs = SelectableLabel(str(chan.config[REMOTE].max_accepted_htlcs))
|
#self.max_htlcs = SelectableLabel(str(chan.config[REMOTE].max_accepted_htlcs))
|
||||||
form_layout.addRow(_('Maximum number of concurrent HTLCs accepted by peer:'), self.max_htlcs)
|
#form_layout.addRow(_('Maximum number of concurrent HTLCs accepted by peer:'), self.max_htlcs)
|
||||||
self.max_htlc_value = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].max_htlc_value_in_flight_msat / 1000))
|
#self.max_htlc_value = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].max_htlc_value_in_flight_msat / 1000))
|
||||||
form_layout.addRow(_('Maximum value of in-flight HTLCs accepted by peer:'), self.max_htlc_value)
|
#form_layout.addRow(_('Maximum value of in-flight HTLCs accepted by peer:'), self.max_htlc_value)
|
||||||
self.dust_limit = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].dust_limit_sat))
|
self.dust_limit = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].dust_limit_sat))
|
||||||
form_layout.addRow(_('Remote dust limit:'), self.dust_limit)
|
form_layout.addRow(_('Remote dust limit:'), self.dust_limit)
|
||||||
self.reserve = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].reserve_sat))
|
self.remote_reserve = self.window.format_amount_and_units(chan.config[REMOTE].reserve_sat)
|
||||||
form_layout.addRow(_('Remote channel reserve:'), self.reserve)
|
form_layout.addRow(_('Remote reserve:'), SelectableLabel(self.remote_reserve))
|
||||||
|
vbox.addLayout(form_layout)
|
||||||
|
|
||||||
# add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
|
# add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
|
||||||
form_layout.addRow(_('Payments (HTLCs):'), None)
|
vbox.addWidget(QLabel(_('Payments (HTLCs):')))
|
||||||
w = QtWidgets.QTreeView(self)
|
w = QtWidgets.QTreeView(self)
|
||||||
htlc_dict = chan.get_payments()
|
htlc_dict = chan.get_payments()
|
||||||
w.setModel(self.make_model(htlc_dict))
|
w.setModel(self.make_model(htlc_dict))
|
||||||
@@ -155,4 +188,4 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
|||||||
vbox.addWidget(w)
|
vbox.addWidget(w)
|
||||||
vbox.addLayout(Buttons(CloseButton(self)))
|
vbox.addLayout(Buttons(CloseButton(self)))
|
||||||
# initialize sent/received fields
|
# initialize sent/received fields
|
||||||
self.update_sent_received()
|
self.update()
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class ChannelsList(MyTreeView):
|
|||||||
if bal_other != bal_minus_htlcs_other:
|
if bal_other != bal_minus_htlcs_other:
|
||||||
label += ' (+' + self.parent.format_amount(bal_other - bal_minus_htlcs_other) + ')'
|
label += ' (+' + self.parent.format_amount(bal_other - bal_minus_htlcs_other) + ')'
|
||||||
labels[subject] = label
|
labels[subject] = label
|
||||||
status = self.lnworker.get_channel_status(chan)
|
status = chan.get_state_for_GUI()
|
||||||
closed = chan.is_closed()
|
closed = chan.is_closed()
|
||||||
if self.parent.network.is_lightning_running():
|
if self.parent.network.is_lightning_running():
|
||||||
node_info = self.lnworker.channel_db.get_node_info_for_node_id(chan.node_id)
|
node_info = self.lnworker.channel_db.get_node_info_for_node_id(chan.node_id)
|
||||||
|
|||||||
@@ -331,6 +331,16 @@ class Channel(Logger):
|
|||||||
def get_state(self):
|
def get_state(self):
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
def get_state_for_GUI(self):
|
||||||
|
# status displayed in the GUI
|
||||||
|
cs = self.get_state()
|
||||||
|
if self.is_closed():
|
||||||
|
return cs.name
|
||||||
|
ps = self.peer_state
|
||||||
|
if ps != peer_states.GOOD:
|
||||||
|
return ps.name
|
||||||
|
return cs.name
|
||||||
|
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
return self.get_state() == channel_states.OPEN
|
return self.get_state() == channel_states.OPEN
|
||||||
|
|
||||||
|
|||||||
@@ -510,17 +510,6 @@ class LNWallet(LNWorker):
|
|||||||
self.network.trigger_callback('channel', chan)
|
self.network.trigger_callback('channel', chan)
|
||||||
super().peer_closed(peer)
|
super().peer_closed(peer)
|
||||||
|
|
||||||
def get_channel_status(self, chan):
|
|
||||||
# status displayed in the GUI
|
|
||||||
cs = chan.get_state()
|
|
||||||
if chan.is_closed():
|
|
||||||
return cs.name
|
|
||||||
peer = self.peers.get(chan.node_id)
|
|
||||||
ps = chan.peer_state
|
|
||||||
if ps != peer_states.GOOD:
|
|
||||||
return ps.name
|
|
||||||
return cs.name
|
|
||||||
|
|
||||||
def get_settled_payments(self):
|
def get_settled_payments(self):
|
||||||
# return one item per payment_hash
|
# return one item per payment_hash
|
||||||
# note: with AMP we will have several channels per payment
|
# note: with AMP we will have several channels per payment
|
||||||
@@ -1237,6 +1226,7 @@ class LNWallet(LNWorker):
|
|||||||
chan.logger.info('received unexpected payment_failed, probably from previous session')
|
chan.logger.info('received unexpected payment_failed, probably from previous session')
|
||||||
self.network.trigger_callback('invoice_status', key)
|
self.network.trigger_callback('invoice_status', key)
|
||||||
self.network.trigger_callback('payment_failed', key, '')
|
self.network.trigger_callback('payment_failed', key, '')
|
||||||
|
self.network.trigger_callback('ln_payment_failed', payment_hash, chan.channel_id)
|
||||||
|
|
||||||
def payment_sent(self, chan, payment_hash: bytes):
|
def payment_sent(self, chan, payment_hash: bytes):
|
||||||
self.set_payment_status(payment_hash, PR_PAID)
|
self.set_payment_status(payment_hash, PR_PAID)
|
||||||
|
|||||||
Reference in New Issue
Block a user