initial lightning channel details, action menu
This commit is contained in:
248
electrum/gui/qml/components/ChannelDetails.qml
Normal file
248
electrum/gui/qml/components/ChannelDetails.qml
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import QtQuick 2.6
|
||||||
|
import QtQuick.Layouts 1.0
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Controls.Material 2.0
|
||||||
|
|
||||||
|
import org.electrum 1.0
|
||||||
|
|
||||||
|
import "controls"
|
||||||
|
|
||||||
|
Pane {
|
||||||
|
id: root
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
property string channelid
|
||||||
|
|
||||||
|
property string title: qsTr("Channel details")
|
||||||
|
|
||||||
|
property QtObject menu: Menu {
|
||||||
|
id: menu
|
||||||
|
MenuItem {
|
||||||
|
icon.color: 'transparent'
|
||||||
|
action: Action {
|
||||||
|
text: qsTr('Backup');
|
||||||
|
enabled: false
|
||||||
|
onTriggered: {}
|
||||||
|
//icon.source: '../../icons/wallet.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
icon.color: 'transparent'
|
||||||
|
action: Action {
|
||||||
|
text: qsTr('Close channel');
|
||||||
|
enabled: false
|
||||||
|
onTriggered: {}
|
||||||
|
//icon.source: '../../icons/wallet.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
icon.color: 'transparent'
|
||||||
|
action: Action {
|
||||||
|
text: qsTr('Force-close');
|
||||||
|
enabled: false
|
||||||
|
onTriggered: {}
|
||||||
|
//icon.source: '../../icons/wallet.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
icon.color: 'transparent'
|
||||||
|
action: Action {
|
||||||
|
text: channeldetails.frozenForSending ? qsTr('Unfreeze (for sending)') : qsTr('Freeze (for sending)')
|
||||||
|
onTriggered: channeldetails.freezeForSending()
|
||||||
|
//icon.source: '../../icons/wallet.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
icon.color: 'transparent'
|
||||||
|
action: Action {
|
||||||
|
text: channeldetails.frozenForReceiving ? qsTr('Unfreeze (for receiving)') : qsTr('Freeze (for receiving)')
|
||||||
|
onTriggered: channeldetails.freezeForReceiving()
|
||||||
|
//icon.source: '../../icons/wallet.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
contentHeight: rootLayout.height
|
||||||
|
clip:true
|
||||||
|
interactive: height < contentHeight
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: rootLayout
|
||||||
|
width: parent.width
|
||||||
|
columns: 2
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Channel name')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: channeldetails.name
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Short channel ID')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: channeldetails.short_cid
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('State')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: channeldetails.state
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Initiator')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: channeldetails.initiator
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Capacity')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Label {
|
||||||
|
font.family: FixedFont
|
||||||
|
text: Config.formatSats(channeldetails.capacity)
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
color: Material.accentColor
|
||||||
|
text: Config.baseUnit
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: Daemon.fx.enabled
|
||||||
|
? '(' + Daemon.fx.fiatValue(channeldetails.capacity) + ' ' + Daemon.fx.fiatCurrency + ')'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Can send')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: !channeldetails.frozenForSending && channeldetails.isOpen
|
||||||
|
Label {
|
||||||
|
font.family: FixedFont
|
||||||
|
text: Config.formatSats(channeldetails.canSend)
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
color: Material.accentColor
|
||||||
|
text: Config.baseUnit
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: Daemon.fx.enabled
|
||||||
|
? '(' + Daemon.fx.fiatValue(channeldetails.canSend) + ' ' + Daemon.fx.fiatCurrency + ')'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: channeldetails.frozenForSending && channeldetails.isOpen
|
||||||
|
text: qsTr('n/a (frozen)')
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: !channeldetails.isOpen
|
||||||
|
text: qsTr('n/a (channel not open)')
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Can Receive')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
visible: !channeldetails.frozenForReceiving && channeldetails.isOpen
|
||||||
|
Label {
|
||||||
|
font.family: FixedFont
|
||||||
|
text: Config.formatSats(channeldetails.canReceive)
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
color: Material.accentColor
|
||||||
|
text: Config.baseUnit
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: Daemon.fx.enabled
|
||||||
|
? '(' + Daemon.fx.fiatValue(channeldetails.canReceive) + ' ' + Daemon.fx.fiatCurrency + ')'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: channeldetails.frozenForReceiving && channeldetails.isOpen
|
||||||
|
text: qsTr('n/a (frozen)')
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
visible: !channeldetails.isOpen
|
||||||
|
text: qsTr('n/a (channel not open)')
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Channel type')
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: channeldetails.channelType
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr('Remote node ID')
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
color: Material.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
TextHighlightPane {
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
padding: 0
|
||||||
|
leftPadding: constants.paddingSmall
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
Label {
|
||||||
|
text: channeldetails.pubkey
|
||||||
|
font.pixelSize: constants.fontSizeLarge
|
||||||
|
font.family: FixedFont
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
icon.source: '../../icons/share.png'
|
||||||
|
icon.color: 'transparent'
|
||||||
|
onClicked: {
|
||||||
|
var dialog = share.createObject(root, { 'title': qsTr('Channel node ID'), 'text': channeldetails.pubkey })
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelDetails {
|
||||||
|
id: channeldetails
|
||||||
|
wallet: Daemon.currentWallet
|
||||||
|
channelid: root.channelid
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: share
|
||||||
|
GenericShareDialog {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,7 +108,9 @@ Pane {
|
|||||||
model: Daemon.currentWallet.channelModel
|
model: Daemon.currentWallet.channelModel
|
||||||
|
|
||||||
delegate: ChannelDelegate {
|
delegate: ChannelDelegate {
|
||||||
//highlighted: ListView.isCurrentItem
|
onClicked: {
|
||||||
|
app.stack.push(Qt.resolvedUrl('ChannelDetails.qml'), { 'channelid': model.cid })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollIndicator.vertical: ScrollIndicator { }
|
ScrollIndicator.vertical: ScrollIndicator { }
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from .qeaddressdetails import QEAddressDetails
|
|||||||
from .qetxdetails import QETxDetails
|
from .qetxdetails import QETxDetails
|
||||||
from .qechannelopener import QEChannelOpener
|
from .qechannelopener import QEChannelOpener
|
||||||
from .qelnpaymentdetails import QELnPaymentDetails
|
from .qelnpaymentdetails import QELnPaymentDetails
|
||||||
|
from .qechanneldetails import QEChannelDetails
|
||||||
|
|
||||||
notification = None
|
notification = None
|
||||||
|
|
||||||
@@ -149,6 +150,7 @@ class ElectrumQmlApplication(QGuiApplication):
|
|||||||
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
|
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
|
||||||
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
|
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
|
||||||
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
|
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
|
||||||
|
qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails')
|
||||||
|
|
||||||
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
||||||
|
|
||||||
|
|||||||
132
electrum/gui/qml/qechanneldetails.py
Normal file
132
electrum/gui/qml/qechanneldetails.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Q_ENUMS
|
||||||
|
|
||||||
|
from electrum.logging import get_logger
|
||||||
|
from electrum.util import register_callback, unregister_callback
|
||||||
|
from electrum.lnutil import LOCAL, REMOTE
|
||||||
|
|
||||||
|
from .qewallet import QEWallet
|
||||||
|
from .qetypes import QEAmount
|
||||||
|
|
||||||
|
class QEChannelDetails(QObject):
|
||||||
|
|
||||||
|
_logger = get_logger(__name__)
|
||||||
|
_wallet = None
|
||||||
|
_channelid = None
|
||||||
|
_channel = None
|
||||||
|
|
||||||
|
channelChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
register_callback(self.on_network, ['channel']) # TODO unregister too
|
||||||
|
|
||||||
|
def on_network(self, event, *args):
|
||||||
|
if event == 'channel':
|
||||||
|
wallet, channel = args
|
||||||
|
if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex():
|
||||||
|
self.channelChanged.emit()
|
||||||
|
|
||||||
|
walletChanged = pyqtSignal()
|
||||||
|
@pyqtProperty(QEWallet, notify=walletChanged)
|
||||||
|
def wallet(self):
|
||||||
|
return self._wallet
|
||||||
|
|
||||||
|
@wallet.setter
|
||||||
|
def wallet(self, wallet: QEWallet):
|
||||||
|
if self._wallet != wallet:
|
||||||
|
self._wallet = wallet
|
||||||
|
self.walletChanged.emit()
|
||||||
|
|
||||||
|
channelidChanged = pyqtSignal()
|
||||||
|
@pyqtProperty(str, notify=channelidChanged)
|
||||||
|
def channelid(self):
|
||||||
|
return self._channelid
|
||||||
|
|
||||||
|
@channelid.setter
|
||||||
|
def channelid(self, channelid: str):
|
||||||
|
if self._channelid != channelid:
|
||||||
|
self._channelid = channelid
|
||||||
|
if channelid:
|
||||||
|
self.load()
|
||||||
|
self.channelidChanged.emit()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
lnchannels = self._wallet.wallet.lnworker.channels
|
||||||
|
for channel in lnchannels.values():
|
||||||
|
self._logger.debug('%s == %s ?' % (self._channelid, channel.channel_id))
|
||||||
|
if self._channelid == channel.channel_id.hex():
|
||||||
|
self._channel = channel
|
||||||
|
self.channelChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def name(self):
|
||||||
|
if not self._channel:
|
||||||
|
return
|
||||||
|
return self._wallet.wallet.lnworker.get_node_alias(self._channel.node_id) or self._channel.node_id.hex()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def pubkey(self):
|
||||||
|
return self._channel.node_id.hex() #if self._channel else ''
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def short_cid(self):
|
||||||
|
return self._channel.short_id_for_GUI()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def state(self):
|
||||||
|
return self._channel.get_state_for_GUI()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def initiator(self):
|
||||||
|
return 'Local' if self._channel.constraints.is_initiator else 'Remote'
|
||||||
|
|
||||||
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
||||||
|
def capacity(self):
|
||||||
|
self._capacity = QEAmount(amount_sat=self._channel.get_capacity())
|
||||||
|
return self._capacity
|
||||||
|
|
||||||
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
||||||
|
def canSend(self):
|
||||||
|
self._can_send = QEAmount(amount_sat=self._channel.available_to_spend(LOCAL)/1000)
|
||||||
|
return self._can_send
|
||||||
|
|
||||||
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
||||||
|
def canReceive(self):
|
||||||
|
self._can_receive = QEAmount(amount_sat=self._channel.available_to_spend(REMOTE)/1000)
|
||||||
|
return self._can_receive
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=channelChanged)
|
||||||
|
def frozenForSending(self):
|
||||||
|
return self._channel.is_frozen_for_sending()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=channelChanged)
|
||||||
|
def frozenForReceiving(self):
|
||||||
|
return self._channel.is_frozen_for_receiving()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify=channelChanged)
|
||||||
|
def channelType(self):
|
||||||
|
return self._channel.storage['channel_type'].name_minimal
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=channelChanged)
|
||||||
|
def isOpen(self):
|
||||||
|
return self._channel.is_open()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def freezeForSending(self):
|
||||||
|
lnworker = self._channel.lnworker
|
||||||
|
if lnworker.channel_db or lnworker.is_trampoline_peer(self._channel.node_id):
|
||||||
|
#self.is_frozen_for_sending = not self.is_frozen_for_sending
|
||||||
|
self._channel.set_frozen_for_sending(not self.frozenForSending)
|
||||||
|
self.channelChanged.emit()
|
||||||
|
else:
|
||||||
|
self._logger.debug('TODO: messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP')
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def freezeForReceiving(self):
|
||||||
|
lnworker = self._channel.lnworker
|
||||||
|
if lnworker.channel_db or lnworker.is_trampoline_peer(self._channel.node_id):
|
||||||
|
#self.is_frozen_for_sending = not self.is_frozen_for_sending
|
||||||
|
self._channel.set_frozen_for_receiving(not self.frozenForReceiving)
|
||||||
|
self.channelChanged.emit()
|
||||||
|
else:
|
||||||
|
self._logger.debug('TODO: messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP')
|
||||||
@@ -14,7 +14,7 @@ class QEChannelListModel(QAbstractListModel):
|
|||||||
|
|
||||||
# define listmodel rolemap
|
# define listmodel rolemap
|
||||||
_ROLE_NAMES=('cid','state','initiator','capacity','can_send','can_receive',
|
_ROLE_NAMES=('cid','state','initiator','capacity','can_send','can_receive',
|
||||||
'l_csv_delat','r_csv_delay','send_frozen','receive_frozen',
|
'l_csv_delay','r_csv_delay','send_frozen','receive_frozen',
|
||||||
'type','node_id','node_alias','short_cid','funding_tx')
|
'type','node_id','node_alias','short_cid','funding_tx')
|
||||||
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
|
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
|
||||||
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
|
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
|
||||||
@@ -81,7 +81,7 @@ class QEChannelListModel(QAbstractListModel):
|
|||||||
def channel_to_model(self, lnc):
|
def channel_to_model(self, lnc):
|
||||||
lnworker = self.wallet.lnworker
|
lnworker = self.wallet.lnworker
|
||||||
item = {}
|
item = {}
|
||||||
item['channel_id'] = lnc.channel_id
|
item['cid'] = lnc.channel_id.hex()
|
||||||
item['node_alias'] = lnworker.get_node_alias(lnc.node_id) or lnc.node_id.hex()
|
item['node_alias'] = lnworker.get_node_alias(lnc.node_id) or lnc.node_id.hex()
|
||||||
item['short_cid'] = lnc.short_id_for_GUI()
|
item['short_cid'] = lnc.short_id_for_GUI()
|
||||||
item['state'] = lnc.get_state_for_GUI()
|
item['state'] = lnc.get_state_for_GUI()
|
||||||
@@ -114,7 +114,7 @@ class QEChannelListModel(QAbstractListModel):
|
|||||||
def on_channel_updated(self, channel):
|
def on_channel_updated(self, channel):
|
||||||
i = 0
|
i = 0
|
||||||
for c in self.channels:
|
for c in self.channels:
|
||||||
if c['channel_id'] == channel.channel_id:
|
if c['cid'] == channel.channel_id:
|
||||||
self.do_update(i,channel)
|
self.do_update(i,channel)
|
||||||
break
|
break
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
|||||||
@@ -57,9 +57,8 @@ class QEInvoice(QObject):
|
|||||||
|
|
||||||
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
|
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
|
||||||
|
|
||||||
def __init__(self, config, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.config = config
|
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
@pyqtProperty(int, notify=invoiceChanged)
|
@pyqtProperty(int, notify=invoiceChanged)
|
||||||
|
|||||||
Reference in New Issue
Block a user