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
|
||||
|
||||
delegate: ChannelDelegate {
|
||||
//highlighted: ListView.isCurrentItem
|
||||
onClicked: {
|
||||
app.stack.push(Qt.resolvedUrl('ChannelDetails.qml'), { 'channelid': model.cid })
|
||||
}
|
||||
}
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator { }
|
||||
|
||||
@@ -25,6 +25,7 @@ from .qeaddressdetails import QEAddressDetails
|
||||
from .qetxdetails import QETxDetails
|
||||
from .qechannelopener import QEChannelOpener
|
||||
from .qelnpaymentdetails import QELnPaymentDetails
|
||||
from .qechanneldetails import QEChannelDetails
|
||||
|
||||
notification = None
|
||||
|
||||
@@ -149,6 +150,7 @@ class ElectrumQmlApplication(QGuiApplication):
|
||||
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
|
||||
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
|
||||
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')
|
||||
|
||||
|
||||
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
|
||||
_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')
|
||||
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_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):
|
||||
lnworker = self.wallet.lnworker
|
||||
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['short_cid'] = lnc.short_id_for_GUI()
|
||||
item['state'] = lnc.get_state_for_GUI()
|
||||
@@ -114,7 +114,7 @@ class QEChannelListModel(QAbstractListModel):
|
||||
def on_channel_updated(self, channel):
|
||||
i = 0
|
||||
for c in self.channels:
|
||||
if c['channel_id'] == channel.channel_id:
|
||||
if c['cid'] == channel.channel_id:
|
||||
self.do_update(i,channel)
|
||||
break
|
||||
i = i + 1
|
||||
|
||||
@@ -57,9 +57,8 @@ class QEInvoice(QObject):
|
||||
|
||||
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
|
||||
|
||||
def __init__(self, config, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.config = config
|
||||
self.clear()
|
||||
|
||||
@pyqtProperty(int, notify=invoiceChanged)
|
||||
|
||||
Reference in New Issue
Block a user