From b09e8a6d4bbadccd5cbf458cb393ab41a9f2856d Mon Sep 17 00:00:00 2001 From: eelke Date: Sat, 24 Aug 2019 20:47:32 +0200 Subject: [PATCH 01/12] ConnectionManager overhaul - connection settings are now changed by seperate component currently called in a seperate window - old settings pane on the right of the connections had been removed - new edit config button added between new connection and remove connection --- pglab/ConnectionConfigurationWidget.cpp | 181 +++++++++++++++++++ pglab/ConnectionConfigurationWidget.h | 60 +++++++ pglab/ConnectionController.cpp | 216 +++++++++++++++++++++++ pglab/ConnectionController.h | 63 +++++++ pglab/ConnectionList.cpp | 163 ----------------- pglab/ConnectionList.h | 54 ------ pglab/ConnectionListModel.cpp | 223 ++++++++++++++++++------ pglab/ConnectionListModel.h | 30 +++- pglab/ConnectionManagerWindow.cpp | 59 ++----- pglab/ConnectionManagerWindow.h | 9 +- pglab/ConnectionManagerWindow.ui | 157 ++--------------- pglab/DatabaseWindow.cpp | 1 + pglab/MasterController.cpp | 200 +-------------------- pglab/MasterController.h | 51 ------ pglab/SslModeModel.cpp | 69 ++++++++ pglab/SslModeModel.h | 20 +++ pglab/icons/server_configuration.png | Bin 0 -> 2506 bytes pglab/pglab.pro | 8 +- pglab/resources.qrc | 1 + pglablib/ConnectionConfig.h | 4 +- 20 files changed, 836 insertions(+), 733 deletions(-) create mode 100644 pglab/ConnectionConfigurationWidget.cpp create mode 100644 pglab/ConnectionConfigurationWidget.h create mode 100644 pglab/ConnectionController.cpp create mode 100644 pglab/ConnectionController.h delete mode 100644 pglab/ConnectionList.cpp delete mode 100644 pglab/ConnectionList.h create mode 100644 pglab/SslModeModel.cpp create mode 100644 pglab/SslModeModel.h create mode 100644 pglab/icons/server_configuration.png diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp new file mode 100644 index 0000000..81fa6b6 --- /dev/null +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -0,0 +1,181 @@ +#include "ConnectionConfigurationWidget.h" +#include "SslModeModel.h" +#include "ConnectionConfig.h" +#include "ConnectionController.h" +#include "ConnectionListModel.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SET_OBJECT_NAME(var) var->setObjectName(#var) + +void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg) +{ + auto w = new ConnectionConfigurationWidget; + w->setData(cfg); + + + auto btn_hbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); + //auto btn_test = btn_hbox->addButton(tr("Test"), QDialogButtonBox::ActionRole); + + auto vbox = new QVBoxLayout; + vbox->addWidget(w); + vbox->addWidget(btn_hbox); + + auto win = new QDialog; + win->setWindowTitle(tr("Edit connection configuration")); + win->setLayout(vbox); + + win->connect(btn_hbox, &QDialogButtonBox::accepted, [ctrl, w, win] () { + auto cc = w->data(); + ctrl->getConnectionListModel()->save(cc); + win->accept(); + }); + win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] () { win->reject(); }); + + win->connect(win, &QDialog::finished, [win] (int) + { + delete win; + }); + + win->show(); +} + +ConnectionConfigurationWidget::ConnectionConfigurationWidget(QWidget *parent) + : QWidget(parent) +{ + lblName = new QLabel; + SET_OBJECT_NAME(lblName); + edtName = new QLineEdit ; + SET_OBJECT_NAME(edtName); + lblName->setBuddy(edtName); + + lblHost = new QLabel; + SET_OBJECT_NAME(lblHost); + edtHost = new QLineEdit; + SET_OBJECT_NAME(edtHost); + lblHost->setBuddy(edtHost); + + lblPort = new QLabel; + SET_OBJECT_NAME(lblPort); + spinPort = new QSpinBox; + SET_OBJECT_NAME(spinPort); + spinPort->setRange(1, std::numeric_limits::max()); + lblPort->setBuddy(spinPort); + + lblUser = new QLabel; + SET_OBJECT_NAME(lblUser); + edtUser = new QLineEdit; + SET_OBJECT_NAME(edtUser); + lblUser->setBuddy(edtUser); + + lblDbName = new QLabel; + SET_OBJECT_NAME(lblDbName); + edtDbname = new QLineEdit; + SET_OBJECT_NAME(edtDbname); + lblDbName->setBuddy(edtDbname); + + lblSsl = new QLabel; + SET_OBJECT_NAME(lblSsl); + cmbbxSsl = new QComboBox; + SET_OBJECT_NAME(cmbbxSsl); + cmbbxSsl->setModelColumn(0); + auto ssl_model = new SslModeModel(this); + cmbbxSsl->setModel(ssl_model); + lblSsl->setBuddy(cmbbxSsl); + + lblCert = new QLabel; + SET_OBJECT_NAME(lblCert); + edtCert = new QLineEdit; + SET_OBJECT_NAME(edtCert); + lblCert->setBuddy(edtCert); + + lblKey = new QLabel; + SET_OBJECT_NAME(lblKey); + edtKey = new QLineEdit; + SET_OBJECT_NAME(edtKey); + lblKey->setBuddy(edtKey); + + lblRootCert = new QLabel; + SET_OBJECT_NAME(lblRootCert); + edtRootCert = new QLineEdit; + SET_OBJECT_NAME(edtRootCert); + lblRootCert->setBuddy(edtRootCert); + + lblCrl = new QLabel; + SET_OBJECT_NAME(lblCrl); + edtCrl = new QLineEdit; + SET_OBJECT_NAME(edtCrl); + lblCrl->setBuddy(edtCrl); + + formLayout = new QFormLayout; + + setLayout(formLayout); + formLayout->addRow(lblName, edtName); + formLayout->addRow(lblHost, edtHost); + formLayout->addRow(lblPort, spinPort); + formLayout->addRow(lblUser, edtUser); + formLayout->addRow(lblDbName, edtDbname); + formLayout->addRow(lblSsl, cmbbxSsl); + formLayout->addRow(lblCert, edtCert); + formLayout->addRow(lblKey, edtKey); + formLayout->addRow(lblRootCert, edtRootCert); + formLayout->addRow(lblCrl, edtCrl); + + retranslateUi(); +} + +void ConnectionConfigurationWidget::retranslateUi() +{ + lblName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Name", nullptr)); + lblHost->setText(QApplication::translate("ConnectionConfigurationWidget", "&Host", nullptr)); + lblPort->setText(QApplication::translate("ConnectionConfigurationWidget", "&Port", nullptr)); + lblUser->setText(QApplication::translate("ConnectionConfigurationWidget", "&Username", nullptr)); + lblDbName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Database", nullptr)); + lblSsl->setText(QApplication::translate("ConnectionConfigurationWidget", "&SSL mode", nullptr)); + lblCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Certificate", nullptr)); + lblKey->setText(QApplication::translate("ConnectionConfigurationWidget", "&Key", nullptr)); + lblRootCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Root cert.", nullptr)); + lblCrl->setText(QApplication::translate("ConnectionConfigurationWidget", "Revocation &list", nullptr)); +} + +void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) +{ + m_uuid = cfg.uuid(); + edtName->setText(stdStrToQ(cfg.name())); + edtHost->setText(stdStrToQ(cfg.host())); + spinPort->setValue(cfg.port()); + edtUser->setText(stdStrToQ(cfg.user())); + edtDbname->setText(stdStrToQ(cfg.dbname())); + cmbbxSsl->setCurrentIndex(static_cast(cfg.sslMode())); + edtCert->setText(stdStrToQ(cfg.sslCert())); + edtKey->setText(stdStrToQ(cfg.sslKey())); + edtRootCert->setText(stdStrToQ(cfg.sslRootCert())); + edtCrl->setText(stdStrToQ(cfg.sslCrl())); +} + +ConnectionConfig ConnectionConfigurationWidget::data() const +{ + ConnectionConfig cfg; + cfg.setUuid(m_uuid); + cfg.setName(qStrToStd(edtName->text())); + cfg.setHost(qStrToStd(edtHost->text())); + cfg.setPort(static_cast(spinPort->value())); + cfg.setUser(qStrToStd(edtUser->text())); + cfg.setDbname(qStrToStd(edtDbname->text())); + cfg.setSslMode(static_cast(cmbbxSsl->currentIndex())); + cfg.setSslCert(qStrToStd(edtCert->text())); + cfg.setSslKey(qStrToStd(edtKey->text())); + cfg.setSslRootCert(qStrToStd(edtRootCert->text())); + cfg.setSslCrl(qStrToStd(edtCrl->text())); + return cfg; +} + diff --git a/pglab/ConnectionConfigurationWidget.h b/pglab/ConnectionConfigurationWidget.h new file mode 100644 index 0000000..4d4eadf --- /dev/null +++ b/pglab/ConnectionConfigurationWidget.h @@ -0,0 +1,60 @@ +#ifndef CONNECTIONCONFIGURATIONWIDGET_H +#define CONNECTIONCONFIGURATIONWIDGET_H + +#include +#include + +class ConnectionController; +class ConnectionConfig; +class QFormLayout; +class QLabel; +class QLineEdit; +class QSpinBox; +class QComboBox; +class QDataWidgetMapper; + + +class ConnectionConfigurationWidget : public QWidget +{ + Q_OBJECT +public: + static void editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg); + + explicit ConnectionConfigurationWidget(QWidget *parent = nullptr); + + void retranslateUi(); + void setData(const ConnectionConfig &cfg); + ConnectionConfig data() const; +signals: + +private: + QUuid m_uuid; + + QFormLayout *formLayout; + QLabel *lblName; + QLineEdit *edtName; + QLabel *lblHost; + QLineEdit *edtHost; + QLabel *lblPort; + QSpinBox *spinPort; + QLabel *lblUser; + QLineEdit *edtUser; + QLabel *lblDbName; + QLineEdit *edtDbname; + QLabel *lblSsl; + QComboBox *cmbbxSsl; + QLabel *lblCert; + QLineEdit *edtCert; + QLabel *lblKey; + QLineEdit *edtKey; + QLabel *lblRootCert; + QLineEdit *edtRootCert; + QLabel *lblCrl; + QLineEdit *edtCrl; + +public slots: +}; + + + +#endif // CONNECTIONCONFIGURATIONWIDGET_H diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp new file mode 100644 index 0000000..013e3cc --- /dev/null +++ b/pglab/ConnectionController.cpp @@ -0,0 +1,216 @@ +#include "ConnectionController.h" +#include "MasterController.h" +#include "ConnectionManagerWindow.h" +#include "ConnectionListModel.h" +#include "PasswordManager.h" +#include "DatabaseWindow.h" +#include "ServerWindow.h" +#include "BackupDialog.h" +#include "PasswordPromptDialog.h" +#include "ConnectionConfigurationWidget.h" + + +ConnectionController::ConnectionController(MasterController *parent) + : QObject(parent) + , m_masterController(parent) +{} + +ConnectionController::~ConnectionController() +{ + delete m_connectionManagerWindow; + delete m_connectionListModel; +} + +void ConnectionController::init() +{ + //std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data(); + //m_userConfigDatabase = std::make_shared(dbfilename); + + + m_passwordManager = std::make_shared(); + + m_connectionListModel = new ConnectionListModel(this); + m_connectionListModel->load(); + + m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); + m_connectionManagerWindow->show(); + +} + +void ConnectionController::showConnectionManager() +{ + m_connectionManagerWindow->show(); +} + +void ConnectionController::openSqlWindowForConnection(int connection_index) +{ + + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + + if (retrieveConnectionPassword(cc)) { + m_connectionListModel->save(cc); + // TODO instead of directly openening the mainwindow + // do async connect and only open window when we have + // working connection + auto w = new DatabaseWindow(m_masterController, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc); + w->showMaximized(); + } + } + +} + +void ConnectionController::openBackupDlgForConnection(int connection_index) +{ + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + if (retrieveConnectionPassword(cc)) { + m_connectionListModel->save(cc); + auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc); + w->show(); + } + } +} + +void ConnectionController::createConnection() +{ + ConnectionConfig cc; + cc.setUuid(QUuid::createUuid()); + ConnectionConfigurationWidget::editExistingInWindow(this, cc); +} + +void ConnectionController::editConnection(int connection_index) +{ + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + ConnectionConfigurationWidget::editExistingInWindow(this, cc); + } +} + +void ConnectionController::openServerWindowForConnection(int connection_index) +{ + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + if (retrieveConnectionPassword(cc)) { + m_connectionListModel->save(cc); + auto w = new ServerWindow(m_masterController, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc); + w->show(); + } + } +} + + +bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) +{ + auto pw_state = cc.passwordState(); + if (pw_state == PasswordState::NotNeeded) { + return true; + } + else if (pw_state == PasswordState::SavedPasswordManager) { + std::string pw; + bool result = getPasswordFromPskdb(getPskId(cc), pw); + if (result) { + cc.setPassword(pw); + return true; + } + } + // Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord + // ook aan de gebruiker vragen zoals hier gebeurd. + QString str = ConnectionListModel::makeLongDescription(cc); + auto dlg = std::make_unique(PasswordPromptDialog::SaveOption, nullptr); + dlg->setCaption(tr("Connection password prompt")); + dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); + int exec_result = dlg->exec(); + + if (exec_result == QDialog::Accepted) { + std::string password = dlg->password().toUtf8().data(); + cc.setPassword(password); + if (dlg->saveChecked()) { + storePasswordInPskdb(getPskId(cc), password); + cc.setPasswordState(PasswordState::SavedPasswordManager); + } + return true; + } + return false; +} + + +bool ConnectionController::getPasswordFromPskdb(const std::string &password_id, std::string &password) +{ + if (!UnlockPasswordManagerIfNeeded()) + return false; + + return m_passwordManager->get(password_id, password); +} + + +bool ConnectionController::storePasswordInPskdb(const std::string &password_id, const std::string password) +{ + if (!UnlockPasswordManagerIfNeeded()) + return false; + + m_passwordManager->set(password_id, password); + return true; +} + +bool ConnectionController::UnlockPasswordManagerIfNeeded() +{ + auto&& user_cfg_db = m_masterController->userConfigDatabase(); + if (m_passwordManager->initialized(user_cfg_db)) { + if (!m_passwordManager->locked()) + return true; + + while (true) { + // ask user for passphrase + auto dlg = std::make_unique(nullptr, nullptr); + dlg->setCaption(tr("Unlock password manager")); + dlg->setDescription(tr("Enter password for password manager")); + int exec_result = dlg->exec(); + bool ok = (exec_result == QDialog::Accepted); + + if (!ok) { + // leave this retry loop + break; + } + // user gave OK, if succeeds return true otherwise loop a prompt for password again. + if (m_passwordManager->openDatabase(user_cfg_db, dlg->password())) + return true; + } + } + else { + // Ask user for passphrase + confirmation, clearly instruct this is first setup + // create + auto dlg = std::make_unique(PasswordPromptDialog::ConfirmPassword, nullptr); + dlg->setCaption(tr("Password manager setup")); + dlg->setDescription(tr("Enter a strong password for password manager initialization. A strong key will be " + "derived from your password and it will be impossible to recover anything from the " + "password manager without the password you enter here.")); + int exec_result = dlg->exec(); + if (exec_result == QDialog::Accepted) { + QString passphrase = dlg->password(); + if (m_passwordManager->createDatabase(user_cfg_db, passphrase)) + return true; + } + } + return false; +} + +std::string ConnectionController::getPskId(const ConnectionConfig &cc) +{ + std::string id = "dbpw/"; + id += cc.uuid().toString().toUtf8().data(); + return id; +} + + + diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h new file mode 100644 index 0000000..a5c8f11 --- /dev/null +++ b/pglab/ConnectionController.h @@ -0,0 +1,63 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include + +class MasterController; +class ConnectionConfig; +class ConnectionList; +class ConnectionListModel; +class ConnectionManagerWindow; +class PasswordManager; + +class ConnectionController : public QObject { + Q_OBJECT +public: + explicit ConnectionController(MasterController *parent = nullptr); + ~ConnectionController(); + + void init(); + + ConnectionListModel *getConnectionListModel() + { + return m_connectionListModel; + } + + void showConnectionManager(); + void openSqlWindowForConnection(int connection_index); + void openServerWindowForConnection(int connection_index); + void openBackupDlgForConnection(int connection_index); + + /// Starts the form for creating a new conncetion. + /// This function returns immidiatly! + void createConnection(); + /// Starts the form for editing a conncetion. + /// This function returns immidiatly! + void editConnection(int connection_index); +private: + MasterController *m_masterController; + ConnectionList *m_connectionList = nullptr; + ConnectionListModel *m_connectionListModel = nullptr; + ConnectionManagerWindow *m_connectionManagerWindow = nullptr; + + /** Using long lived object so it can remember its master password for sometime + * if the user wishes it. + */ + std::shared_ptr m_passwordManager; + + /** Retrieves the connection password from the user (directly or through the psk db) + * + */ + bool retrieveConnectionPassword(ConnectionConfig &cc); + + bool getPasswordFromPskdb(const std::string &password_id, std::string &password); + + bool storePasswordInPskdb(const std::string &password_id, const std::string password); + + bool UnlockPasswordManagerIfNeeded(); + + static std::string getPskId(const ConnectionConfig &cc); +}; + + +#endif // CONNECTIONCONTROLLER_H diff --git a/pglab/ConnectionList.cpp b/pglab/ConnectionList.cpp deleted file mode 100644 index a6c8a01..0000000 --- a/pglab/ConnectionList.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "ConnectionList.h" -#include "ScopeGuard.h" -#include "util.h" -#include "PasswordManager.h" -#include -#include -#include - - -namespace { - - /** Saves a connection configuration. - - Before calling this you may want to call beginGroup. - */ - void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) - { - settings.setValue("name", stdStrToQ(cc.name())); - settings.setValue("host", stdStrToQ(cc.host())); - settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); - settings.setValue("port", cc.port()); - settings.setValue("user", stdStrToQ(cc.user())); - //settings.setValue("password", stdStrToQ(cc.password())); - settings.setValue("dbname", stdStrToQ(cc.dbname())); - settings.setValue("sslmode", static_cast(cc.sslMode())); - settings.setValue("sslcert", stdStrToQ(cc.sslCert())); - settings.setValue("sslkey", stdStrToQ(cc.sslKey())); - settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); - settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); - settings.setValue("passwordState", static_cast(cc.passwordState())); - } - - template - bool in_range(T value) - { - return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); - } - - void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) - { - cc.setName(qvarToStdStr(settings.value("name"))); - cc.setHost(qvarToStdStr(settings.value("host"))); - cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); - int p = settings.value("port", 5432).toInt(); - if (!in_range(p)) { - p = 0; // let the user re-enter a valid value - } - - cc.setPort(static_cast(p)); - cc.setUser(qvarToStdStr(settings.value("user"))); - - //cc.setPassword(qvarToStdStr(settings.value("password"))); - - cc.setDbname(qvarToStdStr(settings.value("dbname"))); - cc.setSslMode(static_cast(settings.value("sslmode").toInt())); - cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); - cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); - cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); - cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); - - PasswordState pwstate; - QVariant v = settings.value("passwordState"); - if (v.isNull()) pwstate = PasswordState::NotStored; - else pwstate = static_cast(v.toInt()); - cc.setPasswordState(pwstate); - } - - - -} // end of unnamed namespace - -/// \todo should return an expected as creation of the folder can fail -QString ConnectionList::iniFileName() -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - path += "/connections.ini"; - return path; -} - - -ConnectionList::ConnectionList() = default; - -size_t ConnectionList::createNew() -{ - ConnectionConfig cc; - cc.setUuid(QUuid::createUuid()); - m_connections.push_back(cc); - return m_connections.size()-1; -} - -void ConnectionList::remove(size_t idx, size_t count) -{ - auto f = m_connections.begin() + static_cast(idx); - auto l = f + static_cast(count); - deleteFromIni(f, l); - m_connections.erase(f, l); - - // remove from password save -} - -void ConnectionList::deleteFromIni(const t_Connections::iterator &begin, const t_Connections::iterator &end) -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (auto i = begin; i != end; ++i) { - settings.remove(i->uuid().toString()); - } -} - -void ConnectionList::load() -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - auto groups = settings.childGroups(); - for (auto&& grp : groups) { - if (grp == "c_IniGroupSecurity") { - // Read security settings - - } else { - QUuid uuid(grp); - if ( ! uuid.isNull() ) { - settings.beginGroup(grp); - SCOPE_EXIT { settings.endGroup(); }; - - ConnectionConfig cc; - cc.setUuid(uuid); - LoadConnectionConfig(settings, cc); - m_connections.push_back(cc); - } - } - } -} - -void ConnectionList::save() -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (auto& e : m_connections) { - settings.beginGroup(e.uuid().toString()); - SCOPE_EXIT { settings.endGroup(); }; - - SaveConnectionConfig(settings, e); - e.clean(); - } - settings.sync(); -} - -void ConnectionList::save(size_t index) -{ - auto& e = m_connections.at(index); - if (e.dirty()) { - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - settings.beginGroup(e.uuid().toString()); - SaveConnectionConfig(settings, e); - e.clean(); - settings.sync(); - } -} diff --git a/pglab/ConnectionList.h b/pglab/ConnectionList.h deleted file mode 100644 index 2cd8a9c..0000000 --- a/pglab/ConnectionList.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CONNECTIONLIST_H -#define CONNECTIONLIST_H - -#include "ConnectionConfig.h" - -#include -#include -#include -#include "Expected.h" - -class ConnectionList { -public: - - ConnectionList(); - size_t size() const { return m_connections.size(); } - - ConnectionConfig& getConfigByIdx(size_t idx) - { - return m_connections.at(idx); - } - - void setConfigByIdx(size_t idx, const ConnectionConfig &cc) - { - m_connections[idx] = cc; - } - - size_t createNew(); - - void remove(size_t idx, size_t count); - - void load(); - void save(); - void save(size_t index); - -private: -// class LijstElem { -// public: -// QUuid m_uuid; ///< Unique identifier, used as a key for storing password in psk db. -// ConnectionConfig m_config; - -// LijstElem(const QUuid id, const ConnectionConfig &cfg) -// : m_uuid(id), m_config(cfg) -// {} -// }; - - using t_Connections = std::vector; - t_Connections m_connections; - - void deleteFromIni(const t_Connections::iterator &begin, const t_Connections::iterator &end); - - static QString iniFileName(); -}; - -#endif // CONNECTIONLIST_H diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 7297db7..29914c0 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -1,14 +1,78 @@ #include "ConnectionListModel.h" -#include "ConnectionList.h" #include "ScopeGuard.h" #include "util.h" #include +#include +#include +#include -ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent) +namespace { + + /** Saves a connection configuration. + + Before calling this you may want to call beginGroup. + */ + void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) + { + settings.setValue("name", stdStrToQ(cc.name())); + settings.setValue("host", stdStrToQ(cc.host())); + settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); + settings.setValue("port", cc.port()); + settings.setValue("user", stdStrToQ(cc.user())); + //settings.setValue("password", stdStrToQ(cc.password())); + settings.setValue("dbname", stdStrToQ(cc.dbname())); + settings.setValue("sslmode", static_cast(cc.sslMode())); + settings.setValue("sslcert", stdStrToQ(cc.sslCert())); + settings.setValue("sslkey", stdStrToQ(cc.sslKey())); + settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); + settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); + settings.setValue("passwordState", static_cast(cc.passwordState())); + } + + template + bool in_range(T value) + { + return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); + } + + void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) + { + cc.setName(qvarToStdStr(settings.value("name"))); + cc.setHost(qvarToStdStr(settings.value("host"))); + cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); + int p = settings.value("port", 5432).toInt(); + if (!in_range(p)) { + p = 0; // let the user re-enter a valid value + } + + cc.setPort(static_cast(p)); + cc.setUser(qvarToStdStr(settings.value("user"))); + + //cc.setPassword(qvarToStdStr(settings.value("password"))); + + cc.setDbname(qvarToStdStr(settings.value("dbname"))); + cc.setSslMode(static_cast(settings.value("sslmode").toInt())); + cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); + cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); + cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); + cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); + + PasswordState pwstate; + QVariant v = settings.value("passwordState"); + if (v.isNull()) pwstate = PasswordState::NotStored; + else pwstate = static_cast(v.toInt()); + cc.setPasswordState(pwstate); + } + + + +} // end of unnamed namespace + + +ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) - , m_connections(conns) { } @@ -18,7 +82,7 @@ int ConnectionListModel::rowCount(const QModelIndex &parent) const { int result = 0; if (parent == QModelIndex()) { - result = m_connections->size(); + result = m_connections.size(); } return result; } @@ -34,7 +98,7 @@ QVariant ConnectionListModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole || role == Qt::EditRole) { int row = index.row(); int col = index.column(); - const ConnectionConfig& cfg = m_connections->getConfigByIdx(row); + const ConnectionConfig& cfg = m_connections.at(row); switch (col) { case Description: result = makeLongDescription(cfg); @@ -72,7 +136,7 @@ bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &valu // auto& elem = m_connections.at(row); // elem.m_dirty = true; // ConnectionConfig& cfg = elem.m_config; - ConnectionConfig& cfg = m_connections->getConfigByIdx(row); + ConnectionConfig& cfg = m_connections[row]; if (col > 0) { result = true; } @@ -109,7 +173,7 @@ Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const { Qt::ItemFlags result; int row = index.row(); - if (row >= 0 && row < (int)m_connections->size()) { + if (row >= 0 && row < m_connections.size()) { result = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (index.column() != Description) result |= Qt::ItemIsEditable; @@ -133,75 +197,122 @@ QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg) return stdStrToQ(result); } -void ConnectionListModel::newItem() -{ - int i = m_connections->createNew(); - auto idx = createIndex(i, 0); - emit dataChanged(idx, idx); -} - -Expected ConnectionListModel::get(size_t row) -{ - if (row < m_connections->size()) { - return m_connections->getConfigByIdx(row); - } - return Expected::fromException(std::out_of_range("Invalid row")); -} - - -#include - -template -size_t as_size_t(T t); - -template <> -size_t as_size_t(int t) -{ - BOOST_ASSERT(t >= 0); - return static_cast(t); -} - -template <> -size_t as_size_t(long t) -{ - BOOST_ASSERT(t >= 0); - return static_cast(t); -} - - -//void ConnectionListModel::del(const int idx) bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) { bool result = false; - if (row >= 0 && row < (int)m_connections->size()) { + + if (row >= 0 && row < m_connections.size()) { beginRemoveRows(parent, row, row + count -1); SCOPE_EXIT { endRemoveRows(); }; - m_connections->remove(as_size_t(row), as_size_t(count)); + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + for (int idx = 0; idx < count; ++idx) { + auto&& cc = m_connections[idx+row]; + { + settings.beginGroup(cc.uuid().toString()); + SCOPE_EXIT { settings.endGroup(); }; + for (auto&& k : settings.childKeys()) { + settings.remove(k); + } + } + } + m_connections.remove(row, count); result = true; } return result; } +void ConnectionListModel::newItem() +{ +// int i = m_connections->createNew(); +// auto idx = createIndex(i, 0); +// emit dataChanged(idx, idx); +} -//void ConnectionListModel::load() -//{ -// m_connections->load(); -//} +Expected ConnectionListModel::get(int row) +{ + if (row < m_connections.size()) { + return m_connections.at(row); + } + return Expected::fromException(std::out_of_range("Invalid row")); +} + +void ConnectionListModel::load() +{ + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + auto groups = settings.childGroups(); + for (auto&& grp : groups) { + if (grp == "c_IniGroupSecurity") { + // Read security settings + + } else { + QUuid uuid(grp); + if ( ! uuid.isNull() ) { + settings.beginGroup(grp); + SCOPE_EXIT { settings.endGroup(); }; + + ConnectionConfig cc; + cc.setUuid(uuid); + LoadConnectionConfig(settings, cc); + m_connections.push_back(cc); + } + } + } +} void ConnectionListModel::save() { - m_connections->save(); + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + for (auto& e : m_connections) { + settings.beginGroup(e.uuid().toString()); + SCOPE_EXIT { settings.endGroup(); }; + + SaveConnectionConfig(settings, e); + e.clean(); + } + settings.sync(); } -void ConnectionListModel::save(size_t index) +void ConnectionListModel::save(int index) { - m_connections->save(index); + auto& e = m_connections[index]; + if (e.dirty()) { + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + settings.beginGroup(e.uuid().toString()); + SaveConnectionConfig(settings, e); + e.clean(); + settings.sync(); + } } -void ConnectionListModel::save(size_t index, const ConnectionConfig &cc) +void ConnectionListModel::save(const ConnectionConfig &cc) { - m_connections->setConfigByIdx(index, cc); - m_connections->save(index); + auto find_res = std::find(m_connections.begin(), m_connections.end(), cc.uuid()); + int i; + if (find_res == m_connections.end()) { + m_connections.push_back(cc); + i = m_connections.size() - 1; + } + else { + *find_res = cc; + i = find_res - m_connections.begin(); + } + emit dataChanged(createIndex(i, 0), createIndex(i, ColCount-1)); + save(i); +} + +QString ConnectionListModel::iniFileName() +{ + QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir dir(path); + if (!dir.exists()) { + dir.mkpath("."); + } + path += "/connections.ini"; + return path; } diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index be61202..611bb04 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -8,8 +8,8 @@ #include "ConnectionConfig.h" #include "Expected.h" +#include -class ConnectionList; /** \brief Model class for the list of connections. * @@ -30,30 +30,44 @@ public: ColCount }; - ConnectionListModel(ConnectionList *conns, QObject *parent); + ConnectionListModel(QObject *parent); ConnectionListModel(const ConnectionListModel&) = delete; ~ConnectionListModel() override; + + // BEGIN Model/View related functions virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &/*parent*/) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + // END Model/View related functions void newItem(); - Expected get(size_t row); - virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + Expected get(int row); + void load(); + // Writes all entries to storage void save(); - void save(size_t index); - void save(size_t index, const ConnectionConfig &cc); + // Writes the specified entry to storage + void save(int index); +// // Save changes to the config +// void save(size_t index, const ConnectionConfig &cc); + /** Matches cc to the list by looking at its uuid. + * + * If it is not in the list it is added. If the uuid is in the list that entry is updated. + * In both cases the data is also directly written to long term storage. + */ + void save(const ConnectionConfig &cc); static QString makeLongDescription(const ConnectionConfig &cfg); private: - ConnectionList *m_connections; - + using ConnectionList = QVector; + ConnectionList m_connections; + QString iniFileName(); }; #endif // CONNECTIONLISTMODEL_H diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 7c053e0..fae38d3 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -1,8 +1,7 @@ #include "ConnectionManagerWindow.h" #include "ui_ConnectionManagerWindow.h" -//#include "mainwindow.h" #include "MasterController.h" -#include +#include "ConnectionController.h" #include #include #include "ConnectionListModel.h" @@ -30,43 +29,17 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg { ui->setupUi(this); ui->listView->setModel(m_connectionController->getConnectionListModel()); - - setupWidgetMappings(); - - connect(ui->listView->selectionModel(), - SIGNAL(currentChanged(QModelIndex,QModelIndex)), - this, SLOT(on_currentChanged(QModelIndex,QModelIndex))); } ConnectionManagerWindow::~ConnectionManagerWindow() { delete ui; - delete m_mapper; } void ConnectionManagerWindow::on_actionAdd_Connection_triggered() { - auto clm = m_connectionController->getConnectionListModel(); - clm->newItem(); - - // Select the new row - auto idx = clm->index(clm->rowCount() - 1, 0); - ui->listView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Select); -} - -void ConnectionManagerWindow::on_currentChanged(const QModelIndex ¤t, - const QModelIndex &) -{ - int currow = current.row(); - auto clm = m_connectionController->getConnectionListModel(); - if (prevSelection) - clm->save(*prevSelection); - m_mapper->setCurrentIndex(currow); - if (currow >= 0) - prevSelection = static_cast(currow); - else - prevSelection.reset(); + m_connectionController->createConnection(); } void ConnectionManagerWindow::on_actionDelete_connection_triggered() @@ -82,33 +55,18 @@ void ConnectionManagerWindow::on_actionDelete_connection_triggered() } } - -void ConnectionManagerWindow::setupWidgetMappings() -{ - auto clm = m_connectionController->getConnectionListModel(); - m_mapper = new QDataWidgetMapper(this); - m_mapper->setModel(clm); - m_mapper->addMapping(ui->edtName, 1); - m_mapper->addMapping(ui->edtHost, 2); - m_mapper->addMapping(ui->spinPort, 3); - m_mapper->addMapping(ui->edtUser, 4); - m_mapper->addMapping(ui->edtDbname, 6); - m_mapper->toFirst(); -} - void ConnectionManagerWindow::on_actionConnect_triggered() { auto ci = ui->listView->selectionModel()->currentIndex(); if (ci.isValid()) { - auto r = static_cast(ci.row()); - m_connectionController->openSqlWindowForConnection(r); + m_connectionController->openSqlWindowForConnection(ci.row()); } } void ConnectionManagerWindow::on_actionQuit_application_triggered() { auto res = QMessageBox::question(this, "pglab", - tr("Close ALL windows?"), QMessageBox::Yes, QMessageBox::No); + tr("Close all windows?"), QMessageBox::Yes, QMessageBox::No); if (res == QMessageBox::Yes) { QApplication::quit(); } @@ -129,7 +87,12 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() void ConnectionManagerWindow::on_listView_activated(const QModelIndex &index) { if (index.isValid()) { - auto r = static_cast(index.row()); - m_connectionController->openSqlWindowForConnection(r); + m_connectionController->openSqlWindowForConnection(index.row()); } } + +void ConnectionManagerWindow::on_actionConfigure_connection_triggered() +{ + auto ci = ui->listView->selectionModel()->currentIndex(); + m_connectionController->editConnection(ci.row()); +} diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index 85638fc..54e1d1e 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -11,7 +11,6 @@ class ConnectionManagerWindow; class ConnectionConfig; class ConnectionController; class MasterController; -class QDataWidgetMapper; class QStandardItemModel; /** \brief Class that holds glue code for the ConnectionManager UI. @@ -25,7 +24,6 @@ public: private slots: void on_actionAdd_Connection_triggered(); - void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void on_actionDelete_connection_triggered(); void on_actionConnect_triggered(); void on_actionQuit_application_triggered(); @@ -34,15 +32,12 @@ private slots: void on_listView_activated(const QModelIndex &index); + void on_actionConfigure_connection_triggered(); + private: Ui::ConnectionManagerWindow *ui; - QDataWidgetMapper *m_mapper = nullptr; MasterController *m_masterController; ConnectionController *m_connectionController; - - std::optional prevSelection; - - void setupWidgetMappings(); }; #endif // CONNECTIONMANAGERWINDOW_H diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index 1fae730..41fc0f6 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -6,7 +6,7 @@ 0 0 - 800 + 413 600 @@ -28,148 +28,6 @@ true - - - - - - Name - - - - - - - - - - Host - - - - - - - - - - Port - - - - - - - 65535 - - - - - - - Username - - - - - - - - - - Database - - - - - - - - - - SSL - - - - - - - 2 - - - - reject - - - - - allow - - - - - prefer - - - - - require - - - - - verify-ca - - - - - verify-full - - - - - - - - Certificate - - - - - - - - - - Key - - - - - - - - - - Root cert. - - - - - - - - - - Revocation list - - - - - - - - @@ -179,7 +37,7 @@ 0 0 - 800 + 413 20 @@ -224,6 +82,7 @@ QToolButton { + @@ -285,6 +144,16 @@ QToolButton { Manage server + + + + :/icons/server_configuration.png + + + + Configure connection + + diff --git a/pglab/DatabaseWindow.cpp b/pglab/DatabaseWindow.cpp index 2e24156..4467836 100644 --- a/pglab/DatabaseWindow.cpp +++ b/pglab/DatabaseWindow.cpp @@ -3,6 +3,7 @@ #include "CrudTab.h" #include "widgets/CatalogTablesPage.h" #include "OpenDatabase.h" +#include "ConnectionController.h" #include "MasterController.h" #include "TaskExecutor.h" #include diff --git a/pglab/MasterController.cpp b/pglab/MasterController.cpp index df2fd88..a851e9f 100644 --- a/pglab/MasterController.cpp +++ b/pglab/MasterController.cpp @@ -1,16 +1,8 @@ #include "MasterController.h" -#include "ConnectionManagerWindow.h" -#include "ConnectionList.h" -#include "ConnectionListModel.h" -#include "PasswordManager.h" -#include "DatabaseWindow.h" -#include "ServerWindow.h" -#include "BackupDialog.h" -#include "PasswordPromptDialog.h" +#include "ConnectionController.h" #include #include #include -#include namespace { @@ -60,193 +52,3 @@ QSqlDatabase& MasterController::userConfigDatabase() } -ConnectionController::ConnectionController(MasterController *parent) - : QObject(parent) - , m_masterController(parent) -{} - -ConnectionController::~ConnectionController() -{ - delete m_connectionManagerWindow; - delete m_connectionListModel; - delete m_connectionList; -} - -void ConnectionController::init() -{ - //std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data(); - //m_userConfigDatabase = std::make_shared(dbfilename); - - - m_passwordManager = std::make_shared(); - - m_connectionList = new ConnectionList; - m_connectionList->load(); - m_connectionListModel = new ConnectionListModel(m_connectionList, this); - - m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); - m_connectionManagerWindow->show(); - -} - -void ConnectionController::showConnectionManager() -{ - m_connectionManagerWindow->show(); -} - -void ConnectionController::openSqlWindowForConnection(size_t connection_index) -{ - - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(connection_index, cc); - // TODO instead of directly openening the mainwindow - // do async connect and only open window when we have - // working connection - auto w = new DatabaseWindow(m_masterController, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); - w->showMaximized(); - } - } - -} - -void ConnectionController::openBackupDlgForConnection(size_t connection_index) -{ - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(connection_index, cc); - auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); - w->show(); - } - } -} - -void ConnectionController::openServerWindowForConnection(size_t connection_index) -{ - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(connection_index, cc); - auto w = new ServerWindow(m_masterController, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); - w->show(); - } - } -} - - -bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) -{ - auto pw_state = cc.passwordState(); - if (pw_state == PasswordState::NotNeeded) { - return true; - } - else if (pw_state == PasswordState::SavedPasswordManager) { - std::string pw; - bool result = getPasswordFromPskdb(getPskId(cc), pw); - if (result) { - cc.setPassword(pw); - return true; - } - } - // Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord - // ook aan de gebruiker vragen zoals hier gebeurd. - QString str = ConnectionListModel::makeLongDescription(cc); - auto dlg = std::make_unique(PasswordPromptDialog::SaveOption, nullptr); - dlg->setCaption(tr("Connection password prompt")); - dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); - int exec_result = dlg->exec(); - - if (exec_result == QDialog::Accepted) { - std::string password = dlg->password().toUtf8().data(); - cc.setPassword(password); - if (dlg->saveChecked()) { - storePasswordInPskdb(getPskId(cc), password); - cc.setPasswordState(PasswordState::SavedPasswordManager); - } - return true; - } - return false; -} - - -bool ConnectionController::getPasswordFromPskdb(const std::string &password_id, std::string &password) -{ - if (!UnlockPasswordManagerIfNeeded()) - return false; - - return m_passwordManager->get(password_id, password); -} - - -bool ConnectionController::storePasswordInPskdb(const std::string &password_id, const std::string password) -{ - if (!UnlockPasswordManagerIfNeeded()) - return false; - - m_passwordManager->set(password_id, password); - return true; -} - -bool ConnectionController::UnlockPasswordManagerIfNeeded() -{ - auto&& user_cfg_db = m_masterController->userConfigDatabase(); - if (m_passwordManager->initialized(user_cfg_db)) { - if (!m_passwordManager->locked()) - return true; - - while (true) { - // ask user for passphrase - auto dlg = std::make_unique(nullptr, nullptr); - dlg->setCaption(tr("Unlock password manager")); - dlg->setDescription(tr("Enter password for password manager")); - int exec_result = dlg->exec(); - bool ok = (exec_result == QDialog::Accepted); - - if (!ok) { - // leave this retry loop - break; - } - // user gave OK, if succeeds return true otherwise loop a prompt for password again. - if (m_passwordManager->openDatabase(user_cfg_db, dlg->password())) - return true; - } - } - else { - // Ask user for passphrase + confirmation, clearly instruct this is first setup - // create - auto dlg = std::make_unique(PasswordPromptDialog::ConfirmPassword, nullptr); - dlg->setCaption(tr("Password manager setup")); - dlg->setDescription(tr("Enter a strong password for password manager initialization. A strong key will be " - "derived from your password and it will be impossible to recover anything from the " - "password manager without the password you enter here.")); - int exec_result = dlg->exec(); - if (exec_result == QDialog::Accepted) { - QString passphrase = dlg->password(); - if (m_passwordManager->createDatabase(user_cfg_db, passphrase)) - return true; - } - } - return false; -} - -std::string ConnectionController::getPskId(const ConnectionConfig &cc) -{ - std::string id = "dbpw/"; - id += cc.uuid().toString().toUtf8().data(); - return id; -} - - - diff --git a/pglab/MasterController.h b/pglab/MasterController.h index 888d783..824976a 100644 --- a/pglab/MasterController.h +++ b/pglab/MasterController.h @@ -8,15 +8,6 @@ #include #include -//namespace Botan { -// class Sqlite3_Database; -//} - -class ConnectionConfig; -class ConnectionList; -class ConnectionListModel; -class ConnectionManagerWindow; -class PasswordManager; class ConnectionController; /** \brief Controller class responsible for all things global. @@ -43,47 +34,5 @@ private: ConnectionController* m_connectionController = nullptr; }; -class ConnectionController : public QObject { - Q_OBJECT -public: - explicit ConnectionController(MasterController *parent = nullptr); - ~ConnectionController(); - - void init(); - - ConnectionListModel *getConnectionListModel() - { - return m_connectionListModel; - } - - void showConnectionManager(); - void openSqlWindowForConnection(size_t connection_index); - void openServerWindowForConnection(size_t connection_index); - void openBackupDlgForConnection(size_t connection_index); - -private: - MasterController *m_masterController; - ConnectionList *m_connectionList = nullptr; - ConnectionListModel *m_connectionListModel = nullptr; - ConnectionManagerWindow *m_connectionManagerWindow = nullptr; - - /** Using long lived object so it can remember its master password for sometime - * if the user wishes it. - */ - std::shared_ptr m_passwordManager; - - /** Retrieves the connection password from the user (directly or through the psk db) - * - */ - bool retrieveConnectionPassword(ConnectionConfig &cc); - - bool getPasswordFromPskdb(const std::string &password_id, std::string &password); - - bool storePasswordInPskdb(const std::string &password_id, const std::string password); - - bool UnlockPasswordManagerIfNeeded(); - - static std::string getPskId(const ConnectionConfig &cc); -}; #endif // MASTERCONTROLLER_H diff --git a/pglab/SslModeModel.cpp b/pglab/SslModeModel.cpp new file mode 100644 index 0000000..9694d2f --- /dev/null +++ b/pglab/SslModeModel.cpp @@ -0,0 +1,69 @@ +#include "SslModeModel.h" + +SslModeModel::SslModeModel(QObject *parent) + : QAbstractListModel(parent) +{} + +int SslModeModel::rowCount(const QModelIndex &) const +{ + return 6; +} + +int SslModeModel::columnCount(const QModelIndex &) const +{ + return ColCount; +} + +QVariant SslModeModel::data(const QModelIndex &index, int role) const +{ + QVariant v; + if (role == Qt::DisplayRole) { + switch (index.column()) { + case Name: + switch(index.row()) { + case 0: + v = tr("disable"); + break; + case 1: + v = tr("allow"); + break; + case 2: + v = tr("prefer"); + break; + case 3: + v = tr("require"); + break; + case 4: + v = tr("verify_ca"); + break; + case 5: + v = tr("verify_full"); + break; + } + break; + case Description: + switch(index.row()) { + case 0: + v = tr("try a non encrypted connection only"); + break; + case 1: + v = tr("try no encryption first then try encrypted"); + break; + case 2: + v = tr("try encrypted first then not encrypted"); + break; + case 3: + v = tr("require an encrypted connection"); + break; + case 4: + v = tr("verify encryption certificate has a valid signature"); + break; + case 5: + v = tr("verify encryption certificate has a valid signature and matches the host"); + break; + } + break; + } + } + return v; +} diff --git a/pglab/SslModeModel.h b/pglab/SslModeModel.h new file mode 100644 index 0000000..687f1ef --- /dev/null +++ b/pglab/SslModeModel.h @@ -0,0 +1,20 @@ +#ifndef SSLMODEMODEL_H +#define SSLMODEMODEL_H + +#include + +class SslModeModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Column { Name, Description, ColCount }; + + SslModeModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +}; + +#endif // SSLMODEMODEL_H diff --git a/pglab/icons/server_configuration.png b/pglab/icons/server_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf14aea005460d689baf7aeb38ce09f5e3872c0 GIT binary patch literal 2506 zcmaJ@c|25mA0Eq~qHCLOO_pQ0qL}U4W@I0Ot5FzHQO3-fC1x>m8e7@2rc%AdSmLIQ zG9_L+kqC8@k|^DhHi;&cTomunm?LD8*IltxeJkNJ~{y3SNd^|LkBA3EoFbywH zlAnA97GE_L`BxMh`$4{}g4}~3e?Ak6q6tBm3xgj90$yAi3-kkNjF`x7a6JsBgyK+x zpdiWyBAw4g(-tx4Xs$rchQZc5MGI*3a1a8*Ko*DRfRZ(~pa2fT0TpOX!BPZnAe-YE zD+K*xeW>)#H?jE;U&Q7M05^YYG!6*l z@EH6k(HA_0LiFN^AR3PjdXXGZas@Pp!yw|_2`)IC9oE*y)dq)i$Ged61hSpAn~Rkj z&f3P7^o>j6(|2(}9`udN_=k)8A$QRVT!B0?2^4a~AcHLAbAc}<6FEQ3#n%0YdSAJW zALe39{vj75PX@C%*#8>zw=21O7PsGRD?fZUKFE{1T`0FT`q`Kk45s4fMRK7=KPV3j z-xLs_JKsZ?Hp(y^F}5}|GtEfO55}o#Sh`C!tyyc&UNih$W*$zap1G5vWwm<)VK8V* zU$&kH_up2%60c{^6OmmA7r42|`E4YkP9UqFF6?_SXHb+sEu8wp;$z{B?Q`4ah8}MX zH&h78HrgdSVVrFC%g%fiem0=iOMN~-kzL&Cb&>bijM}UjcXqb*$zfv;lhln%Pp4Y5 zw-+k=I!_%V-W{;jqkJBf+K#U2cnni7PMCV{ix@e7_+On@*;dX|NeYKo3hE!0Htp6& z-PPfQKlXW-o2gJdK`1qEk1Hr=i_^Wsr>+dHw<(^%CDa^r8dj-{KUd-`F6&x%G4-bc z*%>7u{(&+!p=SA(EGGrEwV!n=I*CPlX*; z{#e&MkV&b${ns zOKxWz+}Ae$*-7toeNx&%D6ElYUiVaBq@R>qS%TAm-F})bY}u|LQC+Sl0WY<;QkqLX zg~6wOHXfH+*7=2`R#xI2%bJ^;4+Y%1U@?5G!c8f80JgqfHu)Oi3~T-=dw=2nrY6O; zJZ*IqB_tAgs0Mr=HrwJYE3Dn}{++UOC_1N)p>=Zao_nx@Tc2X#;S85mOOA@Wqxkr| z(bDLaQsb&uL@^dc%x=ADce+}e>E1f9%*)3MG5tDZiM2Mfv9J4O1x#XIl-AK&U=Wf) zwe433eTi{zx)Bq%lKD(>1&WD`^Q(*#*Xa^P_KHf1fL7fVfHrp2vwW!Hk}uEzS5cmL zmOIq9SoK9b)9!(unS}+J_r_ej z_lUZF=FOh2t10)wr%|k5nxhr1!qD{IY)NIL#zL{R@iNKr#E8z&h~0bU$LuQ=ii>JT za?Y#gIIB+RcJ=g-v6rp{9SXsZT^K+dw~M!smX(z?_@}53_HXHL*eXpI!#9k+wg`Z< zbZ>hObqCBi(!|tzOXl7qzdbygX{^?%sG- zglV`*qlamhe$cpD(5y-OO_Q#&nCDt%||rkf_W}Pm{B%NF&pI9TLYFMZ=d{ONgE0eJm|Qd&G^(`QhRBMTSXx zb9s~d-zF}ri(4?@1*5xbx!a~r6iuV~kKtDx2P&U;a(*~RPW%}hw-A;f}!zJso8b@!|E40`SE+lFW2(nV&$!I+q`{0c7Gx~S_=dy z*qI^H4&=iN)F19VMyPWg7N_ehP;(&F+L?FNCZmSB^sY3BO2%YI_P!{jn|rhJ-_MIH zKe`W3lvb};ahG-ZedLbTnCFTz82k0+v&~zwdWDUrHXy6o?nUS@I#Oq^R}rQf@0H8e z)wLMvF!5q8vIDK#&=t|}pt)$KPc3mq0trs`0DS5EZli@Yt*{A?*fj_D!Gjn7uDslR KNEci~lm7#qLMdJV literal 0 HcmV?d00001 diff --git a/pglab/pglab.pro b/pglab/pglab.pro index 3d0e3f9..ed2bb43 100644 --- a/pglab/pglab.pro +++ b/pglab/pglab.pro @@ -21,6 +21,8 @@ DEFINES += _WIN32_WINNT=0x0501 win32:RC_ICONS += pglab.ico SOURCES += main.cpp\ + ConnectionConfigurationWidget.cpp \ + ConnectionController.cpp \ NotificationListWidget.cpp \ NotificationModel.cpp \ NotificationService.cpp \ @@ -30,6 +32,7 @@ SOURCES += main.cpp\ CreateDatabaseDialog.cpp \ ConnectionManagerWindow.cpp \ ConnectionListModel.cpp \ + SslModeModel.cpp \ stopwatch.cpp \ TuplesResultWidget.cpp \ BackupDialog.cpp \ @@ -40,7 +43,6 @@ SOURCES += main.cpp\ ServerWindow.cpp \ DatabasesTableModel.cpp \ RolesTableModel.cpp \ - ConnectionList.cpp \ ProcessStdioWidget.cpp \ GlobalIoService.cpp \ ResultTableModelUtil.cpp \ @@ -85,6 +87,8 @@ PropertyProxyModel.cpp \ widgets/CatalogSequencesPage.cpp HEADERS += \ + ConnectionConfigurationWidget.h \ + ConnectionController.h \ IDatabaseWindow.h \ NotificationListWidget.h \ NotificationModel.h \ @@ -95,6 +99,7 @@ HEADERS += \ CreateDatabaseDialog.h \ ConnectionManagerWindow.h \ ConnectionListModel.h \ + SslModeModel.h \ stopwatch.h \ TuplesResultWidget.h \ BackupDialog.h \ @@ -105,7 +110,6 @@ HEADERS += \ ServerWindow.h \ DatabasesTableModel.h \ RolesTableModel.h \ - ConnectionList.h \ ProcessStdioWidget.h \ GlobalIoService.h \ ResultTableModelUtil.h \ diff --git a/pglab/resources.qrc b/pglab/resources.qrc index 476a271..689fb61 100644 --- a/pglab/resources.qrc +++ b/pglab/resources.qrc @@ -28,5 +28,6 @@ icons/constraints/primarykey.png icons/constraints/unique.png icons/constraints/index.png + icons/server_configuration.png diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index 1652bcf..a58d69a 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -26,7 +26,7 @@ class QString; class ConnectionConfig { public: - ConnectionConfig(); + ConnectionConfig(); // Default object containing invalid uuid void setUuid(const QUuid &uuid); const QUuid &uuid() const; @@ -79,6 +79,8 @@ public: bool dirty() const; void clean(); + + bool operator==(QUuid id) const { return m_uuid == id; } private: QUuid m_uuid; std::string m_name; From b65edaa6ee444dd8292ca0b1f887dbd18ecbc571 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 25 Aug 2019 08:00:54 +0200 Subject: [PATCH 02/12] Improved how connection is removed from the configuration file. --- pglab/ConnectionListModel.cpp | 21 ++++++++------------- pglab/ConnectionListModel.h | 4 ---- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 29914c0..b14b652 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -210,26 +210,21 @@ bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &pare QSettings settings(file_name, QSettings::IniFormat); for (int idx = 0; idx < count; ++idx) { auto&& cc = m_connections[idx+row]; - { - settings.beginGroup(cc.uuid().toString()); - SCOPE_EXIT { settings.endGroup(); }; - for (auto&& k : settings.childKeys()) { - settings.remove(k); - } - } + settings.remove(cc.uuid().toString()); } + settings.sync(); m_connections.remove(row, count); result = true; } return result; } -void ConnectionListModel::newItem() -{ -// int i = m_connections->createNew(); -// auto idx = createIndex(i, 0); -// emit dataChanged(idx, idx); -} +//void ConnectionListModel::newItem() +//{ +//// int i = m_connections->createNew(); +//// auto idx = createIndex(i, 0); +//// emit dataChanged(idx, idx); +//} Expected ConnectionListModel::get(int row) { diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 611bb04..3496464 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -34,7 +34,6 @@ public: ConnectionListModel(const ConnectionListModel&) = delete; ~ConnectionListModel() override; - // BEGIN Model/View related functions virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &/*parent*/) const override; @@ -45,7 +44,6 @@ public: virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; // END Model/View related functions - void newItem(); Expected get(int row); void load(); @@ -53,8 +51,6 @@ public: void save(); // Writes the specified entry to storage void save(int index); -// // Save changes to the config -// void save(size_t index, const ConnectionConfig &cc); /** Matches cc to the list by looking at its uuid. * * If it is not in the list it is added. If the uuid is in the list that entry is updated. From 3721808df4b15f8343f0cea7906d0f3e431845b0 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 25 Aug 2019 08:10:37 +0200 Subject: [PATCH 03/12] Remove unused function --- pglab/ConnectionManagerWindow.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index fae38d3..3fe7019 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -10,17 +10,6 @@ #include #include -QString pskFileName() -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - path += "/psk.ini"; - return path; -} - ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidget *parent) : QMainWindow(parent) , ui(new Ui::ConnectionManagerWindow) From 8840d3bcbb6608c978b520dc31f0fc9b6d9467cb Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 25 Aug 2019 15:33:51 +0200 Subject: [PATCH 04/12] Basic version of ConnectionTreeModel is working. --- pglab/ConnectionController.cpp | 3 + pglab/ConnectionController.h | 7 + pglab/ConnectionListModel.cpp | 244 +++++++++++++++++++++++++++++- pglab/ConnectionListModel.h | 42 +++++ pglab/ConnectionManagerWindow.cpp | 1 + pglab/ConnectionManagerWindow.ui | 3 + pglablib/ConnectionConfig.cpp | 16 ++ pglablib/ConnectionConfig.h | 35 ++++- 8 files changed, 348 insertions(+), 3 deletions(-) diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index 013e3cc..281eeba 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -18,6 +18,7 @@ ConnectionController::ConnectionController(MasterController *parent) ConnectionController::~ConnectionController() { delete m_connectionManagerWindow; + delete m_connectionTreeModel; delete m_connectionListModel; } @@ -31,6 +32,8 @@ void ConnectionController::init() m_connectionListModel = new ConnectionListModel(this); m_connectionListModel->load(); + m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase()); + m_connectionTreeModel->load(); m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); m_connectionManagerWindow->show(); diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index a5c8f11..06c3633 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -7,6 +7,7 @@ class MasterController; class ConnectionConfig; class ConnectionList; class ConnectionListModel; +class ConnectionTreeModel; class ConnectionManagerWindow; class PasswordManager; @@ -23,6 +24,11 @@ public: return m_connectionListModel; } + ConnectionTreeModel *getConnectionTreeModel() + { + return m_connectionTreeModel; + } + void showConnectionManager(); void openSqlWindowForConnection(int connection_index); void openServerWindowForConnection(int connection_index); @@ -38,6 +44,7 @@ private: MasterController *m_masterController; ConnectionList *m_connectionList = nullptr; ConnectionListModel *m_connectionListModel = nullptr; + ConnectionTreeModel *m_connectionTreeModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; /** Using long lived object so it can remember its master password for sometime diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index b14b652..24eaca5 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -4,12 +4,89 @@ #include #include -#include #include +#include +#include +#include +#include namespace { + const char * const q_create_table_conngroup = +R"__( +CREATE TABLE IF NOT EXISTS conngroup ( + conngroup_id INTEGER PRIMARY KEY, + gname TEXT NOT NULL +);)__"; + + const char * const q_create_table_connection = +R"__( +CREATE TABLE IF NOT EXISTS connection ( + uuid TEXT PRIMARY KEY, + cname TEXT NOT NULL, + conngroup_id INTEGER NOT NULL, + host TEXT NOT NULL, + hostaddr TEXT NOT NULL, + port INTEGER NOT NULL, + user TEXT NOT NULL, + dbname TEXT NOT NULL, + sslmode INTEGER NOT NULL, + sslcert TEXT NOT NULL, + sslkey TEXT NOT NULL, + sslrootcert TEXT NOT NULL, + sslcrl TEXT NOT NULL, + passwordstate INTEGER NOT NULL +);)__"; + + const char * const q_insert_or_replace_into_connection = +R"__(INSERT OR REPLACE INTO connection + VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname, + :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :passwordstate); +)__" ; + + std::tuple InitConnectionTables(QSqlDatabase &db) + { + QSqlQuery q_create_table(db); + q_create_table.prepare(q_create_table_conngroup); + if (!q_create_table.exec()) { + auto err = q_create_table.lastError(); + return { false, err }; + } + q_create_table.prepare(q_create_table_connection); + if (!q_create_table.exec()) { + auto err = q_create_table.lastError(); + return { false, err }; + } + return {true, {}}; + } + + std::tuple SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) + { + QSqlQuery q(db); + q.prepare(q_insert_or_replace_into_connection); + q.bindValue(":uuid", cc.uuid().toString()); + q.bindValue(":name", stdStrToQ(cc.name())); + q.bindValue(":conngroup_id", conngroup_id); + q.bindValue(":host", stdStrToQ(cc.host())); + q.bindValue(":hostaddr", stdStrToQ(cc.hostAddr())); + q.bindValue(":port", cc.port()); + q.bindValue(":user", stdStrToQ(cc.user())); + q.bindValue(":dbname", stdStrToQ(cc.dbname())); + q.bindValue(":sslmode", static_cast(cc.sslMode())); + q.bindValue(":sslcert", stdStrToQ(cc.sslCert())); + q.bindValue(":sslkey", stdStrToQ(cc.sslKey())); + q.bindValue(":sslrootcert", stdStrToQ(cc.sslRootCert())); + q.bindValue(":sslcrl", stdStrToQ(cc.sslCrl())); + q.bindValue(":passwordstate", static_cast(cc.passwordState())); + + if (!q.exec()) { + auto err = q.lastError(); + return { false, err }; + } + return {true, {}}; + } + /** Saves a connection configuration. Before calling this you may want to call beginGroup. @@ -31,6 +108,8 @@ namespace { settings.setValue("passwordState", static_cast(cc.passwordState())); } + + template bool in_range(T value) { @@ -67,10 +146,10 @@ namespace { } - } // end of unnamed namespace + ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) { @@ -311,3 +390,164 @@ QString ConnectionListModel::iniFileName() path += "/connections.ini"; return path; } + + +ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) + : QAbstractItemModel(parent) + , m_db(db) +{ +} + +void ConnectionTreeModel::load() +{ + InitConnectionTables(m_db); + + auto g1 = std::make_shared(); + g1->name = "Testing"; + + for (int i = 1; i < 3; ++i) { + auto cc = std::make_shared(); + cc->setUuid(QUuid::createUuid()); + cc->setName("testconn " + std::to_string(i)); + g1->add(cc); + } + + auto g2 = std::make_shared(); + g2->name = "Production"; + for (int i = 1; i < 4; ++i) { + auto cc = std::make_shared(); + cc->setUuid(QUuid::createUuid()); + cc->setName("prodconn " + std::to_string(i)); + g2->add(cc); + } + + m_groups = { g1, g2 }; +} + +QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const +{ + // Code below assumes two level tree groups/connections + // it will fail for nested groups + QVariant v; + + auto privdata = static_cast(index.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + // This is a group + if (role == Qt::DisplayRole) { + if (index.column() == Name) { + v = group->name; + } + } + } + else if (auto conn = dynamic_cast(privdata); conn != nullptr) { + // This is a connection + if (role == Qt::DisplayRole) { + switch (index.column()) { + case Name: v = stdStrToQ(conn->name()); break; + case Host: v = stdStrToQ(conn->host()); break; + case Port: v = conn->port(); break; + case User: v = stdStrToQ(conn->user()); break; + case DbName: v= stdStrToQ(conn->dbname()); break; + } + } + } + + return v; +} + +QVariant ConnectionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant v; + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + switch (section) { + case Name: v = tr("Name"); break; + case Host: v = tr("Host"); break; + case Port: v = tr("Port"); break; + case User: v = tr("User"); break; + case DbName: v= tr("Database"); break; + } + } + } + return v; +} + +QModelIndex ConnectionTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return {}; + + const ConnectionNode *node = nullptr; + if (parent.isValid()) { + auto privdata = static_cast(parent.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + node = group->connections().at(row).get(); + } + else { + throw std::logic_error("Should never ask for a child index of a connectionconfig"); + } + } + else { + node = m_groups[row].get(); + } + return createIndex(row, column, const_cast(node)); + +// void *p = nullptr; +// if (parent.isValid()) { +// auto cg = static_cast(parent.internalPointer()); +// auto cc = &cg->connections().at(row); +// p = const_cast(cc); +// } +// else { +// p = const_cast(&m_groups.at(row)); +// } +// return createIndex(row, column, p); +} + +QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + auto privdata = static_cast(index.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + return {}; + } + else if (auto config = dynamic_cast(privdata); config != nullptr) { + auto p = config->parent(); + auto find_res = std::find_if(m_groups.begin(), m_groups.end(), [p] (auto item) -> bool { return *p == *item; }); + if (find_res != m_groups.end()) { + return createIndex(find_res - m_groups.begin(), 0, config->parent()); + } + } + throw std::logic_error("Should never get here"); +} + +int ConnectionTreeModel::rowCount(const QModelIndex &parent) const +{ + int result = 0; + if (parent.isValid()) { +// if (parent.column() <= 0) { +// result = m_groups[parent.row()].connections().size(); +// } +// else { +// result = 1; +// } + auto privdata = static_cast(parent.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + result = group->connections().size(); + } + else if (auto config = dynamic_cast(privdata); config != nullptr) { + result = 0; + } + } + else { + result = m_groups.size(); + } + return result; +} + +int ConnectionTreeModel::columnCount(const QModelIndex &parent) const +{ + return ColCount; +} diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 3496464..b053cc9 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -10,6 +10,48 @@ #include "Expected.h" #include +//#include +class QSqlDatabase; + +class ConnectionTreeModel : public QAbstractItemModel { + Q_OBJECT +public: + enum Columns { + Name, + Host, + Port, + User, + DbName, + + ColCount + }; + + ConnectionTreeModel(QObject *parent, QSqlDatabase &db); + + void load(); + + QVariant data(const QModelIndex &index, int role) const override; + +// Qt::ItemFlags flags(const QModelIndex &index) const override; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + +private: + using Groups = QVector>; + + QSqlDatabase &m_db; + Groups m_groups; +}; /** \brief Model class for the list of connections. * diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 3fe7019..ec7210e 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -18,6 +18,7 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg { ui->setupUi(this); ui->listView->setModel(m_connectionController->getConnectionListModel()); + ui->treeView->setModel(m_connectionController->getConnectionTreeModel()); } diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index 41fc0f6..cca5094 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -30,6 +30,9 @@ + + + diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index 1dd47b7..f2ba553 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -57,6 +57,16 @@ ConnectionConfig::ConnectionConfig() : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) {} +ConnectionGroup *ConnectionConfig::parent() +{ + return m_group; +} + +void ConnectionConfig::setParent(ConnectionGroup *grp) +{ + m_group = grp; +} + void ConnectionConfig::setUuid(const QUuid &uuid) { @@ -347,3 +357,9 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co else env.insert(var, stdStrToQ(val)); } + +void ConnectionGroup::add(std::shared_ptr cc) +{ + cc->setParent(this); + m_connections.push_back(cc); +} diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index a58d69a..20e9109 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -2,6 +2,7 @@ #define CONNECTION_H #include +#include #include #include @@ -24,10 +25,41 @@ enum class PasswordState { class QProcessEnvironment; class QString; -class ConnectionConfig { +class ConnectionConfig; + +/** Base class for ConnectionGroup and ConnectionConfig + * to enable the use of RTTI in the tree model class. + */ +class ConnectionNode { +public: + virtual ~ConnectionNode() = default; +}; + +class ConnectionGroup: public ConnectionNode { +public: + int conngroup_id; + QString name; + + using Connections = QVector>; + const Connections& connections() const { return m_connections; } + + void add(std::shared_ptr cc); + + bool operator==(const ConnectionGroup &rhs) const { + return conngroup_id == rhs.conngroup_id + && name == rhs.name; + } +private: + Connections m_connections; +}; + +class ConnectionConfig: public ConnectionNode { public: ConnectionConfig(); // Default object containing invalid uuid + ConnectionGroup* parent(); + void setParent(ConnectionGroup *grp); + void setUuid(const QUuid &uuid); const QUuid &uuid() const; @@ -102,6 +134,7 @@ private: PasswordState m_passwordState = PasswordState::NotStored; bool m_dirty = false; + ConnectionGroup* m_group; static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val); From b3a98f6dc0509fca9b181f0e009ba72517206170 Mon Sep 17 00:00:00 2001 From: eelke Date: Tue, 27 Aug 2019 20:12:00 +0200 Subject: [PATCH 05/12] Most of functionality for connections in tree works now. Old list largely removed. --- pglab/ConnectionConfigurationWidget.cpp | 27 +- pglab/ConnectionConfigurationWidget.h | 10 +- pglab/ConnectionController.cpp | 67 ++--- pglab/ConnectionController.h | 15 +- pglab/ConnectionListModel.cpp | 317 +++++++++++++++++++----- pglab/ConnectionListModel.h | 28 ++- pglab/ConnectionManagerWindow.cpp | 37 +-- pglab/ConnectionManagerWindow.ui | 15 -- pglablib/ConnectionConfig.cpp | 32 ++- pglablib/ConnectionConfig.h | 9 +- 10 files changed, 399 insertions(+), 158 deletions(-) diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp index 81fa6b6..0e656d1 100644 --- a/pglab/ConnectionConfigurationWidget.cpp +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -19,7 +19,7 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg) { - auto w = new ConnectionConfigurationWidget; + auto w = new ConnectionConfigurationWidget(ctrl->getConnectionTreeModel()); w->setData(cfg); @@ -35,8 +35,8 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c win->setLayout(vbox); win->connect(btn_hbox, &QDialogButtonBox::accepted, [ctrl, w, win] () { - auto cc = w->data(); - ctrl->getConnectionListModel()->save(cc); + auto [grp, cc] = w->data(); + ctrl->getConnectionTreeModel()->save(grp, cc); win->accept(); }); win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] () { win->reject(); }); @@ -49,9 +49,17 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c win->show(); } -ConnectionConfigurationWidget::ConnectionConfigurationWidget(QWidget *parent) +ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent) : QWidget(parent) { + lblGroup = new QLabel; + cmbbxGroup = new QComboBox; + cmbbxGroup->setModel(connection_model); + cmbbxGroup->setModelColumn(0); +// cmbbxGroup->setEditable(true); +// cmbbxGroup->setInsertPolicy(QComboBox::NoInsert); + lblGroup->setBuddy(cmbbxGroup); + lblName = new QLabel; SET_OBJECT_NAME(lblName); edtName = new QLineEdit ; @@ -118,7 +126,7 @@ ConnectionConfigurationWidget::ConnectionConfigurationWidget(QWidget *parent) formLayout = new QFormLayout; - setLayout(formLayout); + formLayout->addRow(lblGroup, cmbbxGroup); formLayout->addRow(lblName, edtName); formLayout->addRow(lblHost, edtHost); formLayout->addRow(lblPort, spinPort); @@ -129,12 +137,14 @@ ConnectionConfigurationWidget::ConnectionConfigurationWidget(QWidget *parent) formLayout->addRow(lblKey, edtKey); formLayout->addRow(lblRootCert, edtRootCert); formLayout->addRow(lblCrl, edtCrl); + setLayout(formLayout); retranslateUi(); } void ConnectionConfigurationWidget::retranslateUi() { + lblGroup->setText(QApplication::translate("ConnectionConfigurationWidget", "&Group", nullptr)); lblName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Name", nullptr)); lblHost->setText(QApplication::translate("ConnectionConfigurationWidget", "&Host", nullptr)); lblPort->setText(QApplication::translate("ConnectionConfigurationWidget", "&Port", nullptr)); @@ -162,8 +172,11 @@ void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) edtCrl->setText(stdStrToQ(cfg.sslCrl())); } -ConnectionConfig ConnectionConfigurationWidget::data() const +std::tuple ConnectionConfigurationWidget::data() const { + QString group; + group = cmbbxGroup->currentText(); + ConnectionConfig cfg; cfg.setUuid(m_uuid); cfg.setName(qStrToStd(edtName->text())); @@ -176,6 +189,6 @@ ConnectionConfig ConnectionConfigurationWidget::data() const cfg.setSslKey(qStrToStd(edtKey->text())); cfg.setSslRootCert(qStrToStd(edtRootCert->text())); cfg.setSslCrl(qStrToStd(edtCrl->text())); - return cfg; + return { group, cfg }; } diff --git a/pglab/ConnectionConfigurationWidget.h b/pglab/ConnectionConfigurationWidget.h index 4d4eadf..21849c1 100644 --- a/pglab/ConnectionConfigurationWidget.h +++ b/pglab/ConnectionConfigurationWidget.h @@ -3,6 +3,7 @@ #include #include +#include class ConnectionController; class ConnectionConfig; @@ -13,6 +14,7 @@ class QSpinBox; class QComboBox; class QDataWidgetMapper; +class ConnectionTreeModel; class ConnectionConfigurationWidget : public QWidget { @@ -20,17 +22,18 @@ class ConnectionConfigurationWidget : public QWidget public: static void editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg); - explicit ConnectionConfigurationWidget(QWidget *parent = nullptr); + explicit ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent = nullptr); void retranslateUi(); void setData(const ConnectionConfig &cfg); - ConnectionConfig data() const; + std::tuple data() const; signals: private: QUuid m_uuid; - QFormLayout *formLayout; + QLabel *lblGroup; + QComboBox *cmbbxGroup; QLabel *lblName; QLineEdit *edtName; QLabel *lblHost; @@ -51,6 +54,7 @@ private: QLineEdit *edtRootCert; QLabel *lblCrl; QLineEdit *edtCrl; + QFormLayout *formLayout; public slots: }; diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index 281eeba..f4890f9 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -19,7 +19,6 @@ ConnectionController::~ConnectionController() { delete m_connectionManagerWindow; delete m_connectionTreeModel; - delete m_connectionListModel; } void ConnectionController::init() @@ -30,8 +29,6 @@ void ConnectionController::init() m_passwordManager = std::make_shared(); - m_connectionListModel = new ConnectionListModel(this); - m_connectionListModel->load(); m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase()); m_connectionTreeModel->load(); @@ -45,37 +42,47 @@ void ConnectionController::showConnectionManager() m_connectionManagerWindow->show(); } -void ConnectionController::openSqlWindowForConnection(int connection_index) +namespace { + + ConnectionConfig* getConfigFromModelIndex(QModelIndex index) + { + if (!index.isValid()) + return nullptr; + auto node = static_cast(index.internalPointer()); + return dynamic_cast(node); + } + +} + + +void ConnectionController::openSqlWindowForConnection(QModelIndex index) { + auto config = getConfigFromModelIndex(index); + if (config) { - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(cc); + if (retrieveConnectionPassword(*config)) { + m_connectionTreeModel->save(*config); // TODO instead of directly openening the mainwindow // do async connect and only open window when we have // working connection auto w = new DatabaseWindow(m_masterController, nullptr); w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); + w->setConfig(*config); w->showMaximized(); } } } -void ConnectionController::openBackupDlgForConnection(int connection_index) +void ConnectionController::openBackupDlgForConnection(QModelIndex index) { - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(cc); + auto config = getConfigFromModelIndex(index); + if (config) { + if (retrieveConnectionPassword(*config)) { + m_connectionTreeModel->save(*config); auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr); w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); + w->setConfig(*config); w->show(); } } @@ -88,25 +95,23 @@ void ConnectionController::createConnection() ConnectionConfigurationWidget::editExistingInWindow(this, cc); } -void ConnectionController::editConnection(int connection_index) +void ConnectionController::editConnection(QModelIndex index) { - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - ConnectionConfigurationWidget::editExistingInWindow(this, cc); + auto config = getConfigFromModelIndex(index); + if (config) { + ConnectionConfigurationWidget::editExistingInWindow(this, *config); } } -void ConnectionController::openServerWindowForConnection(int connection_index) +void ConnectionController::openServerWindowForConnection(QModelIndex index) { - auto res = m_connectionListModel->get(connection_index); - if (res.valid()) { - auto cc = res.get(); - if (retrieveConnectionPassword(cc)) { - m_connectionListModel->save(cc); + auto config = getConfigFromModelIndex(index); + if (config) { + if (retrieveConnectionPassword(*config)) { + m_connectionTreeModel->save(*config); auto w = new ServerWindow(m_masterController, nullptr); w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); + w->setConfig(*config); w->show(); } } @@ -129,7 +134,7 @@ bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc) } // Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord // ook aan de gebruiker vragen zoals hier gebeurd. - QString str = ConnectionListModel::makeLongDescription(cc); + QString str = cc.makeLongDescription(); auto dlg = std::make_unique(PasswordPromptDialog::SaveOption, nullptr); dlg->setCaption(tr("Connection password prompt")); dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index 06c3633..b8056bf 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -6,7 +6,6 @@ class MasterController; class ConnectionConfig; class ConnectionList; -class ConnectionListModel; class ConnectionTreeModel; class ConnectionManagerWindow; class PasswordManager; @@ -19,31 +18,25 @@ public: void init(); - ConnectionListModel *getConnectionListModel() - { - return m_connectionListModel; - } - ConnectionTreeModel *getConnectionTreeModel() { return m_connectionTreeModel; } void showConnectionManager(); - void openSqlWindowForConnection(int connection_index); - void openServerWindowForConnection(int connection_index); - void openBackupDlgForConnection(int connection_index); + void openSqlWindowForConnection(QModelIndex index); + void openServerWindowForConnection(QModelIndex index); + void openBackupDlgForConnection(QModelIndex index); /// Starts the form for creating a new conncetion. /// This function returns immidiatly! void createConnection(); /// Starts the form for editing a conncetion. /// This function returns immidiatly! - void editConnection(int connection_index); + void editConnection(QModelIndex index); private: MasterController *m_masterController; ConnectionList *m_connectionList = nullptr; - ConnectionListModel *m_connectionListModel = nullptr; ConnectionTreeModel *m_connectionTreeModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 24eaca5..1cb67c1 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -17,7 +17,7 @@ namespace { R"__( CREATE TABLE IF NOT EXISTS conngroup ( conngroup_id INTEGER PRIMARY KEY, - gname TEXT NOT NULL + gname TEXT NOT NULL UNIQUE );)__"; const char * const q_create_table_connection = @@ -39,6 +39,8 @@ CREATE TABLE IF NOT EXISTS connection ( passwordstate INTEGER NOT NULL );)__"; + + const char * const q_insert_or_replace_into_connection = R"__(INSERT OR REPLACE INTO connection VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname, @@ -61,7 +63,7 @@ R"__(INSERT OR REPLACE INTO connection return {true, {}}; } - std::tuple SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) + std::optional SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) { QSqlQuery q(db); q.prepare(q_insert_or_replace_into_connection); @@ -81,75 +83,74 @@ R"__(INSERT OR REPLACE INTO connection q.bindValue(":passwordstate", static_cast(cc.passwordState())); if (!q.exec()) { - auto err = q.lastError(); - return { false, err }; + return q.lastError(); } - return {true, {}}; + return {}; } /** Saves a connection configuration. Before calling this you may want to call beginGroup. */ - void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) - { - settings.setValue("name", stdStrToQ(cc.name())); - settings.setValue("host", stdStrToQ(cc.host())); - settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); - settings.setValue("port", cc.port()); - settings.setValue("user", stdStrToQ(cc.user())); - //settings.setValue("password", stdStrToQ(cc.password())); - settings.setValue("dbname", stdStrToQ(cc.dbname())); - settings.setValue("sslmode", static_cast(cc.sslMode())); - settings.setValue("sslcert", stdStrToQ(cc.sslCert())); - settings.setValue("sslkey", stdStrToQ(cc.sslKey())); - settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); - settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); - settings.setValue("passwordState", static_cast(cc.passwordState())); - } +// void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) +// { +// settings.setValue("name", stdStrToQ(cc.name())); +// settings.setValue("host", stdStrToQ(cc.host())); +// settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); +// settings.setValue("port", cc.port()); +// settings.setValue("user", stdStrToQ(cc.user())); +// //settings.setValue("password", stdStrToQ(cc.password())); +// settings.setValue("dbname", stdStrToQ(cc.dbname())); +// settings.setValue("sslmode", static_cast(cc.sslMode())); +// settings.setValue("sslcert", stdStrToQ(cc.sslCert())); +// settings.setValue("sslkey", stdStrToQ(cc.sslKey())); +// settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); +// settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); +// settings.setValue("passwordState", static_cast(cc.passwordState())); +// } - template - bool in_range(T value) - { - return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); - } +// template +// bool in_range(T value) +// { +// return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); +// } - void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) - { - cc.setName(qvarToStdStr(settings.value("name"))); - cc.setHost(qvarToStdStr(settings.value("host"))); - cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); - int p = settings.value("port", 5432).toInt(); - if (!in_range(p)) { - p = 0; // let the user re-enter a valid value - } +// void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) +// { +// cc.setName(qvarToStdStr(settings.value("name"))); +// cc.setHost(qvarToStdStr(settings.value("host"))); +// cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); +// int p = settings.value("port", 5432).toInt(); +// if (!in_range(p)) { +// p = 0; // let the user re-enter a valid value +// } - cc.setPort(static_cast(p)); - cc.setUser(qvarToStdStr(settings.value("user"))); +// cc.setPort(static_cast(p)); +// cc.setUser(qvarToStdStr(settings.value("user"))); - //cc.setPassword(qvarToStdStr(settings.value("password"))); +// //cc.setPassword(qvarToStdStr(settings.value("password"))); - cc.setDbname(qvarToStdStr(settings.value("dbname"))); - cc.setSslMode(static_cast(settings.value("sslmode").toInt())); - cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); - cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); - cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); - cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); +// cc.setDbname(qvarToStdStr(settings.value("dbname"))); +// cc.setSslMode(static_cast(settings.value("sslmode").toInt())); +// cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); +// cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); +// cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); +// cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); - PasswordState pwstate; - QVariant v = settings.value("passwordState"); - if (v.isNull()) pwstate = PasswordState::NotStored; - else pwstate = static_cast(v.toInt()); - cc.setPasswordState(pwstate); - } +// PasswordState pwstate; +// QVariant v = settings.value("passwordState"); +// if (v.isNull()) pwstate = PasswordState::NotStored; +// else pwstate = static_cast(v.toInt()); +// cc.setPasswordState(pwstate); +// } } // end of unnamed namespace - +#if false ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) { @@ -390,7 +391,7 @@ QString ConnectionListModel::iniFileName() path += "/connections.ini"; return path; } - +#endif ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) : QAbstractItemModel(parent) @@ -402,26 +403,79 @@ void ConnectionTreeModel::load() { InitConnectionTables(m_db); - auto g1 = std::make_shared(); - g1->name = "Testing"; + QSqlQuery q(m_db); + q.prepare("SELECT conngroup_id, gname FROM conngroup;"); + if (!q.exec()) { +// auto err = q_create_table.lastError(); +// return { false, err }; + throw std::runtime_error("Loading groups failed"); + } + while (q.next()) { + int id = q.value(0).toInt(); + QString name = q.value(1).toString(); - for (int i = 1; i < 3; ++i) { - auto cc = std::make_shared(); - cc->setUuid(QUuid::createUuid()); - cc->setName("testconn " + std::to_string(i)); - g1->add(cc); + auto g = std::make_shared(); + g->conngroup_id = id; + g->name = name; + m_groups.push_back(g); } - auto g2 = std::make_shared(); - g2->name = "Production"; - for (int i = 1; i < 4; ++i) { + q.prepare("SELECT uuid, cname, conngroup_id, host, hostaddr, port, " + " user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, " + " passwordstate " + "FROM connection ORDER BY conngroup_id, cname;"); + if (!q.exec()) { +// auto err = q_create_table.lastError(); +// return { false, err }; + throw std::runtime_error("Loading groups failed"); + } + while (q.next()) { auto cc = std::make_shared(); - cc->setUuid(QUuid::createUuid()); - cc->setName("prodconn " + std::to_string(i)); - g2->add(cc); + cc->setUuid(q.value(0).toUuid()); + cc->setName(qvarToStdStr(q.value(1))); + cc->setHost(qvarToStdStr(q.value(3))); + cc->setHostAddr(qvarToStdStr(q.value(4))); + cc->setPort(static_cast(q.value(5).toInt())); + cc->setUser(qvarToStdStr(q.value(6))); + cc->setDbname(qvarToStdStr(q.value(7))); + cc->setSslMode(static_cast(q.value(8).toInt())); + cc->setSslCert(qvarToStdStr(q.value(9))); + cc->setSslKey(qvarToStdStr(q.value(10))); + cc->setSslRootCert(qvarToStdStr(q.value(11))); + cc->setSslCrl(qvarToStdStr(q.value(12))); + cc->setPasswordState(static_cast(q.value(13).toInt())); + + int group_id = q.value(2).toInt(); + auto find_res = std::find_if(m_groups.begin(), m_groups.end(), + [group_id] (auto item) { return item->conngroup_id == group_id; }); + if (find_res != m_groups.end()) { + (*find_res)->add(cc); + } + else { + throw std::runtime_error("conngroup missing"); + } } - m_groups = { g1, g2 }; +// auto g1 = std::make_shared(); +// g1->name = "Testing"; + +// for (int i = 1; i < 3; ++i) { +// auto cc = std::make_shared(); +// cc->setUuid(QUuid::createUuid()); +// cc->setName("testconn " + std::to_string(i)); +// g1->add(cc); +// } + +// auto g2 = std::make_shared(); +// g2->name = "Production"; +// for (int i = 1; i < 4; ++i) { +// auto cc = std::make_shared(); +// cc->setUuid(QUuid::createUuid()); +// cc->setName("prodconn " + std::to_string(i)); +// g2->add(cc); +// } + +// m_groups = { g1, g2 }; } QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const @@ -517,7 +571,8 @@ QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const auto p = config->parent(); auto find_res = std::find_if(m_groups.begin(), m_groups.end(), [p] (auto item) -> bool { return *p == *item; }); if (find_res != m_groups.end()) { - return createIndex(find_res - m_groups.begin(), 0, config->parent()); + return createIndex(find_res - m_groups.begin(), 0, + const_cast(config->parent())); } } throw std::logic_error("Should never get here"); @@ -547,7 +602,133 @@ int ConnectionTreeModel::rowCount(const QModelIndex &parent) const return result; } -int ConnectionTreeModel::columnCount(const QModelIndex &parent) const +int ConnectionTreeModel::columnCount(const QModelIndex &) const { return ColCount; } + +bool ConnectionTreeModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (parent.isValid() && count == 1) { + // should be a group + auto grp = m_groups[parent.row()]; + for (int i = 0; i < count; ++i) { + QUuid uuid = grp->connections().at(row + i)->uuid(); + QSqlQuery q(m_db); + q.prepare( + "DELETE FROM connection " + " WHERE uuid=:uuid"); + q.bindValue(":uuid", uuid); + if (!q.exec()) { + auto err = q.lastError(); + throw std::runtime_error("QqlError"); + } + } + beginRemoveRows(parent, row, row + count - 1); + SCOPE_EXIT { endRemoveRows(); }; + grp->erase(row, count); + } +} + +void ConnectionTreeModel::save(const QString &group_name, const ConnectionConfig &cc) +{ + auto [grp_idx, conn_idx] = findConfig(cc.uuid()); + if (grp_idx >= 0) { + auto grp = m_groups[grp_idx]; + if (grp->name == group_name) { + // update config + grp->update(conn_idx, cc); + // send change event + auto node = grp->connections().at(conn_idx); + dataChanged( + createIndex(conn_idx, 0, node.get()), + createIndex(conn_idx, ColCount-1, node.get())); + saveToDb(*node); + return; + } + else { + auto parent = createIndex(grp_idx, 0, grp.get()); + beginRemoveRows(parent, conn_idx, conn_idx); + SCOPE_EXIT { endRemoveRows(); }; + grp->erase(conn_idx); + } + } + // Here we can assume we have to find the new group or create a new group + // because if the connection was in the right group the function has already returned. + // We assume the model is in sync with the DB as the DB should not be shared! + int new_grp_idx = findGroup(group_name); + if (new_grp_idx < 0) { + // Group not found we are g + auto add_grp_res = addGroup(group_name); + if (std::holds_alternative(add_grp_res)) { + new_grp_idx = std::get(add_grp_res); + } + else { + throw std::runtime_error("SqlError1"); + } + } + auto new_grp = m_groups[new_grp_idx]; + + auto parent = createIndex(new_grp_idx, 0, new_grp.get()); + auto idx = new_grp->connections().size(); + beginInsertRows(parent, idx, idx); + SCOPE_EXIT { endInsertRows(); }; + auto node = std::make_shared(cc); + int node_idx = new_grp->add(node); +// dataChanged( +// createIndex(node_idx, 0, node.get()), +// createIndex(node_idx, ColCount-1, node.get())); + auto save_res = saveToDb(*node); + if (save_res) { + throw std::runtime_error("SqlError2"); + } +} + +void ConnectionTreeModel::save(const ConnectionConfig &cc) +{ + saveToDb(cc); +} + +std::tuple ConnectionTreeModel::findConfig(const QUuid uuid) const +{ + int group_idx = -1, connection_idx = -1; + + for (int grp_idx = 0; grp_idx < m_groups.size(); ++grp_idx) { + auto && grp = m_groups[grp_idx]; + auto && conns = grp->connections(); + auto find_res = std::find_if(conns.begin(), conns.end(), + [&uuid] (auto item) -> bool { return item->uuid() == uuid; }); + if (find_res != conns.end()) { + group_idx = grp_idx; + connection_idx = find_res - conns.begin(); + break; + } + } + + return { group_idx, connection_idx }; +} + +int ConnectionTreeModel::findGroup(QString name) const +{ + for (int idx = 0; idx < m_groups.size(); ++idx) { + if (m_groups[idx]->name == name) return idx; + } + return -1; +} + +std::variant ConnectionTreeModel::addGroup(QString group_name) +{ + QSqlQuery q(m_db); + q.prepare("INSERT INTO conngroup (gname) VALUES (:name)"); + q.bindValue(":name", group_name); + if (!q.exec()) { + auto err = q.lastError(); + return { err }; + } + return q.lastInsertId().toInt(); +} + +std::optional ConnectionTreeModel::saveToDb(const ConnectionConfig &cc) +{ + return SaveConnectionConfig(m_db, cc, cc.parent()->conngroup_id); +} diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index b053cc9..1a56d0d 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -8,9 +8,11 @@ #include "ConnectionConfig.h" #include "Expected.h" +#include +#include #include -//#include +#include class QSqlDatabase; class ConnectionTreeModel : public QAbstractItemModel { @@ -46,13 +48,36 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + /** Matches cc to the list by looking at its uuid. + * + * If it is not in the list it is added. If the uuid is in the list that entry is updated. + * If the group has been changed it is moved to the right group. + * In both cases the data is also directly written to long term storage. + */ + void save(const QString &group, const ConnectionConfig &cc); + /** Save changed config, group is not allowed to change + */ + void save(const ConnectionConfig &cc); private: using Groups = QVector>; QSqlDatabase &m_db; Groups m_groups; + + /// Finds the connection with the specified uuid and returns + /// { group_index, connection_index } + std::tuple findConfig(const QUuid uuid) const; + int findGroup(QString name) const; + + /// Create a new group in the DB and place in the tree + /// dataChanged is sent by this function + std::variant addGroup(QString group_name); + std::optional saveToDb(const ConnectionConfig &cc); }; +#if false /** \brief Model class for the list of connections. * * This class also allows for the editing of the list. @@ -107,5 +132,6 @@ private: QString iniFileName(); }; +#endif #endif // CONNECTIONLISTMODEL_H diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index ec7210e..0b151b6 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -17,7 +17,6 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg , m_connectionController(master->connectionController()) { ui->setupUi(this); - ui->listView->setModel(m_connectionController->getConnectionListModel()); ui->treeView->setModel(m_connectionController->getConnectionTreeModel()); } @@ -34,23 +33,25 @@ void ConnectionManagerWindow::on_actionAdd_Connection_triggered() void ConnectionManagerWindow::on_actionDelete_connection_triggered() { - auto ci = ui->listView->selectionModel()->currentIndex(); + auto ci = ui->treeView->selectionModel()->currentIndex(); if (ci.isValid()) { - auto res = QMessageBox::question(this, "pglab", - tr("Are you sure you want to remove this connection?"), QMessageBox::Yes, QMessageBox::No); - if (res == QMessageBox::Yes) { - auto clm = m_connectionController->getConnectionListModel(); - clm->removeRow(ci.row()); + auto node = static_cast(ci.internalPointer()); + auto cc = dynamic_cast(node); + if (cc) { + auto res = QMessageBox::question(this, "pglab", + tr("Are you sure you want to remove this connection?"), QMessageBox::Yes, QMessageBox::No); + if (res == QMessageBox::Yes) { + auto cm = m_connectionController->getConnectionTreeModel(); + cm->removeRow(ci.row(), ci.parent()); + } } } } void ConnectionManagerWindow::on_actionConnect_triggered() { - auto ci = ui->listView->selectionModel()->currentIndex(); - if (ci.isValid()) { - m_connectionController->openSqlWindowForConnection(ci.row()); - } + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->openSqlWindowForConnection(ci); } void ConnectionManagerWindow::on_actionQuit_application_triggered() @@ -64,25 +65,25 @@ void ConnectionManagerWindow::on_actionQuit_application_triggered() void ConnectionManagerWindow::on_actionBackup_database_triggered() { - auto ci = ui->listView->selectionModel()->currentIndex(); - m_connectionController->openBackupDlgForConnection(ci.row()); + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->openBackupDlgForConnection(ci); } void ConnectionManagerWindow::on_actionManage_server_triggered() { - auto ci = ui->listView->selectionModel()->currentIndex(); - m_connectionController->openServerWindowForConnection(ci.row()); + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->openServerWindowForConnection(ci); } void ConnectionManagerWindow::on_listView_activated(const QModelIndex &index) { if (index.isValid()) { - m_connectionController->openSqlWindowForConnection(index.row()); + m_connectionController->openSqlWindowForConnection(index); } } void ConnectionManagerWindow::on_actionConfigure_connection_triggered() { - auto ci = ui->listView->selectionModel()->currentIndex(); - m_connectionController->editConnection(ci.row()); + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->editConnection(ci); } diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index cca5094..84d535f 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -15,21 +15,6 @@ - - - - Qt::Horizontal - - - - true - - - true - - - - diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index f2ba553..486b7f0 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -57,7 +57,7 @@ ConnectionConfig::ConnectionConfig() : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) {} -ConnectionGroup *ConnectionConfig::parent() +const ConnectionGroup *ConnectionConfig::parent() const { return m_group; } @@ -301,6 +301,21 @@ void ConnectionConfig::clean() m_dirty = false; } +QString ConnectionConfig::makeLongDescription() const +{ + std::string result(name()); + result += " ("; + result += user(); + result += "@"; + result += host(); + result += ":"; + result += std::to_string(port()); + result += "/"; + result += dbname(); + result += ")"; + return stdStrToQ(result); +} + /* PGHOST behaves the same as the host connection parameter. @@ -358,8 +373,21 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co env.insert(var, stdStrToQ(val)); } -void ConnectionGroup::add(std::shared_ptr cc) +void ConnectionGroup::erase(int idx, int count) +{ + m_connections.remove(idx, count); +} + +int ConnectionGroup::add(std::shared_ptr cc) { cc->setParent(this); m_connections.push_back(cc); + return m_connections.size() - 1; +} + +void ConnectionGroup::update(int idx, const ConnectionConfig &cc) +{ + auto node = m_connections[idx]; + *node = cc; + node->setParent(this); } diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index 20e9109..181909c 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -43,7 +43,10 @@ public: using Connections = QVector>; const Connections& connections() const { return m_connections; } - void add(std::shared_ptr cc); + void erase(int idx, int count = 1); + /// Adds cc to the group and returns the index within the group. + int add(std::shared_ptr cc); + void update(int idx, const ConnectionConfig &cc); bool operator==(const ConnectionGroup &rhs) const { return conngroup_id == rhs.conngroup_id @@ -57,7 +60,7 @@ class ConnectionConfig: public ConnectionNode { public: ConnectionConfig(); // Default object containing invalid uuid - ConnectionGroup* parent(); + const ConnectionGroup* parent() const; void setParent(ConnectionGroup *grp); void setUuid(const QUuid &uuid); @@ -113,6 +116,8 @@ public: void clean(); bool operator==(QUuid id) const { return m_uuid == id; } + + QString makeLongDescription() const; private: QUuid m_uuid; std::string m_name; From dbb6e1ab01c9f30ce13d86b39e8c4f4ce53e3ed7 Mon Sep 17 00:00:00 2001 From: eelke Date: Thu, 29 Aug 2019 16:10:28 +0200 Subject: [PATCH 06/12] Can open window for database by clicking on the config in the connection tree. --- pglab/ConnectionManagerWindow.cpp | 4 +++- pglab/ConnectionManagerWindow.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 0b151b6..2f59300 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -18,6 +18,8 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg { ui->setupUi(this); ui->treeView->setModel(m_connectionController->getConnectionTreeModel()); + connect(ui->treeView, &QTreeView::activated, this, + &ConnectionManagerWindow::connectionActivated); } @@ -75,7 +77,7 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() m_connectionController->openServerWindowForConnection(ci); } -void ConnectionManagerWindow::on_listView_activated(const QModelIndex &index) +void ConnectionManagerWindow::connectionActivated(const QModelIndex &index) { if (index.isValid()) { m_connectionController->openSqlWindowForConnection(index); diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index 54e1d1e..f49f711 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -30,7 +30,7 @@ private slots: void on_actionBackup_database_triggered(); void on_actionManage_server_triggered(); - void on_listView_activated(const QModelIndex &index); + void connectionActivated(const QModelIndex &index); void on_actionConfigure_connection_triggered(); From 521d3cdaac72e941c9145ad44321a846bce92601 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:42:21 +0200 Subject: [PATCH 07/12] Add seperate actions for adding and removing connection groups. --- pglab/ConnectionController.cpp | 38 ++++++++++++++++++++++++++++++ pglab/ConnectionController.h | 2 ++ pglab/ConnectionListModel.cpp | 22 +++++++++++++++++ pglab/ConnectionListModel.h | 6 ++--- pglab/ConnectionManagerWindow.cpp | 11 +++++++++ pglab/ConnectionManagerWindow.h | 4 ++++ pglab/ConnectionManagerWindow.ui | 24 ++++++++++++++++++- pglab/icons/folder_add.png | Bin 0 -> 1572 bytes pglab/icons/folder_delete.png | Bin 0 -> 1568 bytes pglab/resources.qrc | 2 ++ 10 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 pglab/icons/folder_add.png create mode 100644 pglab/icons/folder_delete.png diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index f4890f9..939f512 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -8,6 +8,8 @@ #include "BackupDialog.h" #include "PasswordPromptDialog.h" #include "ConnectionConfigurationWidget.h" +#include +#include ConnectionController::ConnectionController(MasterController *parent) @@ -52,6 +54,14 @@ namespace { return dynamic_cast(node); } + ConnectionGroup* getGroupFromModelIndex(QModelIndex index) + { + if (!index.isValid()) + return nullptr; + auto node = static_cast(index.internalPointer()); + return dynamic_cast(node); + } + } @@ -103,6 +113,34 @@ void ConnectionController::editConnection(QModelIndex index) } } +void ConnectionController::addGroup() +{ + auto result = QInputDialog::getText(nullptr, tr("Add new connection group"), + tr("Group name")); + if (!result.isEmpty()) { + auto res = m_connectionTreeModel->addGroup(result); + if (std::holds_alternative(res)) { + QMessageBox::critical(nullptr, tr("Add group failed"), + tr("Failed to add group.\n") + + std::get(res).text()); + } + } +} + +void ConnectionController::removeGroup(QModelIndex index) +{ + auto group = getGroupFromModelIndex(index); + if (group) { + auto btn = QMessageBox::question(nullptr, tr("Connection group"), + tr("Remove the selected group and all connections contained in the group?"), + QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::NoButton); + if (btn == QMessageBox::Yes) { + m_connectionTreeModel->removeGroup(index.row()); + } + } +} + void ConnectionController::openServerWindowForConnection(QModelIndex index) { auto config = getConfigFromModelIndex(index); diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index b8056bf..ffd51c9 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -34,6 +34,8 @@ public: /// Starts the form for editing a conncetion. /// This function returns immidiatly! void editConnection(QModelIndex index); + void addGroup(); + void removeGroup(QModelIndex index); private: MasterController *m_masterController; ConnectionList *m_connectionList = nullptr; diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 1cb67c1..651fd75 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -726,6 +726,28 @@ std::variant ConnectionTreeModel::addGroup(QString group_name) return { err }; } return q.lastInsertId().toInt(); +std::optional ConnectionTreeModel::removeGroup(int row) +{ + beginRemoveRows({}, row, row); + SCOPE_EXIT { endRemoveRows(); }; + auto id = m_groups[row]->conngroup_id; + QSqlQuery q(m_db); + q.prepare("DELETE FROM connection WHERE conngroup_id=:id"); + q.bindValue(":id", id); + if (!q.exec()) { + auto err = q.lastError(); + return { err }; + } + q.prepare("DELETE FROM conngroup WHERE conngroup_id=:id"); + q.bindValue(":id", id); + if (!q.exec()) { + auto err = q.lastError(); + return { err }; + } + + m_groups.remove(row); + return {}; +} } std::optional ConnectionTreeModel::saveToDb(const ConnectionConfig &cc) diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 1a56d0d..d9daf17 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -60,6 +60,9 @@ public: /** Save changed config, group is not allowed to change */ void save(const ConnectionConfig &cc); + /// Create a new group in the DB and place in the tree + std::variant addGroup(QString group_name); + std::optional removeGroup(int row); private: using Groups = QVector>; @@ -71,9 +74,6 @@ private: std::tuple findConfig(const QUuid uuid) const; int findGroup(QString name) const; - /// Create a new group in the DB and place in the tree - /// dataChanged is sent by this function - std::variant addGroup(QString group_name); std::optional saveToDb(const ConnectionConfig &cc); }; diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 2f59300..20ffd5b 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -89,3 +89,14 @@ void ConnectionManagerWindow::on_actionConfigure_connection_triggered() auto ci = ui->treeView->selectionModel()->currentIndex(); m_connectionController->editConnection(ci); } + +void ConnectionManagerWindow::on_actionAdd_group_triggered() +{ + m_connectionController->addGroup(); +} + +void ConnectionManagerWindow::on_actionRemove_group_triggered() +{ + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->removeGroup(ci); +} diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index f49f711..c72b9cb 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -34,6 +34,10 @@ private slots: void on_actionConfigure_connection_triggered(); + void on_actionAdd_group_triggered(); + + void on_actionRemove_group_triggered(); + private: Ui::ConnectionManagerWindow *ui; MasterController *m_masterController; diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index 84d535f..79b6c2a 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -68,12 +68,14 @@ QToolButton { + + + - @@ -142,6 +144,26 @@ QToolButton { Configure connection + + + + :/icons/folder_add.png + + + + Add group + + + + + + :/icons/folder_delete.png + + + + Remove group + + diff --git a/pglab/icons/folder_add.png b/pglab/icons/folder_add.png new file mode 100644 index 0000000000000000000000000000000000000000..d881adf8e9202901244af1bafbc1c39dc939a9a7 GIT binary patch literal 1572 zcmV+<2HW|GP)4k@LZHNm0kuIuj3$~G9|=D&_$DD3O;BSn$`7eQK@(63(eMuu8kGhRsSlvl zMZ?1a-FE51w%gY(ZMVDk;mkSPeUxptC5_%>a_^mc?wy%$zL_&uv27dw=b@13jXRbd z&|s{zEsJjLjLQ^Ng$eyo)r-g0lERoQ2XvAgHa>s-9{MbO*H%VxQ-f#c^N;X)lJI0Y z;1-dS@#y)j6u%a?n0lCSl|KVxD1xTi#UVveCI+*rF?xpyy+=o)rzfmg25i+ycMg&i z1Dp}tgf$dI)rPNc_ARS*`he~r^qyUr2|Yb&h#XVtR?*WD5SJ=q!Q(*Z(Ft0&>2(v% z%8iixf_roa5T(FX#w$Y#Sg)l(lJG1GAhtx@3JtGRGI2oatr*$SMsXlhUa2w^MV!Y* zfWB!Bq^C7A0){9x5p5O+*r=+h@HRE0yY(6qXmkMB38jIus}X^h1fyZ^WW2TUA`lzGf|vFR0%NR(ri}2)L8RQZPLML00y`%1tgvXBL@YzvI0Emf z3&dFyW%DZ#y|@jdH?Wb11ud@}D6BzmIC`2CPCd2ORYE}39PpkwhhR%Pna=c46@_Ib zD8FY8EXx+C3EE_Hz?PM!PMM3~<P}q$N-GMfpk(1r-VyV z$;cAPkvLZp3v8wc1>-7(88y*;wYlSuOMOAE{UidaA>a%Jf|z;la`LU6?w#@aSH@yh z;?O7;oW?{s50{>8$wrqq3td+{IPdMTjpS2KM_=eE58)Rjb^g#j3bb3 zOQmY(R1km81)A~IU-dXb<*6pY$BOb|tgC(<1^zZ1_jK1W{ONr_HPL%DfP#uUgfj|v zFICUp6Zln*Sa;#$-@d_jEoZT2!BePxU@>x>xnj*=bO0XjFL=3bGnOc&`10tre@H!q zM0hf3=xOt#a84y|A3D;cM2NY<(XaYTiQe^t|Wp+lVEIH-3S@jcb17&n_1o&1Qax2(>Xq zp{jwyg*~3{@z|oLarXKb=pOJvSJPWRU$d@@p8e_Hjhd<_;V#5hdgPD@5P`b9tpxu( zy6Y#~1g#^xfW_%T{S_a|O7hVjJ`aORZirYHP1zp!nsxll7e0s5>3O{CS0`FAr=Zu{ zhKh%&Jr5E2^t|zoI^;x-h^`}YMzDR`gEx1?ovV_%&GiZbgQth7EnBf^D`CyynCkg# z7qwSw+~WqZ0C}pLs}KEA34PXgXw8jiTcWk`xqxKuhSl?`BT;igGrCEWS}1J9I@VtO zyk;rYJ3~!-T8z#S>O0#XlAn9^2L*0jCfwzaeE7KmsM~}RT_hdwesb_~=eWAg&0EaW z>g8zc>PFR!=`upoO5Y-KsZw(t@)>P6=pakf>6`d{c7S|F4@uvxI?&}oZA&rs&MKCz z!{D}`vWTN*zOr$iQ7^Xu?Ty&Z{G8~Fo9WctCX7%@sg%#;hLy9)=H zrN7b?(BZ|w;~(M`()hJWaDZ8Bc^lAFbSKI>L%2PM!q1;Zu+t3ecFLC55dfMzG+4Ld z@K4+DCaJD-m!1p+WI>Qkl3A52u(WIu);p)+?$iM~8p7GO-?6Lt2u_lwJ)UxNvK$z8 zf*VL7NfF7ExK55+RDC2o19ZoUZ)P9sZ|!H-5&r{nB5fq09}hVL#Kt*5{x`?J0t^7^ WWe&kFMnG%;00003tVB59a_|H#OY53!5ts^YeEs^%;(4;k zl?33DJ16Jqe-6?9729L(rNddbAKB>?x{f{@V~j12X08bK&J%n47P6<#dNZ;xxr0U; zS&jn{5mSdT9l-XM?;pfg)H^MpYQ)}wJ^9#E=Nu6*PD7^7XF%R^WCFhc{-2lW-K5ub zI2u2Mr7yS^2Y@tfoD074gh0$IIh4#ID?*5AF>b_Wo@DtnV9gsD@}n#QAYWW5GZ>S< zg&CkVjpY2W=4T))Q%&x+2ms-fD;(ag0fdIeg@6_(K!9MsQFIj&@D85^n$L35D0V0{4tOajP` zwZSl%lo+ILu%z7!5Pa1}#75fi?ZuIEG?`N8v_cFhD#Bxatf_yTB9cVbcV@mQXF?)U zlCk_!WmcHx#ojJva8^T4>j>Q&nE2~5f!_I$JNL6!= zxg}7haaxMy05D0Xw84_~@ZY|Un?vDKG&?0|QSDwQOXgHGK>}6abk`soAE3n?HUq_b zsz^)jR1oR*!Q0l0iOWN?sV@>_-Sdy2;msN(!ng5LPw;}Ee|{a{IwJRcsHks}V3??0 zTrFOdZ0Bs1ivA;4;kz_~`rW(n=;61amaekYl}IAg(~TP+oPgHk!0AgPw`h7Av)f}9 zUE$#|cs4asWP(;joRt3o5={Z+YDTl)ufk6@~_G^TF|Ioa7{(5`#w4MV{1zbQthWPeTgzG zK1mF$mL~4STZH`6F# zy`(M0C`2v|zK%QaRC%Be_Jiq^Zd0^@WP&ViTSCr>|HLzE*1F;3eCKQ_79oguhfz<+ zFiql9OP5w#L??7=ly)kVdv_o*&wc)S!B?WMG=mKf&eTn-6ze9NR5Y#p#_?lRUX5iO zwJ3p0EFB;kre9Za%;K3ycqaNXQ!B_CTJ~<;cDLE7BG#s@Biw(#}h$NGo+*wySWAjg6uN!mUGWR`e<{BN#*1sDKhJ~EdS Sh`3Jx0000icons/constraints/unique.png icons/constraints/index.png icons/server_configuration.png + icons/folder_add.png + icons/folder_delete.png From 2823297482fba328e777f0c2febf92caf3a65a83 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:43:28 +0200 Subject: [PATCH 08/12] Fix crash when closing dialoog with X button --- pglab/ConnectionConfigurationWidget.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp index 0e656d1..d8cb329 100644 --- a/pglab/ConnectionConfigurationWidget.cpp +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -33,17 +33,16 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c auto win = new QDialog; win->setWindowTitle(tr("Edit connection configuration")); win->setLayout(vbox); + win->setAttribute( Qt::WA_DeleteOnClose, true ); win->connect(btn_hbox, &QDialogButtonBox::accepted, [ctrl, w, win] () { auto [grp, cc] = w->data(); ctrl->getConnectionTreeModel()->save(grp, cc); win->accept(); }); - win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] () { win->reject(); }); - - win->connect(win, &QDialog::finished, [win] (int) + win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] () { - delete win; + win->reject(); }); win->show(); From 0fb1d89aee43f7024fd4cc9c81aec79a5b66fd93 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:44:48 +0200 Subject: [PATCH 09/12] When editing connection the dialog is now initialized on the correct connection group. --- pglab/ConnectionConfigurationWidget.cpp | 11 +++++++---- pglab/ConnectionConfigurationWidget.h | 2 ++ pglab/ConnectionListModel.cpp | 8 ++++++++ pglab/ConnectionListModel.h | 1 + 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp index d8cb329..cbbdb66 100644 --- a/pglab/ConnectionConfigurationWidget.cpp +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -41,22 +41,21 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c win->accept(); }); win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] () - { + { win->reject(); - }); + }); win->show(); } ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent) : QWidget(parent) + , m_connectionModel(connection_model) { lblGroup = new QLabel; cmbbxGroup = new QComboBox; cmbbxGroup->setModel(connection_model); cmbbxGroup->setModelColumn(0); -// cmbbxGroup->setEditable(true); -// cmbbxGroup->setInsertPolicy(QComboBox::NoInsert); lblGroup->setBuddy(cmbbxGroup); lblName = new QLabel; @@ -158,6 +157,10 @@ void ConnectionConfigurationWidget::retranslateUi() void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) { + auto group = cfg.parent(); + auto group_idx = m_connectionModel->findGroup(group->conngroup_id); + cmbbxGroup->setCurrentIndex(group_idx); + m_uuid = cfg.uuid(); edtName->setText(stdStrToQ(cfg.name())); edtHost->setText(stdStrToQ(cfg.host())); diff --git a/pglab/ConnectionConfigurationWidget.h b/pglab/ConnectionConfigurationWidget.h index 21849c1..e7b9ab2 100644 --- a/pglab/ConnectionConfigurationWidget.h +++ b/pglab/ConnectionConfigurationWidget.h @@ -30,6 +30,8 @@ public: signals: private: + ConnectionTreeModel *m_connectionModel; + QUuid m_uuid; QLabel *lblGroup; diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index 651fd75..bad449b 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -748,6 +748,14 @@ std::optional ConnectionTreeModel::removeGroup(int row) m_groups.remove(row); return {}; } + +int ConnectionTreeModel::findGroup(int conngroup_id) const +{ + auto find_res = std::find_if(m_groups.begin(), m_groups.end(), + [conngroup_id] (auto item) { return item->conngroup_id == conngroup_id; }); + if (find_res == m_groups.end()) + return -1; + return find_res - m_groups.begin(); } std::optional ConnectionTreeModel::saveToDb(const ConnectionConfig &cc) diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index d9daf17..ee80dc9 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -63,6 +63,7 @@ public: /// Create a new group in the DB and place in the tree std::variant addGroup(QString group_name); std::optional removeGroup(int row); + int findGroup(int conngroup_id) const; private: using Groups = QVector>; From 91049b235a4e61e37ff03be7041ea437b75c91d8 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:48:11 +0200 Subject: [PATCH 10/12] Send InsertRows signals when adding a group. --- pglab/ConnectionListModel.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index bad449b..e3bff93 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -725,7 +725,17 @@ std::variant ConnectionTreeModel::addGroup(QString group_name) auto err = q.lastError(); return { err }; } - return q.lastInsertId().toInt(); + auto cg = std::make_shared(); + cg->conngroup_id = q.lastInsertId().toInt(); + cg->name = group_name; + + int row = m_groups.size(); + beginInsertRows({}, row, row); + SCOPE_EXIT { endInsertRows(); }; + m_groups.push_back(cg); + return row; +} + std::optional ConnectionTreeModel::removeGroup(int row) { beginRemoveRows({}, row, row); From d9431b173c165c0e2829836112d07c127ae34f86 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:48:34 +0200 Subject: [PATCH 11/12] Cleanup --- pglab/ConnectionListModel.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index e3bff93..284484c 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -674,10 +674,7 @@ void ConnectionTreeModel::save(const QString &group_name, const ConnectionConfig beginInsertRows(parent, idx, idx); SCOPE_EXIT { endInsertRows(); }; auto node = std::make_shared(cc); - int node_idx = new_grp->add(node); -// dataChanged( -// createIndex(node_idx, 0, node.get()), -// createIndex(node_idx, ColCount-1, node.get())); + new_grp->add(node); auto save_res = saveToDb(*node); if (save_res) { throw std::runtime_error("SqlError2"); From bf8bb087e7e9b155d17915c37252c3af13958aa9 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 1 Sep 2019 06:52:26 +0200 Subject: [PATCH 12/12] Fix: adding connection Crashed because it tried to select right group when there was no group to set. --- pglab/ConnectionConfigurationWidget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp index cbbdb66..979b909 100644 --- a/pglab/ConnectionConfigurationWidget.cpp +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -158,8 +158,10 @@ void ConnectionConfigurationWidget::retranslateUi() void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) { auto group = cfg.parent(); - auto group_idx = m_connectionModel->findGroup(group->conngroup_id); - cmbbxGroup->setCurrentIndex(group_idx); + if (group) { + auto group_idx = m_connectionModel->findGroup(group->conngroup_id); + cmbbxGroup->setCurrentIndex(group_idx); + } m_uuid = cfg.uuid(); edtName->setText(stdStrToQ(cfg.name()));