added PageSettingsSplitTunneling
- added a call to the context menu when clicking the right mouse button for textInput
This commit is contained in:
parent
2c429fd406
commit
90ae0b3e44
31 changed files with 1018 additions and 240 deletions
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
5
client/images/controls/more-vertical.svg
Normal file
5
client/images/controls/more-vertical.svg
Normal 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 |
5
client/images/controls/trash.svg
Normal file
5
client/images/controls/trash.svg
Normal 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 |
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
34
client/ui/Controls2
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
149
client/ui/controllers/sitesController.cpp
Normal file
149
client/ui/controllers/sitesController.cpp
Normal 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"));
|
||||||
|
}
|
36
client/ui/controllers/sitesController.h
Normal file
36
client/ui/controllers/sitesController.h
Normal 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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
33
client/ui/qml/Controls2/ContextMenuType.qml
Normal file
33
client/ui/qml/Controls2/ContextMenuType.qml
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
70
client/ui/qml/Controls2/TextAreaType.qml
Normal file
70
client/ui/qml/Controls2/TextAreaType.qml
Normal 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()
|
||||||
|
// }
|
||||||
|
}
|
|
@ -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") {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
377
client/ui/qml/Pages2/PageSettingsSplitTunneling.qml
Normal file
377
client/ui/qml/Pages2/PageSettingsSplitTunneling.qml
Normal 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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue