From 0d2496221ac446c62db2438fbe796f85b0463bba Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Fri, 8 May 2020 15:35:19 +0200 Subject: [PATCH] Imported existing sources --- .gitignore | 103 +++++++++++++++++++++-------------- avivpn.pro | 21 ++++++++ entry.cpp | 119 +++++++++++++++++++++++++++++++++++++++++ entry.h | 48 +++++++++++++++++ main.cpp | 25 +++++++++ mainwindow.cpp | 46 ++++++++++++++++ mainwindow.h | 23 ++++++++ mainwindow.ui | 63 ++++++++++++++++++++++ vpnmodel.cpp | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ vpnmodel.h | 48 +++++++++++++++++ 10 files changed, 597 insertions(+), 41 deletions(-) create mode 100644 avivpn.pro create mode 100644 entry.cpp create mode 100644 entry.h create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 vpnmodel.cpp create mode 100644 vpnmodel.h diff --git a/.gitignore b/.gitignore index f147edf..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,73 @@ -# C++ objects and libs -*.slo -*.lo -*.o +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave *.a -*.la -*.lai +*.core +*.moc +*.o +*.obj +*.orig +*.rej *.so *.so.* -*.dll -*.dylib - -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -*.qm -*.prl -# Qt unit tests -target_wrapper.* +# qtcreator generated files +*.pro.user* -# QtCreator -*.autosave +# xemacs temporary files +*.flc -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp -# QtCreator CMake -CMakeLists.txt.user* +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* -# QtCreator 4.8< compilation database -compile_commands.json +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe -# QtCreator local machine specific files for imported projects -*creator.user* diff --git a/avivpn.pro b/avivpn.pro new file mode 100644 index 0000000..474203b --- /dev/null +++ b/avivpn.pro @@ -0,0 +1,21 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000 + +SOURCES += \ + entry.cpp \ + main.cpp \ + mainwindow.cpp \ + vpnmodel.cpp + +HEADERS += \ + entry.h \ + mainwindow.h \ + vpnmodel.h + +FORMS += \ + mainwindow.ui diff --git a/entry.cpp b/entry.cpp new file mode 100644 index 0000000..174a788 --- /dev/null +++ b/entry.cpp @@ -0,0 +1,119 @@ +#include "entry.h" + +#include +#include + +Entry::Entry(const QString &name, const QString &tunnel, bool sudo, QObject *parent) : + QObject{parent}, m_name{name}, m_tunnel{tunnel}, m_sudo{sudo} +{ + connect(&m_process, &QProcess::errorOccurred, this, &Entry::errorOccurred); + connect(&m_process, &QProcess::stateChanged, this, &Entry::stateChanged); + connect(&m_process, &QProcess::readyReadStandardOutput, this, &Entry::readyReadStandardOutput); + connect(&m_process, &QProcess::readyReadStandardError, this, &Entry::readyReadStandardError); +} + +Entry::~Entry() +{ + qDebug() << m_name; + m_process.terminate(); + if (!m_process.waitForFinished(250)) + m_process.kill(); +} + +Qt::CheckState Entry::state() const +{ + switch (m_process.state()) + { + case QProcess::Starting: + return Qt::PartiallyChecked; + case QProcess::Running: + return Qt::Checked; + case QProcess::NotRunning: + return Qt::Unchecked; + } +} + +void Entry::toggle() +{ + if (m_process.state() == QProcess::NotRunning) + start(); + else + stop(); +} + +void Entry::start() +{ + qDebug() << m_name; + m_logOutput.clear(); + m_process.start(binaryName(), arguments()); +} + +void Entry::stop() +{ + qDebug() << m_name; + m_process.terminate(); +} + +QString Entry::binaryName() const +{ + return m_sudo ? "sudo" : "ssh"; +} + +QStringList Entry::arguments() const +{ + QStringList args { + "-v", // Verbose mode. Causes ssh to print debugging messages about its progress. + "-N", // Do not execute a remote command. This is useful for just forwarding ports. + "-T", // Disable pseudo-terminal allocation. + "-oServerAliveInterval=60", + "-oExitOnForwardFailure=yes", + m_tunnel, + "pc178" + }; + + if (m_sudo) + { + const QDir sshDir(QDir::home().absoluteFilePath(".ssh")); + + args.insert(0, "ssh"); + args.insert(1, "-F"); + args.insert(2, sshDir.absoluteFilePath("config")); + args.insert(3, "-i"); + args.insert(4, sshDir.absoluteFilePath("id_rsa")); + } + + return args; +} + +void Entry::errorOccurred(QProcess::ProcessError error) +{ + qDebug() << m_name << error; +} + +void Entry::stateChanged(QProcess::ProcessState state) +{ + qDebug() << m_name << state; + emit dataChanged(); +} + +void Entry::readyReadStandardOutput() +{ + m_process.setReadChannel(QProcess::StandardOutput); + while (m_process.canReadLine()) + { + const auto line = m_process.readLine(); + //qDebug() << m_name << line; + m_logOutput.append(line); + } +} + +void Entry::readyReadStandardError() +{ + m_process.setReadChannel(QProcess::StandardError); + while (m_process.canReadLine()) + { + const auto line = m_process.readLine(); + //qDebug() << m_name << line; + m_logOutput.append(line); + } +} diff --git a/entry.h b/entry.h new file mode 100644 index 0000000..3dc2a97 --- /dev/null +++ b/entry.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class Entry : public QObject +{ + Q_OBJECT + +public: + Entry(const QString &name, const QString &tunnel, bool sudo, QObject *parent = nullptr); + ~Entry() override; + + const QString &name() const { return m_name; } + const QString &tunnel() const { return m_tunnel; } + bool sudo() const { return m_sudo; } + + const QString &logOutput() const { return m_logOutput; } + + Qt::CheckState state() const; + + void toggle(); + void start(); + void stop(); + + QString binaryName() const; + QStringList arguments() const; + +signals: + void dataChanged(); + +private slots: + void errorOccurred(QProcess::ProcessError error); + void stateChanged(QProcess::ProcessState state); + void readyReadStandardOutput(); + void readyReadStandardError(); + +private: + const QString m_name; + const QString m_tunnel; + const bool m_sudo; + + bool m_readyReceived{false}; + + QProcess m_process; + + QString m_logOutput; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8eeed02 --- /dev/null +++ b/main.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..626494d --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,46 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow{parent}, + m_ui{std::make_unique()} +{ + m_ui->setupUi(this); + + m_ui->treeView->setModel(&m_model); + + connect(m_ui->treeView, &QWidget::customContextMenuRequested, [&view=*m_ui->treeView,&model=m_model](const QPoint &pos){ + const auto index = view.indexAt(pos); + if (!index.isValid()) + return; + + const auto &entry = model.getEntry(index); + + QMenu menu; + const auto showLogAction = menu.addAction("Show log"); + const auto selectedAction = menu.exec(view.viewport()->mapToGlobal(pos)); + + if (selectedAction == showLogAction) + { + QDialog dialog; + QVBoxLayout layout; + QPlainTextEdit widget; + QFont font = widget.document()->defaultFont(); + font.setFamily("Courier New"); + widget.document()->setDefaultFont(font); + widget.setLineWrapMode(QPlainTextEdit::NoWrap); + widget.setReadOnly(true); + widget.setPlainText(entry.logOutput()); + layout.addWidget(&widget); + dialog.setLayout(&layout); + dialog.exec(); + } + }); +} + +MainWindow::~MainWindow() = default; diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..e9e6028 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#include "vpnmodel.h" + +namespace Ui { class MainWindow; } + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow() override; + +private: + std::unique_ptr m_ui; + + VpnModel m_model; +}; diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..f56a7c0 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,63 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Start all + + + + + + + Stop all + + + + + + + + + Qt::CustomContextMenu + + + false + + + + + + + + + diff --git a/vpnmodel.cpp b/vpnmodel.cpp new file mode 100644 index 0000000..f30c987 --- /dev/null +++ b/vpnmodel.cpp @@ -0,0 +1,142 @@ +#include "vpnmodel.h" + +#include +#include + +#include + +namespace { +enum { + ColumnName, + ColumnCommand, + NumberOfColumns +}; +} + +VpnModel::VpnModel(QObject *parent) : + QAbstractTableModel{parent} +{ + for (Entry &entry : m_entries) + connect(&entry, &Entry::dataChanged, this, &VpnModel::entryChanged); +} + +int VpnModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return std::size(m_entries); +} + +int VpnModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return NumberOfColumns; +} + +QVariant VpnModel::data(const QModelIndex &index, int role) const +{ + const auto &entry = m_entries[index.row()]; + + switch (index.column()) + { + case ColumnName: + switch (Qt::ItemDataRole(role)) + { + case Qt::DisplayRole: + case Qt::EditRole: + return entry.name(); + case Qt::CheckStateRole: + return entry.state(); + case Qt::FontRole: + QFont font; + font.setBold(true); + return font; + } + break; + case ColumnCommand: + switch (Qt::ItemDataRole(role)) + { + case Qt::DisplayRole: + case Qt::EditRole: + return entry.binaryName() + ' ' + entry.arguments().join(' '); + } + break; + } + + return {}; +} + +QVariant VpnModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (orientation) + { + case Qt::Horizontal: + switch (section) + { + case ColumnName: + switch (Qt::ItemDataRole(role)) + { + case Qt::DisplayRole: + case Qt::EditRole: + return tr("Name"); + } + break; + case ColumnCommand: + switch (Qt::ItemDataRole(role)) + { + case Qt::DisplayRole: + case Qt::EditRole: + return tr("Command"); + } + break; + } + break; + } + + return {}; +} + +bool VpnModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + auto &entry = m_entries[index.row()]; + + switch (index.column()) + { + case ColumnName: + switch (role) + { + case Qt::CheckStateRole: + entry.toggle(); + break; + } + break; + } + + return false; +} + +Qt::ItemFlags VpnModel::flags(const QModelIndex &index) const +{ + auto flags = QAbstractTableModel::flags(index); + if (index.column() == 0) + flags |= Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate; + return flags; +} + +void VpnModel::entryChanged() +{ + const auto *sender_ptr = sender(); + const auto iter = std::find_if(std::cbegin(m_entries), std::cend(m_entries), [sender_ptr](const Entry &entry){ + return &entry == sender_ptr; + }); + + if (iter == std::cend(m_entries)) + { + qCritical() << "unknown sender" << sender_ptr; + return; + } + + const auto row = std::distance(std::cbegin(m_entries), iter); + + const auto index = createIndex(row, 0); + emit dataChanged(index, index, { Qt::CheckStateRole }); +} diff --git a/vpnmodel.h b/vpnmodel.h new file mode 100644 index 0000000..83b74b2 --- /dev/null +++ b/vpnmodel.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include + +#include "entry.h" + +class VpnModel : public QAbstractTableModel +{ + Q_OBJECT +public: + VpnModel(QObject *parent = nullptr); + + Entry &getEntry(const QModelIndex &index) { return getEntry(index.row()); } + const Entry &getEntry(const QModelIndex &index) const { return getEntry(index.row()); } + + Entry &getEntry(int row) { return m_entries[row]; } + const Entry &getEntry(int row) const { return m_entries[row]; } + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + +private slots: + void entryChanged(); + +private: + std::array m_entries { + Entry { "Bamboo", "-L 127.1.0.1:2233:bamboo.avibit.com:2233", false }, + Entry { "Bitbucket", "-L 127.2.0.1:2233:bitbucket.avibit.com:2233", false }, + Entry { "Crucible", "-L 127.3.0.1:2233:crucible.avibit.com:2233", false }, + Entry { "Confluence", "-L 127.4.0.1:2233:confluence.avibit.com:2233", false }, + Entry { "Jira", "-L 127.5.0.1:2233:jira.avibit.com:2233", false }, + + Entry { "SVN", "-L 3690:svn.avibit.com:3690", false }, + Entry { "Bitbucket SSH", "-L 127.2.0.1:7999:bitbucket.avibit.com:7999", false }, + Entry { "Timetool", "-L 8080:timetool.avibit.com:8080", false }, + //Entry { "Mirror", "-L 80:mirror.avibit.com:80", true }, + Entry { "Intranet", "-L 80:intranet.avibit.com:80", true }, + Entry { "Sonarqube", "-L 9000:localhost:9000", false } + }; +};