added PageSettingsSplitTunneling

- added a call to the context menu when clicking the right mouse button for textInput
This commit is contained in:
vladimir.kuznetsov 2023-08-08 19:10:14 +05:00
parent 2c429fd406
commit 90ae0b3e44
31 changed files with 1018 additions and 240 deletions

View file

@ -146,16 +146,15 @@ void AmneziaApplication::init()
// m_uiLogic->showOnStartup(); // m_uiLogic->showOnStartup();
// #endif // #endif
// // TODO - fix // TODO - fix
// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
// if (isPrimary()) { if (isPrimary()) {
// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
// qDebug() << "Secondary instance started, showing this window instead"; qDebug() << "Secondary instance started, showing this window instead";
// emit m_uiLogic->show(); emit m_pageController->raiseMainWindow();
// emit m_uiLogic->raise(); });
// }); }
// } #endif
// #endif
// Android TextField clipboard workaround // Android TextField clipboard workaround
// https://bugreports.qt.io/browse/QTBUG-113461 // https://bugreports.qt.io/browse/QTBUG-113461
@ -281,6 +280,9 @@ void AmneziaApplication::initModels()
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
@ -328,4 +330,7 @@ void AmneziaApplication::initControllers()
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
} }

View file

@ -19,6 +19,7 @@
#include "ui/controllers/installController.h" #include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h" #include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h" #include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h" #include "ui/models/protocols/cloakConfigModel.h"
@ -32,6 +33,7 @@
#include "ui/models/protocols_model.h" #include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h" #include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@ -86,6 +88,7 @@ private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QScopedPointer<LanguageModel> m_languageModel; QScopedPointer<LanguageModel> m_languageModel;
QScopedPointer<ProtocolsModel> m_protocolsModel; QScopedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel; QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel; QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
@ -106,6 +109,7 @@ private:
QScopedPointer<ImportController> m_importController; QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController; QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController; QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6H21" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 6V20C19 21 18 22 17 22H7C6 22 5 21 5 20V6" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 6V4C8 3 9 2 10 2H14C15 2 16 3 16 4V6" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 476 B

View file

@ -280,5 +280,10 @@
<file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file> <file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file>
<file>images/controls/eye.svg</file> <file>images/controls/eye.svg</file>
<file>images/controls/eye-off.svg</file> <file>images/controls/eye-off.svg</file>
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
<file>ui/qml/Controls2/ContextMenuType.qml</file>
<file>ui/qml/Controls2/TextAreaType.qml</file>
<file>images/controls/trash.svg</file>
<file>images/controls/more-vertical.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -231,14 +231,15 @@ QString Settings::routeModeString(RouteMode mode) const
} }
} }
void Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
{ {
QVariantMap sites = vpnSites(mode); QVariantMap sites = vpnSites(mode);
if (sites.contains(site) && ip.isEmpty()) if (sites.contains(site) && ip.isEmpty())
return; return false;
sites.insert(site, ip); sites.insert(site, ip);
setVpnSites(mode, sites); setVpnSites(mode, sites);
return true;
} }
void Settings::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites) void Settings::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites)
@ -308,6 +309,11 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites)
setVpnSites(mode, sitesMap); setVpnSites(mode, sitesMap);
} }
void Settings::removeAllVpnSites(RouteMode mode)
{
setVpnSites(mode, QVariantMap());
}
QString Settings::primaryDns() const QString Settings::primaryDns() const
{ {
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();

View file

@ -127,13 +127,14 @@ public:
m_settings.setValue("Conf/" + routeModeString(mode), sites); m_settings.setValue("Conf/" + routeModeString(mode), sites);
m_settings.sync(); m_settings.sync();
} }
void addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip> void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
QStringList getVpnIps(RouteMode mode) const; QStringList getVpnIps(RouteMode mode) const;
void removeVpnSite(RouteMode mode, const QString &site); void removeVpnSite(RouteMode mode, const QString &site);
void addVpnIps(RouteMode mode, const QStringList &ip); void addVpnIps(RouteMode mode, const QStringList &ip);
void removeVpnSites(RouteMode mode, const QStringList &sites); void removeVpnSites(RouteMode mode, const QStringList &sites);
void removeAllVpnSites(RouteMode mode);
bool useAmneziaDns() const bool useAmneziaDns() const
{ {

34
client/ui/Controls2 Normal file
View file

@ -0,0 +1,34 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
TextArea {
id: root
width: parent.width
topPadding: 16
leftPadding: 16
color: "#D7D8DB"
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
placeholderTextColor: "#878B91"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
wrapMode: Text.Wrap
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: contextMenu.open()
}
ContextMenuType {
id: contextMenu
textObj: textField
}
}

View file

@ -1,9 +1,9 @@
#ifndef CONNECTIONCONTROLLER_H #ifndef CONNECTIONCONTROLLER_H
#define CONNECTIONCONTROLLER_H #define CONNECTIONCONTROLLER_H
#include "ui/models/servers_model.h"
#include "ui/models/containers_model.h"
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#include "vpnconnection.h" #include "vpnconnection.h"
class ConnectionController : public QObject class ConnectionController : public QObject
@ -17,8 +17,7 @@ public:
explicit ConnectionController(const QSharedPointer<ServersModel> &serversModel, explicit ConnectionController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<VpnConnection> &vpnConnection, const QSharedPointer<VpnConnection> &vpnConnection, QObject *parent = nullptr);
QObject *parent = nullptr);
bool isConnected() const; bool isConnected() const;
bool isConnectionInProgress() const; bool isConnectionInProgress() const;
@ -32,11 +31,12 @@ public slots:
void onConnectionStateChanged(Vpn::ConnectionState state); void onConnectionStateChanged(Vpn::ConnectionState state);
signals: signals:
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig);
void disconnectFromVpn(); void disconnectFromVpn();
void connectionStateChanged(); void connectionStateChanged();
void connectionErrorOccurred(QString errorMessage); void connectionErrorOccurred(const QString &errorMessage);
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;

View file

@ -39,7 +39,7 @@ public slots:
signals: signals:
void generateConfig(int type); void generateConfig(int type);
void exportErrorOccurred(QString errorMessage); void exportErrorOccurred(const QString &errorMessage);
void exportConfigChanged(); void exportConfigChanged();

View file

@ -34,7 +34,7 @@ public slots:
signals: signals:
void importFinished(); void importFinished();
void importErrorOccurred(QString errorMessage); void importErrorOccurred(const QString &errorMessage);
void qrDecodingFinished(); void qrDecodingFinished();

View file

@ -42,18 +42,18 @@ public slots:
void setEncryptedPassphrase(QString passphrase); void setEncryptedPassphrase(QString passphrase);
signals: signals:
void installContainerFinished(QString finishMessage); void installContainerFinished(const QString &finishMessage);
void installServerFinished(QString finishMessage); void installServerFinished(const QString &finishMessage);
void updateContainerFinished(); void updateContainerFinished();
void scanServerFinished(bool isInstalledContainerFound); void scanServerFinished(bool isInstalledContainerFound);
void removeCurrentlyProcessedServerFinished(QString finishedMessage); void removeCurrentlyProcessedServerFinished(const QString &finishedMessage);
void removeAllContainersFinished(QString finishedMessage); void removeAllContainersFinished(const QString &finishedMessage);
void removeCurrentlyProcessedContainerFinished(QString finishedMessage); void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage);
void installationErrorOccurred(QString errorMessage); void installationErrorOccurred(const QString &errorMessage);
void serverAlreadyExists(int serverIndex); void serverAlreadyExists(int serverIndex);

View file

@ -28,6 +28,7 @@ namespace PageLoader
PageSettingsBackup, PageSettingsBackup,
PageSettingsAbout, PageSettingsAbout,
PageSettingsLogging, PageSettingsLogging,
PageSettingsSplitTunneling,
PageServiceSftpSettings, PageServiceSftpSettings,
PageServiceTorWebsiteSettings, PageServiceTorWebsiteSettings,
@ -83,8 +84,8 @@ signals:
void restorePageHomeState(bool isContainerInstalled = false); void restorePageHomeState(bool isContainerInstalled = false);
void replaceStartPage(); void replaceStartPage();
void showErrorMessage(QString errorMessage); void showErrorMessage(const QString &errorMessage);
void showNotificationMessage(QString message); void showNotificationMessage(const QString &message);
void showBusyIndicator(bool visible); void showBusyIndicator(bool visible);

View file

@ -14,7 +14,7 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__);
} }
void SettingsController::setAmneziaDns(bool enable) void SettingsController::toggleAmneziaDns(bool enable)
{ {
m_settings->setUseAmneziaDns(enable); m_settings->setUseAmneziaDns(enable);
} }
@ -46,7 +46,7 @@ void SettingsController::setSecondaryDns(const QString &dns)
emit secondaryDnsChanged(); emit secondaryDnsChanged();
} }
bool SettingsController::isLoggingEnable() bool SettingsController::isLoggingEnabled()
{ {
return m_settings->isSaveLogs(); return m_settings->isSaveLogs();
} }
@ -111,3 +111,13 @@ void SettingsController::clearSettings()
m_settings->clearSettings(); m_settings->clearSettings();
m_serversModel->resetModel(); m_serversModel->resetModel();
} }
bool SettingsController::isAutoConnectEnabled()
{
return m_settings->isAutoConnect();
}
void SettingsController::toggleAutoConnect(bool enable)
{
m_settings->setAutoConnect(enable);
}

View file

@ -16,10 +16,10 @@ public:
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged)
Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged)
public slots: public slots:
void setAmneziaDns(bool enable); void toggleAmneziaDns(bool enable);
bool isAmneziaDnsEnabled(); bool isAmneziaDnsEnabled();
QString getPrimaryDns(); QString getPrimaryDns();
@ -28,7 +28,7 @@ public slots:
QString getSecondaryDns(); QString getSecondaryDns();
void setSecondaryDns(const QString &dns); void setSecondaryDns(const QString &dns);
bool isLoggingEnable(); bool isLoggingEnabled();
void toggleLogging(bool enable); void toggleLogging(bool enable);
void openLogsFolder(); void openLogsFolder();
@ -42,13 +42,16 @@ public slots:
void clearSettings(); void clearSettings();
bool isAutoConnectEnabled();
void toggleAutoConnect(bool enable);
signals: signals:
void primaryDnsChanged(); void primaryDnsChanged();
void secondaryDnsChanged(); void secondaryDnsChanged();
void loggingStateChanged(); void loggingStateChanged();
void restoreBackupFinished(); void restoreBackupFinished();
void changeSettingsErrorOccurred(QString errorMessage); void changeSettingsErrorOccurred(const QString &errorMessage);
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;

View file

@ -0,0 +1,149 @@
#include "sitesController.h"
#include <QHostInfo>
#include "utilities.h"
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
{
}
void SitesController::addSite(QString hostname)
{
if (hostname.isEmpty()) {
return;
}
if (!hostname.contains(".")) {
emit errorOccurred(tr("Hostname not look like ip adress or domain name"));
return;
}
if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
// get domain name if it present
hostname.replace("https://", "");
hostname.replace("http://", "");
hostname.replace("ftp://", "");
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
}
const auto &processSite = [this](const QString &hostname, const QString &ip) {
m_sitesModel->addSite(hostname, ip);
if (!ip.isEmpty()) {
m_vpnConnection->addRoutes(QStringList() << ip);
m_vpnConnection->flushDns();
} else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
m_vpnConnection->addRoutes(QStringList() << hostname);
m_vpnConnection->flushDns();
}
};
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
const QList<QHostAddress> &addresses = hostInfo.addresses();
for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
processSite(hostInfo.hostName(), addr.toString());
break;
}
}
};
if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
processSite(hostname, "");
} else {
processSite(hostname, "");
QHostInfo::lookupHost(hostname, this, resolveCallback);
}
emit finished(tr("New site added: ") + hostname);
}
void SitesController::removeSite(int index)
{
auto modelIndex = m_sitesModel->index(index);
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
m_sitesModel->removeSite(modelIndex);
emit finished(tr("Site removed: ") + hostname);
}
void SitesController::importSites(bool replaceExisting)
{
QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open sites file"),
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
emit errorOccurred(tr("Can't open file: ") + fileName);
return;
}
QByteArray jsonData = file.readAll();
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
if (jsonDocument.isNull()) {
emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName);
return;
}
if (!jsonDocument.isArray()) {
emit errorOccurred(tr("The JSON data is not an array in file: ") + fileName);
return;
}
auto jsonArray = jsonDocument.array();
QMap<QString, QString> sites;
QStringList ips;
for (auto jsonValue : jsonArray) {
auto jsonObject = jsonValue.toObject();
auto hostname = jsonObject.value("hostname").toString("");
auto ip = jsonObject.value("ip").toString("");
if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
qDebug() << hostname << " not look like ip adress or domain name";
continue;
}
if (ip.isEmpty()) {
ips.append(hostname);
} else {
ips.append(ip);
}
sites.insert(hostname, ip);
}
m_sitesModel->addSites(sites, replaceExisting);
m_vpnConnection->addRoutes(QStringList() << ips);
m_vpnConnection->flushDns();
emit finished(tr("Import completed"));
}
void SitesController::exportSites()
{
auto sites = m_sitesModel->getCurrentSites();
QJsonArray jsonArray;
for (const auto &site : sites) {
QJsonObject jsonObject { { "hostname", site.first }, { "ip", site.second } };
jsonArray.append(jsonObject);
}
QJsonDocument jsonDocument(jsonArray);
QByteArray jsonData = jsonDocument.toJson();
Utils::saveFile(".json", tr("Export sites file"), "sites", jsonData);
emit finished(tr("Export completed"));
}

View file

@ -0,0 +1,36 @@
#ifndef SITESCONTROLLER_H
#define SITESCONTROLLER_H
#include <QObject>
#include "settings.h"
#include "ui/models/sites_model.h"
#include "vpnconnection.h"
class SitesController : public QObject
{
Q_OBJECT
public:
explicit SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
public slots:
void addSite(QString hostname);
void removeSite(int index);
void importSites(bool replaceExisting);
void exportSites();
signals:
void errorOccurred(const QString &errorMessage);
void finished(const QString &message);
private:
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<SitesModel> m_sitesModel;
};
#endif // SITESCONTROLLER_H

View file

@ -1,87 +1,118 @@
#include "sites_model.h" #include "sites_model.h"
SitesModel::SitesModel(std::shared_ptr<Settings> settings, Settings::RouteMode mode, QObject *parent) SitesModel::SitesModel(std::shared_ptr<Settings> settings, QObject *parent)
: QAbstractListModel(parent), : QAbstractListModel(parent), m_settings(settings)
m_settings(settings),
m_mode(mode)
{ {
} m_currentRouteMode = m_settings->routeMode();
fillSites();
void SitesModel::resetCache()
{
beginResetModel();
m_ipsCache.clear();
m_cacheReady = false;
endResetModel();
} }
int SitesModel::rowCount(const QModelIndex &parent) const int SitesModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent)
if (!m_cacheReady) genCache(); return m_sites.size();
return m_ipsCache.size();
} }
QVariant SitesModel::data(const QModelIndex &index, int role) const QVariant SitesModel::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid()) if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant(); return QVariant();
if (!m_cacheReady) genCache(); switch (role) {
case UrlRole: {
if (role == SitesModel::UrlRole || role == SitesModel::IpRole) { return m_sites.at(index.row()).first;
if (m_ipsCache.isEmpty()) return QVariant(); break;
}
if (role == SitesModel::UrlRole) { case IpRole: {
return m_ipsCache.at(index.row()).first; return m_sites.at(index.row()).second;
} break;
if (role == SitesModel::IpRole) { }
return m_ipsCache.at(index.row()).second; default: {
} return true;
}
} }
// if (role == Qt::TextAlignmentRole && index.column() == 1) {
// return Qt::AlignRight;
// }
return QVariant(); return QVariant();
} }
QVariant SitesModel::data(int row, int column) bool SitesModel::addSite(const QString &hostname, const QString &ip)
{ {
if (row < 0 || row >= rowCount() || column < 0 || column >= 2) { if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) {
return QVariant(); return false;
} }
if (!m_cacheReady) genCache(); for (int i = 0; i < m_sites.size(); i++) {
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
if (column == 0) { m_sites[i].second = ip;
return m_ipsCache.at(row).first; QModelIndex index = createIndex(i, i);
emit dataChanged(index, index);
return true;
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
return false;
}
} }
if (column == 1) { beginInsertRows(QModelIndex(), rowCount(), rowCount());
return m_ipsCache.at(row).second; m_sites.append(qMakePair(hostname, ip));
} endInsertRows();
return QVariant(); return true;
} }
void SitesModel::genCache() const void SitesModel::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
{ {
qDebug() << "SitesModel::genCache"; beginResetModel();
m_ipsCache.clear();
const QVariantMap &sites = m_settings->vpnSites(m_mode); if (replaceExisting) {
auto i = sites.constBegin(); m_settings->removeAllVpnSites(m_currentRouteMode);
while (i != sites.constEnd()) {
m_ipsCache.append(qMakePair(i.key(), i.value().toString()));
++i;
} }
m_settings->addVpnSites(m_currentRouteMode, sites);
fillSites();
m_cacheReady= true; endResetModel();
} }
QHash<int, QByteArray> SitesModel::roleNames() const { void SitesModel::removeSite(QModelIndex index)
{
auto hostname = m_sites.at(index.row()).first;
beginRemoveRows(QModelIndex(), index.row(), index.row());
m_settings->removeVpnSite(m_currentRouteMode, hostname);
m_sites.removeAt(index.row());
endRemoveRows();
}
int SitesModel::getRouteMode()
{
return m_currentRouteMode;
}
void SitesModel::setRouteMode(int routeMode)
{
beginResetModel();
m_settings->setRouteMode(static_cast<Settings::RouteMode>(routeMode));
m_currentRouteMode = m_settings->routeMode();
fillSites();
endResetModel();
emit routeModeChanged();
}
QVector<QPair<QString, QString> > SitesModel::getCurrentSites()
{
return m_sites;
}
QHash<int, QByteArray> SitesModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[UrlRole] = "url_path"; roles[UrlRole] = "url";
roles[IpRole] = "ip"; roles[IpRole] = "ip";
return roles; return roles;
} }
void SitesModel::fillSites()
{
m_sites.clear();
const QVariantMap &sites = m_settings->vpnSites(m_currentRouteMode);
auto i = sites.constBegin();
while (i != sites.constEnd()) {
m_sites.append(qMakePair(i.key(), i.value().toString()));
++i;
}
}

View file

@ -10,32 +10,43 @@ class SitesModel : public QAbstractListModel
Q_OBJECT Q_OBJECT
public: public:
enum SiteRoles { enum Roles {
UrlRole = Qt::UserRole + 1, UrlRole = Qt::UserRole + 1,
IpRole IpRole
}; };
explicit SitesModel(std::shared_ptr<Settings> settings, Settings::RouteMode mode, QObject *parent = nullptr); explicit SitesModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
void resetCache();
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(int row, int column);
Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged)
public slots:
bool addSite(const QString &hostname, const QString &ip);
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
void removeSite(QModelIndex index);
int getRouteMode();
void setRouteMode(int routeMode);
QVector<QPair<QString, QString>> getCurrentSites();
signals:
void routeModeChanged();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
void genCache() const; void fillSites();
private:
Settings::RouteMode m_mode;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
mutable QVector<QPair<QString, QString>> m_ipsCache; Settings::RouteMode m_currentRouteMode;
mutable bool m_cacheReady = false;
QVector<QPair<QString, QString>> m_sites;
}; };
#endif // SITESMODEL_H #endif // SITESMODEL_H

View file

@ -9,23 +9,24 @@
#include "vpnconnection.h" #include "vpnconnection.h"
#include <functional> #include <functional>
#include "../uilogic.h"
#include "../models/sites_model.h" #include "../models/sites_model.h"
#include "../uilogic.h"
SitesLogic::SitesLogic(UiLogic *logic, QObject *parent): SitesLogic::SitesLogic(UiLogic *logic, QObject *parent)
PageLogicBase(logic, parent), : PageLogicBase(logic, parent),
m_labelSitesAddCustomText{}, m_labelSitesAddCustomText {},
m_tableViewSitesModel{nullptr}, m_tableViewSitesModel { nullptr },
m_lineEditSitesAddCustomText{} m_lineEditSitesAddCustomText {}
{ {
sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); // sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites));
sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); // sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites));
} }
void SitesLogic::onUpdatePage() void SitesLogic::onUpdatePage()
{ {
Settings::RouteMode m = m_settings->routeMode(); Settings::RouteMode m = m_settings->routeMode();
if (m == Settings::VpnAllSites) return; if (m == Settings::VpnAllSites)
return;
if (m == Settings::VpnOnlyForwardSites) { if (m == Settings::VpnOnlyForwardSites) {
set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); set_labelSitesAddCustomText(tr("These sites will be opened using VPN"));
@ -35,7 +36,7 @@ void SitesLogic::onUpdatePage()
} }
set_tableViewSitesModel(sitesModels.value(m)); set_tableViewSitesModel(sitesModels.value(m));
sitesModels.value(m)->resetCache(); // sitesModels.value(m)->resetCache();
} }
void SitesLogic::onPushButtonAddCustomSitesClicked() void SitesLogic::onPushButtonAddCustomSitesClicked()
@ -47,8 +48,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
QString newSite = lineEditSitesAddCustomText(); QString newSite = lineEditSitesAddCustomText();
if (newSite.isEmpty()) return; if (newSite.isEmpty())
if (!newSite.contains(".")) return; return;
if (!newSite.contains("."))
return;
if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
// get domain name if it present // get domain name if it present
@ -65,8 +68,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
if (!ip.isEmpty()) { if (!ip.isEmpty()) {
uiLogic()->m_vpnConnection->addRoutes(QStringList() << ip); uiLogic()->m_vpnConnection->addRoutes(QStringList() << ip);
uiLogic()->m_vpnConnection->flushDns(); uiLogic()->m_vpnConnection->flushDns();
} } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
uiLogic()->m_vpnConnection->addRoutes(QStringList() << newSite); uiLogic()->m_vpnConnection->addRoutes(QStringList() << newSite);
uiLogic()->m_vpnConnection->flushDns(); uiLogic()->m_vpnConnection->flushDns();
} }
@ -74,10 +76,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
onUpdatePage(); onUpdatePage();
}; };
const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo){ const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) {
const QList<QHostAddress> &addresses = hostInfo.addresses(); const QList<QHostAddress> &addresses = hostInfo.addresses();
QString ipv4Addr; QString ipv4Addr;
for (const QHostAddress &addr: hostInfo.addresses()) { for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
cbProcess(hostInfo.hostName(), addr.toString()); cbProcess(hostInfo.hostName(), addr.toString());
break; break;
@ -90,8 +92,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
cbProcess(newSite, ""); cbProcess(newSite, "");
return; return;
} } else {
else {
cbProcess(newSite, ""); cbProcess(newSite, "");
onUpdatePage(); onUpdatePage();
QHostInfo::lookupHost(newSite, this, cbResolv); QHostInfo::lookupHost(newSite, this, cbResolv);
@ -102,7 +103,7 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items)
{ {
Settings::RouteMode mode = m_settings->routeMode(); Settings::RouteMode mode = m_settings->routeMode();
auto siteModel = qobject_cast<SitesModel*> (tableViewSitesModel()); auto siteModel = qobject_cast<SitesModel *>(tableViewSitesModel());
if (!siteModel || items.isEmpty()) { if (!siteModel || items.isEmpty()) {
return; return;
} }
@ -110,14 +111,15 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items)
QStringList sites; QStringList sites;
QStringList ips; QStringList ips;
for (const QString &s: items) { for (const QString &s : items) {
bool ok; bool ok;
int row = s.toInt(&ok); int row = s.toInt(&ok);
if (!ok || row < 0 || row >= siteModel->rowCount()) return; if (!ok || row < 0 || row >= siteModel->rowCount())
sites.append(siteModel->data(row, 0).toString()); return;
// sites.append(siteModel->data(row, 0).toString());
if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) {
ips.append(siteModel->data(row, 1).toString()); // ips.append(siteModel->data(row, 1).toString());
} }
} }
@ -131,11 +133,11 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items)
onUpdatePage(); onUpdatePage();
} }
void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName)
{ {
QFile file(QUrl{fileName}.toLocalFile()); QFile file(QUrl { fileName }.toLocalFile());
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Can't open file " << QUrl{fileName}.toLocalFile(); qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile();
return; return;
} }
@ -164,27 +166,24 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName)
} }
// domain regex cover ip regex, so remove ips from sites // domain regex cover ip regex, so remove ips from sites
for (const QString& ip: line_ips) { for (const QString &ip : line_ips) {
line_sites.removeAll(ip); line_sites.removeAll(ip);
} }
if (line_sites.size() == 1 && line_ips.size() == 1) { if (line_sites.size() == 1 && line_ips.size() == 1) {
sites.insert(line_sites.at(0), line_ips.at(0)); sites.insert(line_sites.at(0), line_ips.at(0));
} } else if (line_sites.size() > 0 && line_ips.size() == 0) {
else if (line_sites.size() > 0 && line_ips.size() == 0) { for (const QString &site : line_sites) {
for (const QString& site: line_sites) {
sites.insert(site, ""); sites.insert(site, "");
} }
} } else {
else { for (const QString &site : line_sites) {
for (const QString& site: line_sites) {
sites.insert(site, ""); sites.insert(site, "");
} }
for (const QString& ip: line_ips) { for (const QString &ip : line_ips) {
ips.append(ip); ips.append(ip);
} }
} }
} }
m_settings->addVpnIps(mode, ips); m_settings->addVpnIps(mode, ips);
@ -208,4 +207,3 @@ void SitesLogic::onPushButtonSitesExportClicked()
} }
uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data);
} }

View file

@ -20,6 +20,8 @@ Button {
property string imageSource property string imageSource
property bool squareLeftSide: false
implicitHeight: 56 implicitHeight: 56
hoverEnabled: true hoverEnabled: true
@ -44,6 +46,32 @@ Button {
Behavior on color { Behavior on color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
} }
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
} }
MouseArea { MouseArea {

View file

@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
Menu {
property var textObj
MenuItem {
text: qsTr("C&ut")
shortcut: StandardKey.Cut
enabled: textObj.selectedText
onTriggered: textObj.cut()
}
MenuItem {
text: qsTr("&Copy")
shortcut: StandardKey.Copy
enabled: textObj.selectedText
onTriggered: textObj.copy()
}
MenuItem {
text: qsTr("&Paste")
shortcut: StandardKey.Paste
enabled: textObj.canPaste
onTriggered: textObj.paste()
}
MenuItem {
text: qsTr("&SelectAll")
shortcut: StandardKey.SelectAll
enabled: textObj.length > 0
onTriggered: textObj.selectAll()
}
}

View file

@ -24,9 +24,11 @@ Item {
property string rootButtonBackgroundColor: "#1C1D21" property string rootButtonBackgroundColor: "#1C1D21"
property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonHoveredBorderColor: "#494B50"
property string rootButtonDefaultBorderColor: "transparent" property string rootButtonDefaultBorderColor: "#2C2D30"
property string rootButtonPressedBorderColor: "#D7D8DB" property string rootButtonPressedBorderColor: "#D7D8DB"
property int rootButtonTextMargins: 16
property real drawerHeight: 0.9 property real drawerHeight: 0.9
property Component listView property Component listView
@ -74,7 +76,9 @@ Item {
spacing: 0 spacing: 0
ColumnLayout { ColumnLayout {
Layout.leftMargin: 16 Layout.leftMargin: rootButtonTextMargins
Layout.topMargin: rootButtonTextMargins
Layout.bottomMargin: rootButtonTextMargins
LabelTextType { LabelTextType {
Layout.fillWidth: true Layout.fillWidth: true
@ -96,16 +100,10 @@ Item {
color: root.enabled ? root.textColor : root.textDisabledColor color: root.enabled ? root.textColor : root.textDisabledColor
text: root.text text: root.text
wrapMode: Text.NoWrap
elide: Text.ElideRight
} }
} }
ImageButtonType { ImageButtonType {
Layout.leftMargin: 4
Layout.rightMargin: 16
hoverEnabled: false hoverEnabled: false
image: rootButtonImage image: rootButtonImage
imageColor: rootButtonImageColor imageColor: rootButtonImageColor

View file

@ -0,0 +1,70 @@
import QtQuick
import QtQuick.Controls
Rectangle {
id: root
property string placeholderText
property string text
property var onEditingFinished
height: 148
color: "#1C1D21"
border.width: 1
border.color: "#2C2D30"
radius: 16
FlickableType {
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: textArea.implicitHeight
TextArea {
id: textArea
width: parent.width
topPadding: 16
leftPadding: 16
anchors.topMargin: 16
anchors.bottomMargin: 16
color: "#D7D8DB"
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
placeholderTextColor: "#878B91"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
placeholderText: root.placeholderText
text: root.text
onEditingFinished: {
if (root.onEditingFinished && typeof root.onEditingFinished === "function") {
root.onEditingFinished()
}
}
wrapMode: Text.Wrap
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: contextMenu.open()
}
ContextMenuType {
id: contextMenu
textObj: textArea
}
}
}
//todo make whole background clickable, with code below we lose ability to select text by mouse
// MouseArea {
// anchors.fill: parent
// cursorShape: Qt.IBeamCursor
// onClicked: textArea.forceActiveFocus()
// }
}

View file

@ -41,7 +41,7 @@ Item {
Rectangle { Rectangle {
id: backgroud id: backgroud
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 74 Layout.preferredHeight: input.implicitHeight
color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor
radius: 16 radius: 16
border.color: textField.focus ? root.borderFocusedColor : root.borderColor border.color: textField.focus ? root.borderFocusedColor : root.borderColor
@ -52,16 +52,17 @@ Item {
} }
RowLayout { RowLayout {
id: input
anchors.fill: backgroud anchors.fill: backgroud
ColumnLayout { ColumnLayout {
Layout.margins: 16
LabelTextType { LabelTextType {
text: root.headerText text: root.headerText
color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor
visible: text !== ""
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
} }
TextField { TextField {
@ -82,9 +83,7 @@ Item {
height: 24 height: 24
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
topPadding: 0 topPadding: 0
rightPadding: 0 rightPadding: 0
leftPadding: 0 leftPadding: 0
@ -98,24 +97,37 @@ Item {
onTextChanged: { onTextChanged: {
root.errorText = "" root.errorText = ""
} }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: contextMenu.open()
}
ContextMenuType {
id: contextMenu
textObj: textField
}
} }
} }
BasicButtonType { BasicButtonType {
visible: (root.buttonText !== "") || (root.buttonImageSource !== "") visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
defaultColor: "transparent" // defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08) // hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12) // pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91" // disabledColor: "#878B91"
textColor: "#D7D8DB" // textColor: "#D7D8DB"
borderWidth: 0 // borderWidth: 0
text: root.buttonText text: root.buttonText
imageSource: root.buttonImageSource imageSource: root.buttonImageSource
Layout.rightMargin: 24 // Layout.rightMargin: 24
Layout.preferredHeight: 32 Layout.preferredHeight: content.implicitHeight
Layout.preferredWidth: content.implicitHeight
squareLeftSide: true
onClicked: { onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") { if (root.clickedFunc && typeof root.clickedFunc === "function") {

View file

@ -148,10 +148,11 @@ PageType {
DropDownType { DropDownType {
id: containersDropDown id: containersDropDown
implicitHeight: 40
rootButtonImageColor: "#0E0E11" rootButtonImageColor: "#0E0E11"
rootButtonBackgroundColor: "#D7D8DB" rootButtonBackgroundColor: "#D7D8DB"
rootButtonHoveredBorderColor: "transparent"
rootButtonPressedBorderColor: "transparent"
rootButtonTextMargins: 8
text: root.defaultContainerName text: root.defaultContainerName
textColor: "#0E0E11" textColor: "#0E0E11"

View file

@ -301,50 +301,18 @@ PageType {
text: qsTr("Additional client configuration commands") text: qsTr("Additional client configuration commands")
} }
Rectangle { TextAreaType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
height: 148
color: "#1C1D21"
border.width: 1
border.color: "#2C2D30"
radius: 16
visible: additionalClientCommandsSwitcher.checked visible: additionalClientCommandsSwitcher.checked
FlickableType { text: additionalClientCommands
anchors.top: parent.top placeholderText: qsTr("Commands:")
anchors.bottom: parent.bottom
contentHeight: additionalClientCommandsTextArea.implicitHeight
TextArea {
id: additionalClientCommandsTextArea
width: parent.width onEditingFinished: {
anchors.topMargin: 16 if (additionalClientCommands !== text) {
anchors.bottomMargin: 16 additionalClientCommands = text
topPadding: 16
leftPadding: 16
color: "#D7D8DB"
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
placeholderTextColor: "#878B91"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
placeholderText: qsTr("Commands:")
text: additionalClientCommands
wrapMode: Text.Wrap
onEditingFinished: {
if (additionalClientCommands !== text) {
additionalClientCommands = text
}
}
} }
} }
} }
@ -359,50 +327,18 @@ PageType {
text: qsTr("Additional server configuration commands") text: qsTr("Additional server configuration commands")
} }
Rectangle { TextAreaType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
height: 148
color: "#1C1D21"
border.width: 1
border.color: "#2C2D30"
radius: 16
visible: additionalServerCommandsSwitcher.checked visible: additionalServerCommandsSwitcher.checked
FlickableType { text: additionalServerCommands
anchors.top: parent.top placeholderText: qsTr("Commands:")
anchors.bottom: parent.bottom
contentHeight: additionalServerCommandsTextArea.implicitHeight
TextArea {
id: additionalServerCommandsTextArea
width: parent.width onEditingFinished: {
anchors.topMargin: 16 if (additionalServerCommands !== text) {
anchors.bottomMargin: 16 additionalServerCommands = text
topPadding: 16
leftPadding: 16
color: "#D7D8DB"
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
placeholderTextColor: "#878B91"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
placeholderText: qsTr("Commands:")
text: additionalServerCommands
wrapMode: Text.Wrap
onEditingFinished: {
if (additionalServerCommands !== text) {
additionalServerCommands = text
}
}
} }
} }
} }

View file

@ -67,7 +67,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Logging") text: qsTr("Logging")
descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {

View file

@ -41,6 +41,24 @@ PageType {
headerText: qsTr("Connection") headerText: qsTr("Connection")
} }
SwitcherType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Auto connect")
descriptionText: qsTr("Connect to VPN on app start")
checked: SettingsController.isAutoConnectEnabled()
onCheckedChanged: {
if (checked !== SettingsController.isAutoConnectEnabled()) {
SettingsController.toggleAutoConnect(checked)
}
}
}
SwitcherType { SwitcherType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
@ -54,7 +72,7 @@ PageType {
checked: SettingsController.isAmneziaDnsEnabled() checked: SettingsController.isAmneziaDnsEnabled()
onCheckedChanged: { onCheckedChanged: {
if (checked !== SettingsController.isAmneziaDnsEnabled()) { if (checked !== SettingsController.isAmneziaDnsEnabled()) {
SettingsController.setAmneziaDns(checked) SettingsController.toggleAmneziaDns(checked)
} }
} }
} }
@ -83,6 +101,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
goToPage(PageEnum.PageSettingsSplitTunneling)
} }
} }

View file

@ -50,10 +50,10 @@ PageType {
text: qsTr("Save logs") text: qsTr("Save logs")
checked: SettingsController.isLoggingEnable checked: SettingsController.isLoggingEnabled
onCheckedChanged: { onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnable) { if (checked !== SettingsController.isLoggingEnabled) {
SettingsController.isLoggingEnable = checked SettingsController.isLoggingEnabled = checked
} }
} }
} }

View file

@ -0,0 +1,377 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
Connections {
target: SitesController
function onFinished(message) {
PageController.showNotificationMessage(message)
}
function onErrorOccurred(errorMessage) {
PageController.showErrorMessage(errorMessage)
}
}
QtObject {
id: routeMode
property int allSites: 0
property int onlyForwardSites: 1
property int allExceptSites: 2
}
property list<QtObject> routeModesModel: [
onlyForwardSites,
allExceptSites
]
QtObject {
id: onlyForwardSites
property string name: qsTr("Addresses from the list should always open via VPN")
property int type: routeMode.onlyForwardSites
}
QtObject {
id: allExceptSites
property string name: qsTr("Addresses from the list should never be opened via VPN")
property int type: routeMode.allExceptSites
}
function getRouteModesModelIndex() {
var currentRouteMode = SitesModel.routeMode
if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) {
return 0
} else if (routeMode.allExceptSites === currentRouteMode) {
return 1
}
}
ColumnLayout {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
BackButtonType {
}
RowLayout {
HeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
headerText: qsTr("Split site tunneling")
}
SwitcherType {
id: switcher
property int lastActiveRouteMode: routeMode.onlyForwardSites
Layout.fillWidth: true
Layout.rightMargin: 16
checked: SitesModel.routeMode !== routeMode.allSites
onToggled: {
if (checked) {
SitesModel.routeMode = lastActiveRouteMode
} else {
lastActiveRouteMode = SitesModel.routeMode
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
SitesModel.routeMode = routeMode.allSites
}
}
}
}
DropDownType {
id: selector
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
drawerHeight: 0.4375
enabled: switcher.checked
headerText: qsTr("Mode")
listView: ListViewType {
rootWidth: root.width
model: root.routeModesModel
currentIndex: getRouteModesModelIndex()
clickedFunction: function() {
selector.text = selectedText
selector.menuVisible = false
if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) {
SitesModel.routeMode = root.routeModesModel[currentIndex].type
}
}
Component.onCompleted: {
if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) {
selector.text = selectedText
} else {
selector.text = root.routeModesModel[0].name
}
}
Connections {
target: SitesModel
function onRouteModeChanged() {
currentIndex = getRouteModesModelIndex()
}
}
}
}
}
FlickableType {
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin
enabled: switcher.checked
Column {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView {
id: sites
width: parent.width
height: sites.contentItem.height
model: SitesModel
clip: true
interactive: false
delegate: Item {
implicitWidth: sites.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelWithButtonType {
Layout.fillWidth: true
text: url
descriptionText: ip
rightImageSource: "qrc:/images/controls/trash.svg"
rightImageColor: "#D7D8DB"
clickedFunction: function() {
questionDrawer.headerText = qsTr("Remove ") + url + "?"
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.visible = false
SitesController.removeSite(index)
}
questionDrawer.noButtonFunction = function() {
questionDrawer.visible = false
}
questionDrawer.visible = true
}
}
DividerType {}
QuestionDrawer {
id: questionDrawer
}
}
}
}
}
}
RowLayout {
id: connectButton
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
TextFieldWithHeaderType {
Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Site or IP")
buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() {
SitesController.addSite(textFieldText)
textFieldText = ""
}
}
ImageButtonType {
implicitWidth: 56
implicitHeight: 56
image: "qrc:/images/controls/more-vertical.svg"
imageColor: "#D7D8DB"
onClicked: function () {
moreActionsDrawer.open()
}
}
}
DrawerType {
id: moreActionsDrawer
width: parent.width
height: parent.height * 0.4375
FlickableType {
anchors.fill: parent
contentHeight: moreActionsDrawerContent.height
ColumnLayout {
id: moreActionsDrawerContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
Header2Type {
Layout.fillWidth: true
Layout.margins: 16
headerText: qsTr("Import/Export Sites")
}
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Import")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
importSitesDrawer.open()
}
}
DividerType {}
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Save site list")
clickedFunction: function() {
SitesController.exportSites()
moreActionsDrawer.close()
}
}
DividerType {}
}
}
}
DrawerType {
id: importSitesDrawer
width: parent.width
height: parent.height * 0.4375
BackButtonType {
id: importSitesDrawerBackButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
backButtonFunction: function() {
importSitesDrawer.close()
}
}
FlickableType {
anchors.top: importSitesDrawerBackButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
contentHeight: importSitesDrawerContent.height
ColumnLayout {
id: importSitesDrawerContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
Header2Type {
Layout.fillWidth: true
Layout.margins: 16
headerText: qsTr("Import a list of sites")
}
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Replace site list")
clickedFunction: function() {
SitesController.importSites(true)
importSitesDrawer.close()
moreActionsDrawer.close()
}
}
DividerType {}
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Add imported sites to existing ones")
clickedFunction: function() {
SitesController.importSites(false)
importSitesDrawer.close()
moreActionsDrawer.close()
}
}
DividerType {}
}
}
}
}