diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp new file mode 100644 index 0000000..979b909 --- /dev/null +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -0,0 +1,198 @@ +#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(ctrl->getConnectionTreeModel()); + 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->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->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); + lblGroup->setBuddy(cmbbxGroup); + + 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; + + formLayout->addRow(lblGroup, cmbbxGroup); + 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); + 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)); + 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) +{ + auto group = cfg.parent(); + if (group) { + 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())); + 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())); +} + +std::tuple ConnectionConfigurationWidget::data() const +{ + QString group; + group = cmbbxGroup->currentText(); + + 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 { group, cfg }; +} + diff --git a/pglab/ConnectionConfigurationWidget.h b/pglab/ConnectionConfigurationWidget.h new file mode 100644 index 0000000..e7b9ab2 --- /dev/null +++ b/pglab/ConnectionConfigurationWidget.h @@ -0,0 +1,66 @@ +#ifndef CONNECTIONCONFIGURATIONWIDGET_H +#define CONNECTIONCONFIGURATIONWIDGET_H + +#include +#include +#include + +class ConnectionController; +class ConnectionConfig; +class QFormLayout; +class QLabel; +class QLineEdit; +class QSpinBox; +class QComboBox; +class QDataWidgetMapper; + +class ConnectionTreeModel; + +class ConnectionConfigurationWidget : public QWidget +{ + Q_OBJECT +public: + static void editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg); + + explicit ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent = nullptr); + + void retranslateUi(); + void setData(const ConnectionConfig &cfg); + std::tuple data() const; +signals: + +private: + ConnectionTreeModel *m_connectionModel; + + QUuid m_uuid; + + QLabel *lblGroup; + QComboBox *cmbbxGroup; + 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; + QFormLayout *formLayout; + +public slots: +}; + + + +#endif // CONNECTIONCONFIGURATIONWIDGET_H diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp new file mode 100644 index 0000000..939f512 --- /dev/null +++ b/pglab/ConnectionController.cpp @@ -0,0 +1,262 @@ +#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" +#include +#include + + +ConnectionController::ConnectionController(MasterController *parent) + : QObject(parent) + , m_masterController(parent) +{} + +ConnectionController::~ConnectionController() +{ + delete m_connectionManagerWindow; + delete m_connectionTreeModel; +} + +void ConnectionController::init() +{ + //std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data(); + //m_userConfigDatabase = std::make_shared(dbfilename); + + + m_passwordManager = std::make_shared(); + + m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase()); + m_connectionTreeModel->load(); + + m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); + m_connectionManagerWindow->show(); + +} + +void ConnectionController::showConnectionManager() +{ + m_connectionManagerWindow->show(); +} + +namespace { + + ConnectionConfig* getConfigFromModelIndex(QModelIndex index) + { + if (!index.isValid()) + return nullptr; + auto node = static_cast(index.internalPointer()); + return dynamic_cast(node); + } + + ConnectionGroup* getGroupFromModelIndex(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) { + + 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(*config); + w->showMaximized(); + } + } + +} + +void ConnectionController::openBackupDlgForConnection(QModelIndex index) +{ + 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(*config); + w->show(); + } + } +} + +void ConnectionController::createConnection() +{ + ConnectionConfig cc; + cc.setUuid(QUuid::createUuid()); + ConnectionConfigurationWidget::editExistingInWindow(this, cc); +} + +void ConnectionController::editConnection(QModelIndex index) +{ + auto config = getConfigFromModelIndex(index); + if (config) { + ConnectionConfigurationWidget::editExistingInWindow(this, *config); + } +} + +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); + if (config) { + if (retrieveConnectionPassword(*config)) { + m_connectionTreeModel->save(*config); + auto w = new ServerWindow(m_masterController, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(*config); + 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 = 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)); + 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..ffd51c9 --- /dev/null +++ b/pglab/ConnectionController.h @@ -0,0 +1,65 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include + +class MasterController; +class ConnectionConfig; +class ConnectionList; +class ConnectionTreeModel; +class ConnectionManagerWindow; +class PasswordManager; + +class ConnectionController : public QObject { + Q_OBJECT +public: + explicit ConnectionController(MasterController *parent = nullptr); + ~ConnectionController(); + + void init(); + + ConnectionTreeModel *getConnectionTreeModel() + { + return m_connectionTreeModel; + } + + void showConnectionManager(); + 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(QModelIndex index); + void addGroup(); + void removeGroup(QModelIndex index); +private: + MasterController *m_masterController; + ConnectionList *m_connectionList = nullptr; + ConnectionTreeModel *m_connectionTreeModel = 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..284484c 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -1,14 +1,158 @@ #include "ConnectionListModel.h" -#include "ConnectionList.h" #include "ScopeGuard.h" #include "util.h" #include +#include +#include +#include +#include +#include +#include -ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent) +namespace { + + const char * const q_create_table_conngroup = +R"__( +CREATE TABLE IF NOT EXISTS conngroup ( + conngroup_id INTEGER PRIMARY KEY, + gname TEXT NOT NULL UNIQUE +);)__"; + + 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::optional 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()) { + return q.lastError(); + } + 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())); +// } + + + +// 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 + + +#if false +ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) - , m_connections(conns) { } @@ -18,7 +162,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 +178,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 +216,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 +253,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 +277,495 @@ 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.remove(cc.uuid().toString()); + } + settings.sync(); + m_connections.remove(row, count); result = true; } return result; } - -//void ConnectionListModel::load() +//void ConnectionListModel::newItem() //{ -// m_connections->load(); +//// int i = m_connections->createNew(); +//// auto idx = createIndex(i, 0); +//// emit dataChanged(idx, idx); //} +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; +} +#endif + +ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) + : QAbstractItemModel(parent) + , m_db(db) +{ +} + +void ConnectionTreeModel::load() +{ + InitConnectionTables(m_db); + + 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(); + + auto g = std::make_shared(); + g->conngroup_id = id; + g->name = name; + m_groups.push_back(g); + } + + 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(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"); + } + } + +// 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, + const_cast(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 &) 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); + new_grp->add(node); + 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 }; + } + 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); + 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 {}; +} + +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) +{ + return SaveConnectionConfig(m_db, cc, cc.parent()->conngroup_id); } diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index be61202..ee80dc9 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -8,9 +8,77 @@ #include "ConnectionConfig.h" #include "Expected.h" +#include +#include +#include -class ConnectionList; +#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; + + 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); + /// 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>; + + 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; + + 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. @@ -30,30 +98,41 @@ 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; - - void newItem(); - Expected get(size_t row); virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + // END Model/View related functions + 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); + /** 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 #endif // CONNECTIONLISTMODEL_H diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 7c053e0..20ffd5b 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" @@ -11,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) @@ -29,86 +17,49 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg , m_connectionController(master->connectionController()) { ui->setupUi(this); - ui->listView->setModel(m_connectionController->getConnectionListModel()); - - setupWidgetMappings(); - - connect(ui->listView->selectionModel(), - SIGNAL(currentChanged(QModelIndex,QModelIndex)), - this, SLOT(on_currentChanged(QModelIndex,QModelIndex))); + ui->treeView->setModel(m_connectionController->getConnectionTreeModel()); + connect(ui->treeView, &QTreeView::activated, this, + &ConnectionManagerWindow::connectionActivated); } 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() { - 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::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); - } + auto ci = ui->treeView->selectionModel()->currentIndex(); + m_connectionController->openSqlWindowForConnection(ci); } 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(); } @@ -116,20 +67,36 @@ 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) +void ConnectionManagerWindow::connectionActivated(const QModelIndex &index) { if (index.isValid()) { - auto r = static_cast(index.row()); - m_connectionController->openSqlWindowForConnection(r); + m_connectionController->openSqlWindowForConnection(index); } } + +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 85638fc..c72b9cb 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,24 +24,24 @@ 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(); 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(); + + void on_actionAdd_group_triggered(); + + void on_actionRemove_group_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..79b6c2a 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -6,7 +6,7 @@ 0 0 - 800 + 413 600 @@ -16,161 +16,7 @@ - - - Qt::Horizontal - - - - true - - - 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 +25,7 @@ 0 0 - 800 + 413 20 @@ -222,11 +68,14 @@ QToolButton { + + + + - @@ -285,6 +134,36 @@ QToolButton { Manage server + + + + :/icons/server_configuration.png + + + + Configure connection + + + + + + :/icons/folder_add.png + + + + Add group + + + + + + :/icons/folder_delete.png + + + + Remove group + + 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/folder_add.png b/pglab/icons/folder_add.png new file mode 100644 index 0000000..d881adf Binary files /dev/null and b/pglab/icons/folder_add.png differ diff --git a/pglab/icons/folder_delete.png b/pglab/icons/folder_delete.png new file mode 100644 index 0000000..f35b7a3 Binary files /dev/null and b/pglab/icons/folder_delete.png differ diff --git a/pglab/icons/server_configuration.png b/pglab/icons/server_configuration.png new file mode 100644 index 0000000..dcf14ae Binary files /dev/null and b/pglab/icons/server_configuration.png differ 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..b86a06f 100644 --- a/pglab/resources.qrc +++ b/pglab/resources.qrc @@ -28,5 +28,8 @@ icons/constraints/primarykey.png icons/constraints/unique.png icons/constraints/index.png + icons/server_configuration.png + icons/folder_add.png + icons/folder_delete.png diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index 1dd47b7..486b7f0 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -57,6 +57,16 @@ ConnectionConfig::ConnectionConfig() : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) {} +const ConnectionGroup *ConnectionConfig::parent() const +{ + return m_group; +} + +void ConnectionConfig::setParent(ConnectionGroup *grp) +{ + m_group = grp; +} + void ConnectionConfig::setUuid(const QUuid &uuid) { @@ -291,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. @@ -347,3 +372,22 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co else env.insert(var, stdStrToQ(val)); } + +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 1652bcf..181909c 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -2,6 +2,7 @@ #define CONNECTION_H #include +#include #include #include @@ -24,9 +25,43 @@ 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: - ConnectionConfig(); + virtual ~ConnectionNode() = default; +}; + +class ConnectionGroup: public ConnectionNode { +public: + int conngroup_id; + QString name; + + using Connections = QVector>; + const Connections& connections() const { return m_connections; } + + 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 + && name == rhs.name; + } +private: + Connections m_connections; +}; + +class ConnectionConfig: public ConnectionNode { +public: + ConnectionConfig(); // Default object containing invalid uuid + + const ConnectionGroup* parent() const; + void setParent(ConnectionGroup *grp); void setUuid(const QUuid &uuid); const QUuid &uuid() const; @@ -79,6 +114,10 @@ public: bool dirty() const; void clean(); + + bool operator==(QUuid id) const { return m_uuid == id; } + + QString makeLongDescription() const; private: QUuid m_uuid; std::string m_name; @@ -100,6 +139,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);