qt,qml: review rework, refactor spinner, add tor probe active indicator
This commit is contained in:
@@ -19,6 +19,8 @@ Item {
|
|||||||
{ text: qsTr('SOCKS4'), value: 'socks4' }
|
{ text: qsTr('SOCKS4'), value: 'socks4' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
property bool _probing: false
|
||||||
|
|
||||||
function toProxyDict() {
|
function toProxyDict() {
|
||||||
var p = {}
|
var p = {}
|
||||||
p['enabled'] = pc.proxy_enabled
|
p['enabled'] = pc.proxy_enabled
|
||||||
@@ -104,19 +106,33 @@ Item {
|
|||||||
Layout.topMargin: constants.paddingLarge
|
Layout.topMargin: constants.paddingLarge
|
||||||
padding: 0
|
padding: 0
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Material.dialogColor
|
color: constants.darkerDialogBackground
|
||||||
}
|
}
|
||||||
FlatButton {
|
FlatButton {
|
||||||
enabled: proxy_enabled_cb.checked
|
enabled: proxy_enabled_cb.checked && !_probing
|
||||||
text: qsTr('Detect TOR proxy')
|
text: qsTr('Detect Tor proxy')
|
||||||
onClicked: Network.probeTor()
|
onClicked: {
|
||||||
|
_probing = true
|
||||||
|
Network.probeTor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: spinner
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: constants.paddingSmall
|
||||||
|
Layout.preferredWidth: constants.iconSizeXLarge
|
||||||
|
Layout.preferredHeight: constants.iconSizeXLarge
|
||||||
|
running: visible
|
||||||
|
visible: _probing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Network
|
target: Network
|
||||||
function onTorProbeFinished(host, port) {
|
function onTorProbeFinished(host, port) {
|
||||||
|
_probing = false
|
||||||
if (host && port) {
|
if (host && port) {
|
||||||
proxytype.currentIndex = 0
|
proxytype.currentIndex = 0
|
||||||
proxy_port = ""+port
|
proxy_port = ""+port
|
||||||
|
|||||||
@@ -25,21 +25,23 @@
|
|||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt, pyqtSignal
|
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, QLineEdit, QDialog, QVBoxLayout, QHeaderView,
|
QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, QLineEdit, QDialog, QVBoxLayout, QHeaderView,
|
||||||
QCheckBox, QTabWidget, QWidget, QLabel, QPushButton
|
QCheckBox, QTabWidget, QWidget, QLabel, QPushButton, QHBoxLayout
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QIntValidator
|
from PyQt6.QtGui import QIntValidator
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum import blockchain
|
from electrum import blockchain
|
||||||
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
|
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
|
||||||
from electrum.network import Network, ProxySettings
|
from electrum.network import Network, ProxySettings, is_valid_host, is_valid_port
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
|
|
||||||
from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit,
|
from .util import (
|
||||||
PasswordLineEdit, QtEventListener, qt_event_listener)
|
Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, PasswordLineEdit, QtEventListener,
|
||||||
|
qt_event_listener, Spinner
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
@@ -260,16 +262,18 @@ class ProxyWidget(QWidget):
|
|||||||
grid.addWidget(self.proxy_user, 2, 1, 1, 2)
|
grid.addWidget(self.proxy_user, 2, 1, 1, 2)
|
||||||
grid.addWidget(self.proxy_password, 2, 3, 1, 2)
|
grid.addWidget(self.proxy_password, 2, 3, 1, 2)
|
||||||
|
|
||||||
spacer = QVBoxLayout()
|
detect_l = QHBoxLayout()
|
||||||
spacer.addStretch(1)
|
self.detect_button = QPushButton(_('Detect Tor proxy'))
|
||||||
grid.addLayout(spacer, 3, 0, 1, 5)
|
self.spinner = Spinner()
|
||||||
|
self.spinner.setMargin(5)
|
||||||
|
detect_l.addWidget(self.detect_button)
|
||||||
|
detect_l.addWidget(self.spinner)
|
||||||
|
|
||||||
self.detect_button = QPushButton(_('Detect TOR proxy'))
|
grid.addLayout(detect_l, 3, 0, 1, 5, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||||
grid.addWidget(self.detect_button, 4, 0, 1, 5, alignment=Qt.AlignmentFlag.AlignHCenter)
|
|
||||||
|
|
||||||
spacer = QVBoxLayout()
|
spacer = QVBoxLayout()
|
||||||
spacer.addStretch(1)
|
spacer.addStretch(1)
|
||||||
grid.addLayout(spacer, 5, 0, 1, 5)
|
grid.addLayout(spacer, 4, 0, 1, 5)
|
||||||
|
|
||||||
self.update_from_config()
|
self.update_from_config()
|
||||||
self.update()
|
self.update()
|
||||||
@@ -293,7 +297,10 @@ class ProxyWidget(QWidget):
|
|||||||
]:
|
]:
|
||||||
item.setEnabled(enabled)
|
item.setEnabled(enabled)
|
||||||
|
|
||||||
if not self.proxy_port.hasAcceptableInput():
|
if not self.proxy_port.hasAcceptableInput() and not is_valid_port(self.proxy_port.text()):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not is_valid_host(self.proxy_host.text()):
|
||||||
return
|
return
|
||||||
|
|
||||||
net_params = self.network.get_parameters()
|
net_params = self.network.get_parameters()
|
||||||
@@ -337,10 +344,15 @@ class ProxyWidget(QWidget):
|
|||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
def detect_tor(self):
|
def detect_tor(self):
|
||||||
|
self.detect_button.setEnabled(False)
|
||||||
|
self.spinner.setVisible(True)
|
||||||
ProxySettings.probe_tor(self.torProbeFinished.emit) # via signal
|
ProxySettings.probe_tor(self.torProbeFinished.emit) # via signal
|
||||||
|
|
||||||
|
@pyqtSlot(str, int)
|
||||||
def on_tor_probe_finished(self, host: str, port: int):
|
def on_tor_probe_finished(self, host: str, port: int):
|
||||||
if host is not None:
|
self.detect_button.setEnabled(True)
|
||||||
|
self.spinner.setVisible(False)
|
||||||
|
if host:
|
||||||
self.proxy_mode.setCurrentIndex(1)
|
self.proxy_mode.setCurrentIndex(1)
|
||||||
self.proxy_host.setText(host)
|
self.proxy_host.setText(host)
|
||||||
self.proxy_port.setText(str(port))
|
self.proxy_port.setText(str(port))
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from electrum.submarine_swaps import SwapServerError
|
|||||||
from .amountedit import AmountEdit, BTCAmountEdit, SizedFreezableLineEdit
|
from .amountedit import AmountEdit, BTCAmountEdit, SizedFreezableLineEdit
|
||||||
from .paytoedit import InvalidPaymentIdentifier
|
from .paytoedit import InvalidPaymentIdentifier
|
||||||
from .util import (WaitingDialog, HelpLabel, MessageBoxMixin, EnterButton, char_width_in_lineedit,
|
from .util import (WaitingDialog, HelpLabel, MessageBoxMixin, EnterButton, char_width_in_lineedit,
|
||||||
get_iconname_camera, read_QIcon, ColorScheme, icon_path, IconLabel)
|
get_iconname_camera, read_QIcon, ColorScheme, icon_path, IconLabel, Spinner)
|
||||||
from .invoice_list import InvoiceList
|
from .invoice_list import InvoiceList
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -142,14 +142,8 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
self.paste_button.setToolTip(_('Paste invoice from clipboard'))
|
self.paste_button.setToolTip(_('Paste invoice from clipboard'))
|
||||||
self.paste_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
self.paste_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||||
|
|
||||||
self.spinner = QMovie(icon_path('spinner.gif'))
|
self.spinner = Spinner()
|
||||||
self.spinner.setScaledSize(QSize(24, 24))
|
grid.addWidget(self.spinner, 0, 1, 1, 4, Qt.AlignmentFlag.AlignRight)
|
||||||
self.spinner.setBackgroundColor(QColor('black'))
|
|
||||||
self.spinner_l = QLabel()
|
|
||||||
self.spinner_l.setMargin(5)
|
|
||||||
self.spinner_l.setVisible(False)
|
|
||||||
self.spinner_l.setMovie(self.spinner)
|
|
||||||
grid.addWidget(self.spinner_l, 0, 1, 1, 4, Qt.AlignmentFlag.AlignRight)
|
|
||||||
|
|
||||||
self.save_button = EnterButton(_("Save"), self.do_save_invoice)
|
self.save_button = EnterButton(_("Save"), self.do_save_invoice)
|
||||||
self.save_button.setEnabled(False)
|
self.save_button.setEnabled(False)
|
||||||
@@ -216,13 +210,6 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
|
|
||||||
self.setTabOrder(self.send_button, self.invoice_list)
|
self.setTabOrder(self.send_button, self.invoice_list)
|
||||||
|
|
||||||
def showSpinner(self, b):
|
|
||||||
self.spinner_l.setVisible(b)
|
|
||||||
if b:
|
|
||||||
self.spinner.start()
|
|
||||||
else:
|
|
||||||
self.spinner.stop()
|
|
||||||
|
|
||||||
def on_amount_changed(self, text):
|
def on_amount_changed(self, text):
|
||||||
# FIXME: implement full valid amount check to enable/disable Pay button
|
# FIXME: implement full valid amount check to enable/disable Pay button
|
||||||
pi = self.payto_e.payment_identifier
|
pi = self.payto_e.payment_identifier
|
||||||
@@ -388,7 +375,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
def prepare_for_send_tab_network_lookup(self):
|
def prepare_for_send_tab_network_lookup(self):
|
||||||
for btn in [self.save_button, self.send_button, self.clear_button]:
|
for btn in [self.save_button, self.send_button, self.clear_button]:
|
||||||
btn.setEnabled(False)
|
btn.setEnabled(False)
|
||||||
self.showSpinner(True)
|
self.spinner.setVisible(True)
|
||||||
|
|
||||||
def payment_request_error(self, error):
|
def payment_request_error(self, error):
|
||||||
self.show_message(error)
|
self.show_message(error)
|
||||||
@@ -499,7 +486,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
# TODO: resolve can happen while typing, we don't want message dialogs to pop up
|
# TODO: resolve can happen while typing, we don't want message dialogs to pop up
|
||||||
# currently we don't set error for emaillike recipients to avoid just that
|
# currently we don't set error for emaillike recipients to avoid just that
|
||||||
self.logger.debug('payment identifier resolve done')
|
self.logger.debug('payment identifier resolve done')
|
||||||
self.showSpinner(False)
|
self.spinner.setVisible(False)
|
||||||
if pi.error:
|
if pi.error:
|
||||||
self.show_error(pi.error)
|
self.show_error(pi.error)
|
||||||
self.do_clear()
|
self.do_clear()
|
||||||
@@ -555,7 +542,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
return self.amount_e.get_amount() or 0
|
return self.amount_e.get_amount() or 0
|
||||||
|
|
||||||
def on_finalize_done(self, pi: PaymentIdentifier):
|
def on_finalize_done(self, pi: PaymentIdentifier):
|
||||||
self.showSpinner(False)
|
self.spinner.setVisible(False)
|
||||||
self.update_fields()
|
self.update_fields()
|
||||||
if pi.error:
|
if pi.error:
|
||||||
self.show_error(pi.error)
|
self.show_error(pi.error)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Se
|
|||||||
|
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage,
|
from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage,
|
||||||
QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent)
|
QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent, QMovie)
|
||||||
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject)
|
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject)
|
||||||
from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit,
|
from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit,
|
||||||
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
|
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
|
||||||
@@ -117,6 +117,22 @@ class AmountLabel(QLabel):
|
|||||||
self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner(QLabel):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
QLabel.__init__(self, *args, **kwargs)
|
||||||
|
self.spinner = QMovie(icon_path('spinner.gif'))
|
||||||
|
self.spinner.setScaledSize(QSize(20, 20))
|
||||||
|
self.spinner.frameChanged.connect(lambda: self.setPixmap(self.spinner.currentPixmap()))
|
||||||
|
self.setVisible(False)
|
||||||
|
|
||||||
|
def setVisible(self, visible):
|
||||||
|
if visible:
|
||||||
|
self.spinner.start()
|
||||||
|
else:
|
||||||
|
self.spinner.stop()
|
||||||
|
super().setVisible(visible)
|
||||||
|
|
||||||
|
|
||||||
class HelpMixin:
|
class HelpMixin:
|
||||||
def __init__(self, help_text: str, *, help_title: str = None):
|
def __init__(self, help_text: str, *, help_title: str = None):
|
||||||
assert isinstance(self, QWidget), "HelpMixin must be a QWidget instance!"
|
assert isinstance(self, QWidget), "HelpMixin must be a QWidget instance!"
|
||||||
|
|||||||
@@ -159,6 +159,21 @@ def pick_random_server(hostmap=None, *, allowed_protocols: Iterable[str],
|
|||||||
return random.choice(eligible) if eligible else None
|
return random.choice(eligible) if eligible else None
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_port(ps: str):
|
||||||
|
try:
|
||||||
|
return 0 < int(ps) < 65535
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_host(ph: str):
|
||||||
|
try:
|
||||||
|
NetAddress(ph, '1')
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class ProxySettings:
|
class ProxySettings:
|
||||||
MODES = ['socks4', 'socks5']
|
MODES = ['socks4', 'socks5']
|
||||||
|
|
||||||
@@ -178,31 +193,21 @@ class ProxySettings:
|
|||||||
def serialize_proxy_cfgstr(self):
|
def serialize_proxy_cfgstr(self):
|
||||||
return ':'.join([self.mode, self.host, self.port])
|
return ':'.join([self.mode, self.host, self.port])
|
||||||
|
|
||||||
def deserialize_proxy_cfgstr(self, s: Optional[str], user: str = None, password: str = None) -> Optional[dict]:
|
def deserialize_proxy_cfgstr(self, s: Optional[str], user: str = None, password: str = None) -> None:
|
||||||
if s is None or not isinstance(s, str) or s.lower() == 'none':
|
if s is None or (isinstance(s, str) and s.lower() == 'none'):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.user = user
|
self.user = user
|
||||||
self.password = password
|
self.password = password
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not isinstance(s, str):
|
||||||
|
raise ValueError('proxy config not a string')
|
||||||
|
|
||||||
args = s.split(':')
|
args = s.split(':')
|
||||||
if args[0] in ProxySettings.MODES:
|
if args[0] in ProxySettings.MODES:
|
||||||
self.mode = args[0]
|
self.mode = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
|
|
||||||
def is_valid_port(ps: str):
|
|
||||||
try:
|
|
||||||
return 0 < int(ps) < 65535
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_valid_host(ph: str):
|
|
||||||
try:
|
|
||||||
NetAddress(ph, '1')
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# detect migrate from old settings
|
# detect migrate from old settings
|
||||||
if len(args) == 4 and is_valid_host(args[0]) and is_valid_port(args[1]): # host:port:user:pass,
|
if len(args) == 4 and is_valid_host(args[0]) and is_valid_port(args[1]): # host:port:user:pass,
|
||||||
self.host = args[0]
|
self.host = args[0]
|
||||||
@@ -234,7 +239,7 @@ class ProxySettings:
|
|||||||
proxy.deserialize_proxy_cfgstr(
|
proxy.deserialize_proxy_cfgstr(
|
||||||
config.NETWORK_PROXY, config.NETWORK_PROXY_USER, config.NETWORK_PROXY_PASSWORD
|
config.NETWORK_PROXY, config.NETWORK_PROXY_USER, config.NETWORK_PROXY_PASSWORD
|
||||||
)
|
)
|
||||||
proxy.enabled = config.NETWORK_ENABLE_PROXY
|
proxy.enabled = config.NETWORK_PROXY_ENABLED
|
||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -253,7 +258,7 @@ class ProxySettings:
|
|||||||
def detect_task(finished: Callable[[str | None, int | None], None]):
|
def detect_task(finished: Callable[[str | None, int | None], None]):
|
||||||
net_addr = detect_tor_socks_proxy()
|
net_addr = detect_tor_socks_proxy()
|
||||||
if net_addr is None:
|
if net_addr is None:
|
||||||
finished(None, None)
|
finished('', -1)
|
||||||
else:
|
else:
|
||||||
host = net_addr[0]
|
host = net_addr[0]
|
||||||
port = net_addr[1]
|
port = net_addr[1]
|
||||||
@@ -750,14 +755,14 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
|||||||
# return
|
# return
|
||||||
self.config.NETWORK_AUTO_CONNECT = net_params.auto_connect
|
self.config.NETWORK_AUTO_CONNECT = net_params.auto_connect
|
||||||
self.config.NETWORK_ONESERVER = net_params.oneserver
|
self.config.NETWORK_ONESERVER = net_params.oneserver
|
||||||
self.config.NETWORK_ENABLE_PROXY = proxy_enabled
|
self.config.NETWORK_PROXY_ENABLED = proxy_enabled
|
||||||
self.config.NETWORK_PROXY = proxy_str
|
self.config.NETWORK_PROXY = proxy_str
|
||||||
self.config.NETWORK_PROXY_USER = proxy_user
|
self.config.NETWORK_PROXY_USER = proxy_user
|
||||||
self.config.NETWORK_PROXY_PASSWORD = proxy_pass
|
self.config.NETWORK_PROXY_PASSWORD = proxy_pass
|
||||||
self.config.NETWORK_SERVER = str(server)
|
self.config.NETWORK_SERVER = str(server)
|
||||||
# abort if changes were not allowed by config
|
# abort if changes were not allowed by config
|
||||||
if self.config.NETWORK_SERVER != str(server) \
|
if self.config.NETWORK_SERVER != str(server) \
|
||||||
or self.config.NETWORK_ENABLE_PROXY != proxy_enabled \
|
or self.config.NETWORK_PROXY_ENABLED != proxy_enabled \
|
||||||
or self.config.NETWORK_PROXY != proxy_str \
|
or self.config.NETWORK_PROXY != proxy_str \
|
||||||
or self.config.NETWORK_PROXY_USER != proxy_user \
|
or self.config.NETWORK_PROXY_USER != proxy_user \
|
||||||
or self.config.NETWORK_PROXY_PASSWORD != proxy_pass \
|
or self.config.NETWORK_PROXY_PASSWORD != proxy_pass \
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ class SimpleConfig(Logger):
|
|||||||
NETWORK_PROXY = ConfigVar('proxy', default=None, type_=str, convert_getter=lambda v: "none" if v is None else v)
|
NETWORK_PROXY = ConfigVar('proxy', default=None, type_=str, convert_getter=lambda v: "none" if v is None else v)
|
||||||
NETWORK_PROXY_USER = ConfigVar('proxy_user', default=None, type_=str)
|
NETWORK_PROXY_USER = ConfigVar('proxy_user', default=None, type_=str)
|
||||||
NETWORK_PROXY_PASSWORD = ConfigVar('proxy_password', default=None, type_=str)
|
NETWORK_PROXY_PASSWORD = ConfigVar('proxy_password', default=None, type_=str)
|
||||||
NETWORK_ENABLE_PROXY = ConfigVar('enable_proxy', default=lambda config: config.NETWORK_PROXY not in [None, "none"], type_=bool)
|
NETWORK_PROXY_ENABLED = ConfigVar('enable_proxy', default=lambda config: config.NETWORK_PROXY not in [None, "none"], type_=bool)
|
||||||
NETWORK_SERVER = ConfigVar('server', default=None, type_=str)
|
NETWORK_SERVER = ConfigVar('server', default=None, type_=str)
|
||||||
NETWORK_NOONION = ConfigVar('noonion', default=False, type_=bool)
|
NETWORK_NOONION = ConfigVar('noonion', default=False, type_=bool)
|
||||||
NETWORK_OFFLINE = ConfigVar('offline', default=False, type_=bool)
|
NETWORK_OFFLINE = ConfigVar('offline', default=False, type_=bool)
|
||||||
|
|||||||
Reference in New Issue
Block a user