MqttMonitor/MqttConnectionProfilesDialog.py

branch
connection_profiles
changeset 26
ad232a5129cc
parent 23
0b23bd856e43
child 30
17ef10819773
equal deleted inserted replaced
25:01d44a4decf5 26:ad232a5129cc
14 from PyQt5.QtCore import pyqtSlot, Qt, QUuid 14 from PyQt5.QtCore import pyqtSlot, Qt, QUuid
15 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \ 15 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
16 QListWidgetItem, QInputDialog, QLineEdit 16 QListWidgetItem, QInputDialog, QLineEdit
17 17
18 from E5Gui import E5MessageBox 18 from E5Gui import E5MessageBox
19 from E5Gui.E5PathPicker import E5PathPickerModes
19 20
20 from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog 21 from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog
21 22
22 import UI.PixmapCache 23 import UI.PixmapCache
23 from Utilities.crypto import pwConvert 24 from Utilities.crypto import pwConvert
35 @type MqttClient 36 @type MqttClient
36 @param profiles dictionary containing dictionaries containing the 37 @param profiles dictionary containing dictionaries containing the
37 connection parameters. Each entry must have the keys 38 connection parameters. Each entry must have the keys
38 "BrokerAddress", "BrokerPort", "ClientId", 39 "BrokerAddress", "BrokerPort", "ClientId",
39 "Keepalive", "CleanSession", "Username", "Password", "WillTopic", 40 "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
40 "WillMessage", "WillQos", "WillRetain". 41 "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
42 "TlsClientCert", "TlsClientKey".
41 @type dict 43 @type dict
42 @param parent reference to the parent widget 44 @param parent reference to the parent widget
43 @type QWidget 45 @type QWidget
44 """ 46 """
45 super(MqttConnectionProfilesDialog, self).__init__(parent) 47 super(MqttConnectionProfilesDialog, self).__init__(parent)
50 self.__profiles = collections.defaultdict(self.__defaultProfile) 52 self.__profiles = collections.defaultdict(self.__defaultProfile)
51 self.__profiles.update(profiles) 53 self.__profiles.update(profiles)
52 self.__profilesChanged = False 54 self.__profilesChanged = False
53 55
54 self.plusButton.setIcon(UI.PixmapCache.getIcon("plus.png")) 56 self.plusButton.setIcon(UI.PixmapCache.getIcon("plus.png"))
57 self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
55 self.minusButton.setIcon(UI.PixmapCache.getIcon("minus.png")) 58 self.minusButton.setIcon(UI.PixmapCache.getIcon("minus.png"))
59
60 self.tlsCertsFilePicker.setMode(E5PathPickerModes.OpenFileMode)
61 self.tlsCertsFilePicker.setFilters(
62 self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
63 self.tlsSelfSignedCertsFilePicker.setMode(
64 E5PathPickerModes.OpenFileMode)
65 self.tlsSelfSignedCertsFilePicker.setFilters(
66 self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
67 self.tlsSelfSignedClientCertFilePicker.setMode(
68 E5PathPickerModes.OpenFileMode)
69 self.tlsSelfSignedClientCertFilePicker.setFilters(
70 self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
71 self.tlsSelfSignedClientKeyFilePicker.setMode(
72 E5PathPickerModes.OpenFileMode)
73 self.tlsSelfSignedClientKeyFilePicker.setFilters(
74 self.tr("Key Files (*.key *.pem);;All Files (*)"))
56 75
57 self.profileTabWidget.setCurrentIndex(0) 76 self.profileTabWidget.setCurrentIndex(0)
58 77
59 if len(self.__profiles) == 0: 78 if len(self.__profiles) == 0:
60 self.minusButton.setEnabled(False) 79 self.minusButton.setEnabled(False)
80 self.copyButton.setEnabled(False)
61 81
62 self.profileFrame.setEnabled(False) 82 self.profileFrame.setEnabled(False)
63 self.__populatingProfile = False 83 self.__populatingProfile = False
64 self.__deletingProfile = False 84 self.__deletingProfile = False
65 85
103 @type QListWidgetItem 123 @type QListWidgetItem
104 @param previous previous current item 124 @param previous previous current item
105 @type QListWidgetItem 125 @type QListWidgetItem
106 """ 126 """
107 self.minusButton.setEnabled(current is not None) 127 self.minusButton.setEnabled(current is not None)
128 self.copyButton.setEnabled(current is not None)
108 129
109 if current is not previous: 130 if current is not previous:
110 if not self.__deletingProfile and self.__isChangedProfile(): 131 if not self.__deletingProfile and self.__isChangedProfile():
111 # modified profile belongs to previous 132 # modified profile belongs to previous
112 yes = E5MessageBox.yesNo( 133 yes = E5MessageBox.yesNo(
133 profileName, ok = QInputDialog.getText( 154 profileName, ok = QInputDialog.getText(
134 self, 155 self,
135 self.tr("New Connection Profile"), 156 self.tr("New Connection Profile"),
136 self.tr("Enter name for the new Connection Profile:"), 157 self.tr("Enter name for the new Connection Profile:"),
137 QLineEdit.Normal) 158 QLineEdit.Normal)
138 if ok and bool(profileName) and profileName not in self.__profiles: 159 if ok and bool(profileName):
139 itm = QListWidgetItem(profileName, self.profilesList) 160 if profileName in self.__profiles:
140 self.profilesList.setCurrentItem(itm) 161 E5MessageBox.warning(
141 self.brokerAddressEdit.setFocus(Qt.OtherFocusReason) 162 self,
163 self.tr("New Connection Profile"),
164 self.tr("""<p>A connection named <b>{0}</b> exists"""
165 """ already. Aborting...</p>""").format(
166 profileName))
167 else:
168 itm = QListWidgetItem(profileName, self.profilesList)
169 self.profilesList.setCurrentItem(itm)
170 self.brokerAddressEdit.setFocus(Qt.OtherFocusReason)
171
172 @pyqtSlot()
173 def on_copyButton_clicked(self):
174 """
175 Private slot to copy the selected profile entry.
176 """
177 itm = self.profilesList.currentItem()
178 if itm:
179 profileName = itm.text()
180 newProfileName, ok = QInputDialog.getText(
181 self,
182 self.tr("Copy Connection Profile"),
183 self.tr("Enter name for the copied Connection Profile:"),
184 QLineEdit.Normal)
185 if ok and bool(newProfileName):
186 if newProfileName in self.__profiles:
187 E5MessageBox.warning(
188 self,
189 self.tr("Copy Connection Profile"),
190 self.tr("""<p>A connection named <b>{0}</b> exists"""
191 """ already. Aborting...</p>""").format(
192 newProfileName))
193 else:
194 profile = self.__defaultProfile()
195 profile.update(self.__profiles[profileName])
196 self.__profiles[newProfileName] = profile
197
198 itm = QListWidgetItem(newProfileName, self.profilesList)
199 self.profilesList.setCurrentItem(itm)
200 self.brokerAddressEdit.setFocus(Qt.OtherFocusReason)
142 201
143 @pyqtSlot() 202 @pyqtSlot()
144 def on_minusButton_clicked(self): 203 def on_minusButton_clicked(self):
145 """ 204 """
146 Private slot to delete the selected entry. 205 Private slot to delete the selected entry.
168 Public method to return a dictionary of profiles. 227 Public method to return a dictionary of profiles.
169 228
170 @return dictionary containing dictionaries containing the defined 229 @return dictionary containing dictionaries containing the defined
171 connection profiles. Each entry have the keys "BrokerAddress", 230 connection profiles. Each entry have the keys "BrokerAddress",
172 "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username", 231 "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username",
173 "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain". 232 "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
233 "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
174 @rtype dict 234 @rtype dict
175 """ 235 """
176 profilesDict = {} 236 profilesDict = {}
177 profilesDict.update(self.__profiles) 237 profilesDict.update(self.__profiles)
178 return profilesDict 238 return profilesDict
195 "Password": pwConvert(self.passwordEdit.text(), encode=True), 255 "Password": pwConvert(self.passwordEdit.text(), encode=True),
196 "WillTopic": self.willTopicEdit.text(), 256 "WillTopic": self.willTopicEdit.text(),
197 "WillMessage": self.willMessageEdit.toPlainText(), 257 "WillMessage": self.willMessageEdit.toPlainText(),
198 "WillQos": self.willQosSpinBox.value(), 258 "WillQos": self.willQosSpinBox.value(),
199 "WillRetain": self.willRetainCheckBox.isChecked(), 259 "WillRetain": self.willRetainCheckBox.isChecked(),
260 "TlsEnable": self.tlsGroupBox.isChecked(),
261 "TlsCaCert": "",
262 "TlsClientCert": "",
263 "TlsClientKey": "",
200 } 264 }
265 if profile["TlsEnable"]:
266 if self.tlsCertsFileButton.isChecked():
267 profile["TlsCaCert"] = self.tlsCertsFilePicker.text()
268 elif self.tlsSelfSignedCertsButton.isChecked():
269 profile["TlsCaCert"] = \
270 self.tlsSelfSignedCertsFilePicker.text()
271 profile["TlsClientCert"] = \
272 self.tlsSelfSignedClientCertFilePicker.text()
273 profile["TlsClientKey"] = \
274 self.tlsSelfSignedClientKeyFilePicker.text()
275
201 self.__profiles[profileName] = profile 276 self.__profiles[profileName] = profile
202 self.__profilesChanged = True 277 self.__profilesChanged = True
203 278
204 return profileName 279 return profileName
205 280
210 @return default dictionary entry 285 @return default dictionary entry
211 @rtype dict 286 @rtype dict
212 """ 287 """
213 defaultProfile = self.__client.defaultConnectionOptions() 288 defaultProfile = self.__client.defaultConnectionOptions()
214 defaultProfile["BrokerAddress"] = "" 289 defaultProfile["BrokerAddress"] = ""
215 defaultProfile["BrokerPort"] = 1883 290 if defaultProfile["TlsEnable"]:
291 defaultProfile["BrokerPort"] = 8883
292 else:
293 defaultProfile["BrokerPort"] = 1883
216 294
217 return defaultProfile 295 return defaultProfile
218 296
219 def __populateProfilesList(self, currentProfile=""): 297 def __populateProfilesList(self, currentProfile=""):
220 """ 298 """
245 Private method to populate the profile data entry fields. 323 Private method to populate the profile data entry fields.
246 324
247 @param profileName name of the profile to get data from 325 @param profileName name of the profile to get data from
248 @type str 326 @type str
249 """ 327 """
328 profile = self.__defaultProfile()
250 if profileName: 329 if profileName:
251 profile = self.__profiles[profileName] 330 profile.update(self.__profiles[profileName])
252 else:
253 profile = self.__defaultProfile()
254 331
255 self.__populatingProfile = True 332 self.__populatingProfile = True
256 if profileName is not None: 333 if profileName is not None:
257 self.profileEdit.setText(profileName) 334 self.profileEdit.setText(profileName)
258 self.brokerAddressEdit.setText(profile["BrokerAddress"]) 335 self.brokerAddressEdit.setText(profile["BrokerAddress"])
264 self.passwordEdit.setText(pwConvert(profile["Password"], encode=False)) 341 self.passwordEdit.setText(pwConvert(profile["Password"], encode=False))
265 self.willTopicEdit.setText(profile["WillTopic"]) 342 self.willTopicEdit.setText(profile["WillTopic"])
266 self.willMessageEdit.setPlainText(profile["WillMessage"]) 343 self.willMessageEdit.setPlainText(profile["WillMessage"])
267 self.willQosSpinBox.setValue(profile["WillQos"]) 344 self.willQosSpinBox.setValue(profile["WillQos"])
268 self.willRetainCheckBox.setChecked(profile["WillRetain"]) 345 self.willRetainCheckBox.setChecked(profile["WillRetain"])
346 self.tlsGroupBox.setChecked(profile["TlsEnable"])
347 if profile["TlsCaCert"] and profile["TlsClientCert"]:
348 self.tlsSelfSignedCertsButton.setChecked(True)
349 self.tlsSelfSignedCertsFilePicker.setText(profile["TlsCaCert"])
350 self.tlsSelfSignedClientCertFilePicker.setText(
351 profile["TlsClientCert"])
352 self.tlsSelfSignedClientKeyFilePicker.setText(
353 profile["TlsClientKey"])
354 elif profile["TlsCaCert"]:
355 self.tlsCertsFileButton.setChecked(True)
356 self.tlsCertsFilePicker.setText(profile["TlsCaCert"])
357 else:
358 self.tlsDefaultCertsButton.setChecked(True)
269 self.__populatingProfile = False 359 self.__populatingProfile = False
270 360
271 self.profileFrame.setEnabled(True) 361 self.profileFrame.setEnabled(True)
272 self.__updateApplyButton() 362 self.__updateApplyButton()
273 363
286 self.passwordEdit.setText("") 376 self.passwordEdit.setText("")
287 self.willTopicEdit.setText("") 377 self.willTopicEdit.setText("")
288 self.willMessageEdit.setPlainText("") 378 self.willMessageEdit.setPlainText("")
289 self.willQosSpinBox.setValue(0) 379 self.willQosSpinBox.setValue(0)
290 self.willRetainCheckBox.setChecked(False) 380 self.willRetainCheckBox.setChecked(False)
381 self.tlsGroupBox.setChecked(False)
382 self.tlsDefaultCertsButton.setChecked(True)
383 self.tlsCertsFileButton.setChecked(True)
384 self.tlsCertsFilePicker.setText("")
385 self.tlsSelfSignedCertsButton.setChecked(False)
386 self.tlsSelfSignedCertsFilePicker.setText("")
387 self.tlsSelfSignedClientCertFilePicker.setText("")
388 self.tlsSelfSignedClientKeyFilePicker.setText("")
291 self.__populatingProfile = False 389 self.__populatingProfile = False
292 390
293 self.profileFrame.setEnabled(False) 391 self.profileFrame.setEnabled(False)
294 self.__updateApplyButton() 392 self.__updateApplyButton()
295 393
320 profileName = self.profileEdit.text() 418 profileName = self.profileEdit.text()
321 if profileName == "": 419 if profileName == "":
322 return False 420 return False
323 421
324 elif profileName in self.__profiles: 422 elif profileName in self.__profiles:
325 profile = self.__profiles[profileName] 423 profile = self.__defaultProfile()
326 return ( 424 profile.update(self.__profiles[profileName])
425 changed = (
327 self.brokerAddressEdit.text() != profile["BrokerAddress"] or 426 self.brokerAddressEdit.text() != profile["BrokerAddress"] or
328 self.brokerPortSpinBox.value() != profile["BrokerPort"] or 427 self.brokerPortSpinBox.value() != profile["BrokerPort"] or
329 self.clientIdEdit.text() != profile["ClientId"] or 428 self.clientIdEdit.text() != profile["ClientId"] or
330 self.keepaliveSpinBox.value() != profile["Keepalive"] or 429 self.keepaliveSpinBox.value() != profile["Keepalive"] or
331 self.cleanSessionCheckBox.isChecked() != 430 self.cleanSessionCheckBox.isChecked() !=
334 self.passwordEdit.text() != 433 self.passwordEdit.text() !=
335 pwConvert(profile["Password"], encode=False) or 434 pwConvert(profile["Password"], encode=False) or
336 self.willTopicEdit.text() != profile["WillTopic"] or 435 self.willTopicEdit.text() != profile["WillTopic"] or
337 self.willMessageEdit.toPlainText() != profile["WillMessage"] or 436 self.willMessageEdit.toPlainText() != profile["WillMessage"] or
338 self.willQosSpinBox.value() != profile["WillQos"] or 437 self.willQosSpinBox.value() != profile["WillQos"] or
339 self.willRetainCheckBox.isChecked() != profile["WillRetain"] 438 self.willRetainCheckBox.isChecked() != profile["WillRetain"] or
439 self.tlsGroupBox.isChecked() != profile["TlsEnable"]
340 ) 440 )
441 # check TLS stuff only, if not yet changed
442 if not changed:
443 if self.tlsCertsFileButton.isChecked():
444 changed |= (
445 self.tlsCertsFilePicker.text() != profile["TlsCaCert"]
446 )
447 elif self.tlsSelfSignedCertsButton.isChecked():
448 changed |= (
449 self.tlsSelfSignedCertsFilePicker.text() !=
450 profile["TlsCaCert"] or
451 self.tlsSelfSignedClientCertFilePicker.text() !=
452 profile["TlsClientCert"] or
453 self.tlsSelfSignedClientKeyFilePicker.text() !=
454 profile["TlsClientKey"]
455 )
456 return changed
341 457
342 else: 458 else:
343 return True 459 return True
344 460
345 def __updateApplyButton(self): 461 def __updateApplyButton(self):
358 E5MessageBox.critical( 474 E5MessageBox.critical(
359 self, 475 self,
360 self.tr("Invalid Connection Parameters"), 476 self.tr("Invalid Connection Parameters"),
361 self.tr("An empty Client ID requires a clean session.")) 477 self.tr("An empty Client ID requires a clean session."))
362 478
479 if self.tlsGroupBox.isChecked():
480 if self.tlsCertsFileButton.isChecked():
481 # condition 3a: if CA certificates file shall be used, it must
482 # be given
483 enable &= bool(self.tlsCertsFilePicker.text())
484 elif self.tlsSelfSignedCertsButton.isChecked():
485 # condition 3b: if client certificates shall be used, all files
486 # must be given
487 enable &= (
488 bool(self.tlsSelfSignedCertsFilePicker.text()) and
489 bool(self.tlsSelfSignedClientCertFilePicker.text()) and
490 bool(self.tlsSelfSignedClientKeyFilePicker.text())
491 )
492
363 self.profileButtonBox.button(QDialogButtonBox.Apply).setEnabled(enable) 493 self.profileButtonBox.button(QDialogButtonBox.Apply).setEnabled(enable)
364 494
365 @pyqtSlot(str) 495 @pyqtSlot(str)
366 def on_brokerAddressEdit_textChanged(self, address): 496 def on_brokerAddressEdit_textChanged(self, address):
367 """ 497 """
377 """ 507 """
378 Private slot to generate a client ID. 508 Private slot to generate a client ID.
379 """ 509 """
380 uuid = QUuid.createUuid() 510 uuid = QUuid.createUuid()
381 self.clientIdEdit.setText(uuid.toString(QUuid.WithoutBraces)) 511 self.clientIdEdit.setText(uuid.toString(QUuid.WithoutBraces))
512
513 @pyqtSlot(str)
514 def on_clientIdEdit_textChanged(self, clientId):
515 """
516 Private slot handling a change of the client ID string.
517
518 @param clientId client ID
519 @type str
520 """
521 self.__updateApplyButton()
522
523 @pyqtSlot(bool)
524 def on_cleanSessionCheckBox_clicked(self, checked):
525 """
526 Private slot to handle a change of the clean session selection.
527
528 @param checked current state of the clean session selection
529 @type bool
530 """
531 self.__updateApplyButton()
532
533 @pyqtSlot(str)
534 def on_tlsCertsFilePicker_textChanged(self, path):
535 """
536 Private slot handling a change of the TLS CA certificates file.
537
538 @param path file path
539 @type str
540 """
541 self.__updateApplyButton()
542
543 @pyqtSlot(str)
544 def on_tlsSelfSignedCertsFilePicker_textChanged(self, path):
545 """
546 Private slot handling a change of the TLS CA certificates file.
547
548 @param path file path
549 @type str
550 """
551 self.__updateApplyButton()
552
553 @pyqtSlot(str)
554 def on_tlsSelfSignedClientCertFilePicker_textChanged(self, path):
555 """
556 Private slot handling a change of the TLS client certificate file.
557
558 @param path file path
559 @type str
560 """
561 self.__updateApplyButton()
562
563 @pyqtSlot(str)
564 def on_tlsSelfSignedClientKeyFilePicker_textChanged(self, path):
565 """
566 Private slot handling a change of the TLS client key file.
567
568 @param path file path
569 @type str
570 """
571 self.__updateApplyButton()
572
573 @pyqtSlot(bool)
574 def on_tlsGroupBox_toggled(self, checked):
575 """
576 Private slot handling the selection of TLS mode.
577
578 @param checked state of the selection
579 @type bool
580 """
581 if checked and self.brokerPortSpinBox.value() == 1883:
582 # port is still standard non-TLS port
583 yes = E5MessageBox.yesNo(
584 self,
585 self.tr("SSL/TLS Enabled"),
586 self.tr(
587 """Encrypted connection using SSL/TLS has been enabled."""
588 """ However, the broker port is still the default"""
589 """ unencrypted port (port 1883). Shall this be"""
590 """ changed?"""),
591 icon=E5MessageBox.Warning,
592 yesDefault=True)
593 if yes:
594 self.brokerPortSpinBox.setValue(8883)
595 elif not checked and self.brokerPortSpinBox.value() == 8883:
596 # port is still standard TLS port
597 yes = E5MessageBox.yesNo(
598 self,
599 self.tr("SSL/TLS Disabled"),
600 self.tr(
601 """Encrypted connection using SSL/TLS has been disabled."""
602 """ However, the broker port is still the default"""
603 """ encrypted port (port 8883). Shall this be"""
604 """ changed?"""),
605 icon=E5MessageBox.Warning,
606 yesDefault=True)
607 if yes:
608 self.brokerPortSpinBox.setValue(1883)
609
610 self.__updateApplyButton()
611
612 @pyqtSlot(bool)
613 def on_tlsDefaultCertsButton_toggled(self, checked):
614 """
615 Private slot handling the selection of using the default
616 certificates file.
617
618 @param checked state of the selection
619 @type bool
620 """
621 self.__updateApplyButton()
622
623 @pyqtSlot(bool)
624 def on_tlsCertsFileButton_toggled(self, checked):
625 """
626 Private slot handling the selection of using a non-default
627 certificates file.
628
629 @param checked state of the selection
630 @type bool
631 """
632 self.__updateApplyButton()
633
634 @pyqtSlot(bool)
635 def on_tlsSelfSignedCertsButton_toggled(self, checked):
636 """
637 Private slot handling the selection of using self signed
638 client certificate and key files.
639
640 @param checked state of the selection
641 @type bool
642 """
643 self.__updateApplyButton()
382 644
383 @pyqtSlot() 645 @pyqtSlot()
384 def reject(self): 646 def reject(self):
385 """ 647 """
386 Public slot to reject the dialog changes. 648 Public slot to reject the dialog changes.
433 yesDefault=True) 695 yesDefault=True)
434 if yes: 696 if yes:
435 self.__applyProfile() 697 self.__applyProfile()
436 698
437 super(MqttConnectionProfilesDialog, self).accept() 699 super(MqttConnectionProfilesDialog, self).accept()
438
439 @pyqtSlot(str)
440 def on_clientIdEdit_textChanged(self, clientId):
441 """
442 Private slot handling a change of the client ID string.
443
444 @param clientId client ID
445 @type str
446 """
447 self.__updateApplyButton()
448
449 @pyqtSlot(bool)
450 def on_cleanSessionCheckBox_clicked(self, checked):
451 """
452 Private slot to handle a change of the clean session selection.
453
454 @param checked current state of the clean session selection
455 @type bool
456 """
457 self.__updateApplyButton()

eric ide

mercurial