diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index aa63738..d2f83c4 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -14,9 +14,12 @@ from queue import Queue from threading import Lock from xmlrpc.client import Binary, ServerProxy -from PyQt5 import QtCore +from PyQt5 import QtCore, QtWidgets +from paramiko.ssh_exception import AuthenticationException from . import proginit as pi +from .ssh_tunneling.server import SSHLocalTunnel +from .sshauth import SSHAuth, SSHAuthType class WidgetData(IntEnum): @@ -40,6 +43,9 @@ class WidgetData(IntEnum): watch_files = 310 watch_path = 311 debug_geos = 312 + ssh_use_tunnel = 313 + ssh_port = 315 + ssh_user = 316 class ConnectionManager(QtCore.QThread): @@ -55,6 +61,8 @@ class ConnectionManager(QtCore.QThread): """This will be triggered, if a connection error was detected.""" status_changed = QtCore.pyqtSignal(str, str) """Status message and color suggestion.""" + connection_recovered = QtCore.pyqtSignal() + """After errors the connection is established again, could have other port information (SSH).""" def __init__(self, parent=None, cycle_time_ms=1000): super(ConnectionManager, self).__init__(parent) @@ -71,6 +79,12 @@ class ConnectionManager(QtCore.QThread): self.name = "" self.port = 55123 + self.ssh_tunnel_server = None # type: SSHLocalTunnel + self.ssh_use_tunnel = False + self.ssh_port = 22 + self.ssh_user = "pi" + self.ssh_pass = "" + # Sync this with revpiplclist to preserve settings self.program_last_dir_upload = "" self.program_last_file_upload = "" @@ -167,6 +181,12 @@ class ConnectionManager(QtCore.QThread): self.address = "" self.name = "" self.port = 55123 + + self.ssh_use_tunnel = False + self.ssh_port = 22 + self.ssh_user = "pi" + self.ssh_pass = "" + self.pyload_version = (0, 0, 0) self.xml_funcs.clear() self.xml_mode = -1 @@ -207,11 +227,12 @@ class ConnectionManager(QtCore.QThread): settings.endArray() - def pyload_connect(self, settings_index: int): + def pyload_connect(self, settings_index: int, parent=None) -> bool: """ Create a new connection from settings object. :param settings_index: Index of settings array 'connections' + :param parent: Qt parent window for dialog positioning :return: True, if the connection was successfully established """ @@ -226,6 +247,13 @@ class ConnectionManager(QtCore.QThread): port = settings.value("port", 55123, int) timeout = settings.value("timeout", 5, int) + ssh_tunnel_server = None + ssh_use_tunnel = settings.value("ssh_use_tunnel", False, bool) + ssh_port = settings.value("ssh_port", 22, int) + ssh_user = settings.value("ssh_user", "pi", str) + ssh_tunnel_port = 0 + ssh_pass = "" + self.program_last_dir_upload = settings.value("last_dir_upload", ".", str) self.program_last_file_upload = settings.value("last_file_upload", ".", str) self.program_last_dir_pictory = settings.value("last_dir_pictory", ".", str) @@ -241,7 +269,42 @@ class ConnectionManager(QtCore.QThread): settings.endArray() socket.setdefaulttimeout(2) - sp = ServerProxy("http://{0}:{1}".format(address, port)) + + if ssh_use_tunnel: + while True: + diag_ssh_auth = SSHAuth(SSHAuthType.PASS, parent) + diag_ssh_auth.username = ssh_user + if not diag_ssh_auth.exec() == QtWidgets.QDialog.Accepted: + self._clear_settings() + return False + + ssh_user = diag_ssh_auth.username + ssh_pass = diag_ssh_auth.password + ssh_tunnel_server = SSHLocalTunnel(port, address, ssh_port) + try: + ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(ssh_user, ssh_pass) + break + except AuthenticationException: + QtWidgets.QMessageBox.critical( + parent, self.tr("Error"), self.tr( + "The combination of username and password was rejected from the SSH server.\n\n" + "Try again." + ) + ) + except Exception as e: + # todo: Check some more kinds of exceptions and nice user info + self._clear_settings() + QtWidgets.QMessageBox.critical( + parent, self.tr("Error"), self.tr( + "Could not establish a SSH connection to server:\n\n{0}" + ).format(str(e)) + ) + return False + + sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) + + else: + sp = ServerProxy("http://{0}:{1}".format(address, port)) # Load values and test connection to Revolution Pi try: @@ -251,19 +314,41 @@ class ConnectionManager(QtCore.QThread): except Exception as e: pi.logger.exception(e) self.connection_error_observed.emit(str(e)) + + if not self.ssh_use_tunnel: + # todo: Change message, that user can use ssh + QtWidgets.QMessageBox.critical( + parent, self.tr("Error"), self.tr( + "Can not connect to RevPi XML-RPC Service! \n\n" + "This could have the following reasons: The RevPi is not " + "online, the XML-RPC service is not running / bind to " + "localhost or the ACL permission is not set for your " + "IP!!!\n\nRun 'sudo revpipyload_secure_installation' on " + "Revolution Pi to setup this function!" + ) + ) + return False self.address = address self.name = name self.port = port + self.ssh_use_tunnel = ssh_use_tunnel + self.ssh_port = ssh_port + self.ssh_user = ssh_user + self.ssh_pass = ssh_pass self.pyload_version = pyload_version self.xml_funcs = xml_funcs self.xml_mode = xml_mode with self._lck_cli: socket.setdefaulttimeout(timeout) + self.ssh_tunnel_server = ssh_tunnel_server self._cli = sp - self._cli_connect.put_nowait((address, port)) + self._cli_connect.put_nowait(( + "127.0.0.1" if ssh_use_tunnel else address, + ssh_tunnel_port if ssh_use_tunnel else port + )) self.connection_established.emit() @@ -286,7 +371,7 @@ class ConnectionManager(QtCore.QThread): elif self._cli is not None: - # Tell all widget, that we want do disconnect, to save the settings + # Tell all widget, that we want to disconnect, to save the settings self.connection_disconnecting.emit() self._save_settings() @@ -299,6 +384,10 @@ class ConnectionManager(QtCore.QThread): self._clear_settings() self._cli = None + if self.ssh_tunnel_server: + self.ssh_tunnel_server.disconnect() + self.ssh_tunnel_server = None + self.connection_disconnected.emit() def pyload_simulate(self, configrsc: str, procimg: str, clean_existing: bool): @@ -383,6 +472,23 @@ class ConnectionManager(QtCore.QThread): pi.logger.warning(e) self.status_changed.emit(self.tr("SERVER ERROR"), "red") self.connection_error_observed.emit("{0} | {1}".format(e, type(e))) + + if self.ssh_tunnel_server and not self.ssh_tunnel_server.connected: + self.ssh_tunnel_server.disconnect() + ssh_tunnel_server = SSHLocalTunnel(self.port, self.address, self.ssh_port) + try: + ssh_tunnel_port = self.ssh_tunnel_server.connect_by_credentials( + self.ssh_user, + self.ssh_pass + ) + sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) + with self._lck_cli: + self.ssh_tunnel_server = ssh_tunnel_server + self._cli = sp + self.connection_recovered.emit() + except Exception: + pass + else: if plc_exit_code == -1: self.status_changed.emit(self.tr("RUNNING"), "green") @@ -452,11 +558,17 @@ class ConnectionManager(QtCore.QThread): return default_value def get_cli(self): - """Connection proxy of actual connection.""" - if self.address and self.port: + """ + Connection proxy of actual connection. + + Use connection_recovered signal to figure out new parameters. + """ + if not self.ssh_use_tunnel and self.address and self.port: return ServerProxy("http://{0}:{1}".format(self.address, self.port)) - else: - return None + if self.ssh_use_tunnel and self.ssh_tunnel_server and self.ssh_tunnel_server.connected: + return ServerProxy("http://127.0.0.1:{0}".format(self.ssh_tunnel_server.local_tunnel_port)) + + return None @property def connected(self) -> bool: diff --git a/src/revpicommander/revpicommander.py b/src/revpicommander/revpicommander.py index c6496d9..77a28b5 100644 --- a/src/revpicommander/revpicommander.py +++ b/src/revpicommander/revpicommander.py @@ -5,7 +5,7 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2018 Sven Sager" __license__ = "GPLv3" -__version__ = "0.9.3" +__version__ = "0.9.10rc1" import webbrowser from os.path import basename, dirname, join @@ -97,17 +97,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): # region # REGION: Connection management def _pyload_connect(self, settings_index: int) -> None: - if not helper.cm.pyload_connect(settings_index): - QtWidgets.QMessageBox.critical( - self, self.tr("Error"), self.tr( - "Can not connect to RevPi XML-RPC Service! \n\n" - "This could have the following reasons: The RevPi is not " - "online, the XML-RPC service is not running / bind to " - "localhost or the ACL permission is not set for your " - "IP!!!\n\nRun 'sudo revpipyload_secure_installation' on " - "Revolution Pi to setup this function!" - ) - ) + helper.cm.pyload_connect(settings_index, self) @QtCore.pyqtSlot(str) def on_cm_connection_error_observed(self, message: str): @@ -192,8 +182,12 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): else: parent_menu = self.men_connections + display_name = helper.settings.value("name") + if helper.settings.value("ssh_use_tunnel", False, bool): + display_name += " (SSH)" + act = QtWidgets.QAction(parent_menu) - act.setText(helper.settings.value("name")) + act.setText(display_name) act.setData(i) act.setToolTip("{0}:{1}".format( helper.settings.value("address"), diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 029eeb1..2df72bc 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -51,6 +51,10 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): con_item.setData(0, WidgetData.port, settings.value("port", self.__default_port, int)) con_item.setData(0, WidgetData.timeout, settings.value("timeout", 5, int)) + con_item.setData(0, WidgetData.ssh_use_tunnel, settings.value("ssh_use_tunnel", False, bool)) + con_item.setData(0, WidgetData.ssh_port, settings.value("ssh_port", 22, int)) + con_item.setData(0, WidgetData.ssh_user, settings.value("ssh_user", "pi", str)) + con_item.setData(0, WidgetData.last_dir_upload, settings.value("last_dir_upload")) con_item.setData(0, WidgetData.last_file_upload, settings.value("last_file_upload")) con_item.setData(0, WidgetData.last_dir_pictory, settings.value("last_dir_pictory")) @@ -61,7 +65,12 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): con_item.setData(0, WidgetData.last_zip_file, settings.value("last_zip_file")) con_item.setData(0, WidgetData.watch_files, settings.value("watch_files")) con_item.setData(0, WidgetData.watch_path, settings.value("watch_path")) - con_item.setData(0, WidgetData.debug_geos, settings.value("debug_geos")) + try: + # Bytes with QSettings are a little difficult sometimes + con_item.setData(0, WidgetData.debug_geos, settings.value("debug_geos")) + except Exception: + # Just drop the geos of IO windows + pass folder = settings.value("folder", "", str) if folder: @@ -95,6 +104,9 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): settings.setValue("name", node.text(0)) settings.setValue("port", node.data(0, WidgetData.port)) settings.setValue("timeout", node.data(0, WidgetData.timeout)) + settings.setValue("ssh_use_tunnel", node.data(0, WidgetData.ssh_use_tunnel)) + settings.setValue("ssh_port", node.data(0, WidgetData.ssh_port)) + settings.setValue("ssh_user", node.data(0, WidgetData.ssh_user)) if node.data(0, WidgetData.last_dir_upload): settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload)) @@ -195,6 +207,10 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.sbx_timeout.setEnabled(con_item) self.cbb_folder.setEnabled(con_item or dir_item) + self.cbx_ssh_use_tunnel.setEnabled(con_item) + self.sbx_ssh_port.setEnabled(con_item) + self.txt_ssh_user.setEnabled(con_item) + def _get_folder_item(self, name: str): """Find the folder entry by name.""" for i in range(self.tre_connections.topLevelItemCount()): @@ -241,6 +257,11 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.cbb_folder.setCurrentIndex(0) else: self.cbb_folder.setCurrentText(current.parent().text(0)) + + self.cbx_ssh_use_tunnel.setChecked(current.data(0, WidgetData.ssh_use_tunnel)) + self.sbx_ssh_port.setValue(current.data(0, WidgetData.ssh_port)) + self.txt_ssh_user.setText(current.data(0, WidgetData.ssh_user)) + elif current and current.type() == NodeType.DIR: self.__current_item = current self.cbb_folder.setCurrentText(current.text(0)) @@ -278,6 +299,9 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.__current_item.setText(0, self.__default_name) self.__current_item.setData(0, WidgetData.port, self.__default_port) self.__current_item.setData(0, WidgetData.timeout, 5) + self.__current_item.setData(0, WidgetData.ssh_use_tunnel, False) + self.__current_item.setData(0, WidgetData.ssh_port, 22) + self.__current_item.setData(0, WidgetData.ssh_user, "pi") sub_folder = self._get_folder_item(self.cbb_folder.currentText()) if sub_folder: sub_folder.addChild(self.__current_item) @@ -312,6 +336,24 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): return self.__current_item.setData(0, WidgetData.timeout, value) + @QtCore.pyqtSlot(int) + def on_cbx_ssh_use_tunnel_stateChanged(self, check_state: int): + if self.__current_item.type() != NodeType.CON: + return + self.__current_item.setData(0, WidgetData.ssh_use_tunnel, check_state == QtCore.Qt.CheckState.Checked) + + @QtCore.pyqtSlot(int) + def on_sbx_ssh_port_valueChanged(self, value: int): + if self.__current_item.type() != NodeType.CON: + return + self.__current_item.setData(0, WidgetData.ssh_port, value) + + @QtCore.pyqtSlot(str) + def on_txt_ssh_user_textEdited(self, text): + if self.__current_item.type() != NodeType.CON: + return + self.__current_item.setData(0, WidgetData.ssh_user, text) + @QtCore.pyqtSlot(str) def on_cbb_folder_editTextChanged(self, text: str): pi.logger.debug("RevPiPlcList.on_cbb_folder_editTextChanged({0})".format(text)) diff --git a/src/revpicommander/ui/revpiplclist_ui.py b/src/revpicommander/ui/revpiplclist_ui.py index b3ead82..dfc30cd 100644 --- a/src/revpicommander/ui/revpiplclist_ui.py +++ b/src/revpicommander/ui/revpiplclist_ui.py @@ -14,9 +14,98 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_diag_connections(object): def setupUi(self, diag_connections): diag_connections.setObjectName("diag_connections") - diag_connections.resize(520, 508) + diag_connections.resize(496, 569) self.gridLayout = QtWidgets.QGridLayout(diag_connections) self.gridLayout.setObjectName("gridLayout") + self.tab_properties = QtWidgets.QTabWidget(diag_connections) + self.tab_properties.setObjectName("tab_properties") + self.tab_connection = QtWidgets.QWidget() + self.tab_connection.setObjectName("tab_connection") + self.formLayout_2 = QtWidgets.QFormLayout(self.tab_connection) + self.formLayout_2.setObjectName("formLayout_2") + self.lbl_name = QtWidgets.QLabel(self.tab_connection) + self.lbl_name.setObjectName("lbl_name") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_name) + self.txt_name = QtWidgets.QLineEdit(self.tab_connection) + self.txt_name.setObjectName("txt_name") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name) + self.lbl_address = QtWidgets.QLabel(self.tab_connection) + self.lbl_address.setObjectName("lbl_address") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address) + self.txt_address = QtWidgets.QLineEdit(self.tab_connection) + self.txt_address.setObjectName("txt_address") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address) + self.lbl_port = QtWidgets.QLabel(self.tab_connection) + self.lbl_port.setObjectName("lbl_port") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port) + self.sbx_port = QtWidgets.QSpinBox(self.tab_connection) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth()) + self.sbx_port.setSizePolicy(sizePolicy) + self.sbx_port.setMinimum(1) + self.sbx_port.setMaximum(65535) + self.sbx_port.setProperty("value", 55123) + self.sbx_port.setObjectName("sbx_port") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port) + self.lbl_timeout = QtWidgets.QLabel(self.tab_connection) + self.lbl_timeout.setObjectName("lbl_timeout") + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout) + self.sbx_timeout = QtWidgets.QSpinBox(self.tab_connection) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth()) + self.sbx_timeout.setSizePolicy(sizePolicy) + self.sbx_timeout.setMinimum(5) + self.sbx_timeout.setMaximum(30) + self.sbx_timeout.setObjectName("sbx_timeout") + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout) + self.lbl_folder = QtWidgets.QLabel(self.tab_connection) + self.lbl_folder.setObjectName("lbl_folder") + self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder) + self.cbb_folder = QtWidgets.QComboBox(self.tab_connection) + self.cbb_folder.setEditable(True) + self.cbb_folder.setObjectName("cbb_folder") + self.cbb_folder.addItem("") + self.cbb_folder.setItemText(0, "") + self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder) + self.tab_properties.addTab(self.tab_connection, "") + self.tab_ssh = QtWidgets.QWidget() + self.tab_ssh.setObjectName("tab_ssh") + self.formLayout = QtWidgets.QFormLayout(self.tab_ssh) + self.formLayout.setObjectName("formLayout") + self.lbl_ssh_use_tunnel = QtWidgets.QLabel(self.tab_ssh) + self.lbl_ssh_use_tunnel.setObjectName("lbl_ssh_use_tunnel") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_use_tunnel) + self.cbx_ssh_use_tunnel = QtWidgets.QCheckBox(self.tab_ssh) + self.cbx_ssh_use_tunnel.setText("") + self.cbx_ssh_use_tunnel.setChecked(True) + self.cbx_ssh_use_tunnel.setObjectName("cbx_ssh_use_tunnel") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cbx_ssh_use_tunnel) + self.lbl_ssh_port = QtWidgets.QLabel(self.tab_ssh) + self.lbl_ssh_port.setObjectName("lbl_ssh_port") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_port) + self.sbx_ssh_port = QtWidgets.QSpinBox(self.tab_ssh) + self.sbx_ssh_port.setMaximum(65535) + self.sbx_ssh_port.setProperty("value", 22) + self.sbx_ssh_port.setObjectName("sbx_ssh_port") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.sbx_ssh_port) + self.lbl_ssh_user = QtWidgets.QLabel(self.tab_ssh) + self.lbl_ssh_user.setObjectName("lbl_ssh_user") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_user) + self.txt_ssh_user = QtWidgets.QLineEdit(self.tab_ssh) + self.txt_ssh_user.setText("pi") + self.txt_ssh_user.setObjectName("txt_ssh_user") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txt_ssh_user) + self.tab_properties.addTab(self.tab_ssh, "") + self.gridLayout.addWidget(self.tab_properties, 1, 0, 1, 2) + self.btn_box = QtWidgets.QDialogButtonBox(diag_connections) + self.btn_box.setOrientation(QtCore.Qt.Horizontal) + self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save) + self.btn_box.setObjectName("btn_box") + self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2) self.tre_connections = QtWidgets.QTreeWidget(diag_connections) self.tre_connections.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.tre_connections.setObjectName("tre_connections") @@ -54,66 +143,9 @@ class Ui_diag_connections(object): self.btn_add.setObjectName("btn_add") self.vl_edit.addWidget(self.btn_add) self.gridLayout.addLayout(self.vl_edit, 0, 1, 1, 1) - self.gb_properties = QtWidgets.QGroupBox(diag_connections) - self.gb_properties.setObjectName("gb_properties") - self.formLayout = QtWidgets.QFormLayout(self.gb_properties) - self.formLayout.setObjectName("formLayout") - self.lbl_name = QtWidgets.QLabel(self.gb_properties) - self.lbl_name.setObjectName("lbl_name") - self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_name) - self.lbl_folder = QtWidgets.QLabel(self.gb_properties) - self.lbl_folder.setObjectName("lbl_folder") - self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder) - self.lbl_address = QtWidgets.QLabel(self.gb_properties) - self.lbl_address.setObjectName("lbl_address") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address) - self.lbl_port = QtWidgets.QLabel(self.gb_properties) - self.lbl_port.setObjectName("lbl_port") - self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port) - self.txt_name = QtWidgets.QLineEdit(self.gb_properties) - self.txt_name.setObjectName("txt_name") - self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name) - self.txt_address = QtWidgets.QLineEdit(self.gb_properties) - self.txt_address.setObjectName("txt_address") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address) - self.sbx_port = QtWidgets.QSpinBox(self.gb_properties) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth()) - self.sbx_port.setSizePolicy(sizePolicy) - self.sbx_port.setMinimum(1) - self.sbx_port.setMaximum(65535) - self.sbx_port.setProperty("value", 55123) - self.sbx_port.setObjectName("sbx_port") - self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port) - self.cbb_folder = QtWidgets.QComboBox(self.gb_properties) - self.cbb_folder.setEditable(True) - self.cbb_folder.setObjectName("cbb_folder") - self.cbb_folder.addItem("") - self.cbb_folder.setItemText(0, "") - self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder) - self.lbl_timeout = QtWidgets.QLabel(self.gb_properties) - self.lbl_timeout.setObjectName("lbl_timeout") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout) - self.sbx_timeout = QtWidgets.QSpinBox(self.gb_properties) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth()) - self.sbx_timeout.setSizePolicy(sizePolicy) - self.sbx_timeout.setMinimum(5) - self.sbx_timeout.setMaximum(30) - self.sbx_timeout.setObjectName("sbx_timeout") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout) - self.gridLayout.addWidget(self.gb_properties, 1, 0, 1, 2) - self.btn_box = QtWidgets.QDialogButtonBox(diag_connections) - self.btn_box.setOrientation(QtCore.Qt.Horizontal) - self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save) - self.btn_box.setObjectName("btn_box") - self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2) self.retranslateUi(diag_connections) + self.tab_properties.setCurrentIndex(0) self.btn_box.accepted.connect(diag_connections.accept) # type: ignore self.btn_box.rejected.connect(diag_connections.reject) # type: ignore QtCore.QMetaObject.connectSlotsByName(diag_connections) @@ -121,15 +153,19 @@ class Ui_diag_connections(object): def retranslateUi(self, diag_connections): _translate = QtCore.QCoreApplication.translate diag_connections.setWindowTitle(_translate("diag_connections", "Revolution Pi connections")) - self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection name")) - self.tre_connections.headerItem().setText(1, _translate("diag_connections", "Address")) - self.gb_properties.setTitle(_translate("diag_connections", "Connection properties")) self.lbl_name.setText(_translate("diag_connections", "Display name:")) - self.lbl_folder.setText(_translate("diag_connections", "Sub folder:")) self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP):")) self.lbl_port.setText(_translate("diag_connections", "Port (Default {0}):")) self.lbl_timeout.setText(_translate("diag_connections", "Connection timeout:")) self.sbx_timeout.setSuffix(_translate("diag_connections", " sec.")) + self.lbl_folder.setText(_translate("diag_connections", "Sub folder:")) + self.tab_properties.setTabText(self.tab_properties.indexOf(self.tab_connection), _translate("diag_connections", "Connection")) + self.lbl_ssh_use_tunnel.setText(_translate("diag_connections", "Connect over SSH tunnel:")) + self.lbl_ssh_port.setText(_translate("diag_connections", "SSH port:")) + self.lbl_ssh_user.setText(_translate("diag_connections", "SSH user name:")) + self.tab_properties.setTabText(self.tab_properties.indexOf(self.tab_ssh), _translate("diag_connections", "Over SSH")) + self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection name")) + self.tre_connections.headerItem().setText(1, _translate("diag_connections", "Address")) from . import ressources_rc diff --git a/ui_dev/revpiplclist.ui b/ui_dev/revpiplclist.ui index 1f9b2ea..d75954d 100644 --- a/ui_dev/revpiplclist.ui +++ b/ui_dev/revpiplclist.ui @@ -6,14 +6,184 @@ 0 0 - 520 - 508 + 496 + 569 Revolution Pi connections + + + + 0 + + + + Connection + + + + + + Display name: + + + + + + + + + + Address (DNS/IP): + + + + + + + + + + Port (Default {0}): + + + + + + + + 0 + 0 + + + + 1 + + + 65535 + + + 55123 + + + + + + + Connection timeout: + + + + + + + + 0 + 0 + + + + sec. + + + 5 + + + 30 + + + + + + + Sub folder: + + + + + + + true + + + + + + + + + + + + + Over SSH + + + + + + Connect over SSH tunnel: + + + + + + + + + + true + + + + + + + SSH port: + + + + + + + 65535 + + + 22 + + + + + + + SSH user name: + + + + + + + pi + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Discard|QDialogButtonBox::Save + + + @@ -92,116 +262,6 @@ - - - - Connection properties - - - - - - Display name: - - - - - - - Sub folder: - - - - - - - Address (DNS/IP): - - - - - - - Port (Default {0}): - - - - - - - - - - - - - - 0 - 0 - - - - 1 - - - 65535 - - - 55123 - - - - - - - true - - - - - - - - - - - - Connection timeout: - - - - - - - - 0 - 0 - - - - sec. - - - 5 - - - 30 - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Discard|QDialogButtonBox::Save - - -