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();
|
||||
// #endif
|
||||
|
||||
// // TODO - fix
|
||||
// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// if (isPrimary()) {
|
||||
// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){
|
||||
// qDebug() << "Secondary instance started, showing this window instead";
|
||||
// emit m_uiLogic->show();
|
||||
// emit m_uiLogic->raise();
|
||||
// });
|
||||
// }
|
||||
// #endif
|
||||
// TODO - fix
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (isPrimary()) {
|
||||
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// Android TextField clipboard workaround
|
||||
// https://bugreports.qt.io/browse/QTBUG-113461
|
||||
|
@ -281,6 +280,9 @@ void AmneziaApplication::initModels()
|
|||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
|
||||
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_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_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/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
|
@ -32,6 +33,7 @@
|
|||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
|
@ -86,6 +88,7 @@ private:
|
|||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QScopedPointer<LanguageModel> m_languageModel;
|
||||
QScopedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
|
@ -106,6 +109,7 @@ private:
|
|||
QScopedPointer<ImportController> m_importController;
|
||||
QScopedPointer<ExportController> m_exportController;
|
||||
QScopedPointer<SettingsController> m_settingsController;
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
};
|
||||
|
||||
#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>images/controls/eye.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>
|
||||
</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);
|
||||
if (sites.contains(site) && ip.isEmpty())
|
||||
return;
|
||||
return false;
|
||||
|
||||
sites.insert(site, ip);
|
||||
setVpnSites(mode, sites);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Settings::removeAllVpnSites(RouteMode mode)
|
||||
{
|
||||
setVpnSites(mode, QVariantMap());
|
||||
}
|
||||
|
||||
QString Settings::primaryDns() const
|
||||
{
|
||||
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
|
||||
|
|
|
@ -127,13 +127,14 @@ public:
|
|||
m_settings.setValue("Conf/" + routeModeString(mode), sites);
|
||||
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>
|
||||
QStringList getVpnIps(RouteMode mode) const;
|
||||
void removeVpnSite(RouteMode mode, const QString &site);
|
||||
|
||||
void addVpnIps(RouteMode mode, const QStringList &ip);
|
||||
void removeVpnSites(RouteMode mode, const QStringList &sites);
|
||||
void removeAllVpnSites(RouteMode mode);
|
||||
|
||||
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
|
||||
#define CONNECTIONCONTROLLER_H
|
||||
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "protocols/vpnprotocol.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "vpnconnection.h"
|
||||
|
||||
class ConnectionController : public QObject
|
||||
|
@ -17,8 +17,7 @@ public:
|
|||
|
||||
explicit ConnectionController(const QSharedPointer<ServersModel> &serversModel,
|
||||
const QSharedPointer<ContainersModel> &containersModel,
|
||||
const QSharedPointer<VpnConnection> &vpnConnection,
|
||||
QObject *parent = nullptr);
|
||||
const QSharedPointer<VpnConnection> &vpnConnection, QObject *parent = nullptr);
|
||||
|
||||
bool isConnected() const;
|
||||
bool isConnectionInProgress() const;
|
||||
|
@ -32,11 +31,12 @@ public slots:
|
|||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
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 connectionStateChanged();
|
||||
|
||||
void connectionErrorOccurred(QString errorMessage);
|
||||
void connectionErrorOccurred(const QString &errorMessage);
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
|
|
|
@ -39,7 +39,7 @@ public slots:
|
|||
|
||||
signals:
|
||||
void generateConfig(int type);
|
||||
void exportErrorOccurred(QString errorMessage);
|
||||
void exportErrorOccurred(const QString &errorMessage);
|
||||
|
||||
void exportConfigChanged();
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ public slots:
|
|||
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(QString errorMessage);
|
||||
void importErrorOccurred(const QString &errorMessage);
|
||||
|
||||
void qrDecodingFinished();
|
||||
|
||||
|
|
|
@ -42,18 +42,18 @@ public slots:
|
|||
void setEncryptedPassphrase(QString passphrase);
|
||||
|
||||
signals:
|
||||
void installContainerFinished(QString finishMessage);
|
||||
void installServerFinished(QString finishMessage);
|
||||
void installContainerFinished(const QString &finishMessage);
|
||||
void installServerFinished(const QString &finishMessage);
|
||||
|
||||
void updateContainerFinished();
|
||||
|
||||
void scanServerFinished(bool isInstalledContainerFound);
|
||||
|
||||
void removeCurrentlyProcessedServerFinished(QString finishedMessage);
|
||||
void removeAllContainersFinished(QString finishedMessage);
|
||||
void removeCurrentlyProcessedContainerFinished(QString finishedMessage);
|
||||
void removeCurrentlyProcessedServerFinished(const QString &finishedMessage);
|
||||
void removeAllContainersFinished(const QString &finishedMessage);
|
||||
void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage);
|
||||
|
||||
void installationErrorOccurred(QString errorMessage);
|
||||
void installationErrorOccurred(const QString &errorMessage);
|
||||
|
||||
void serverAlreadyExists(int serverIndex);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace PageLoader
|
|||
PageSettingsBackup,
|
||||
PageSettingsAbout,
|
||||
PageSettingsLogging,
|
||||
PageSettingsSplitTunneling,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
@ -83,8 +84,8 @@ signals:
|
|||
void restorePageHomeState(bool isContainerInstalled = false);
|
||||
void replaceStartPage();
|
||||
|
||||
void showErrorMessage(QString errorMessage);
|
||||
void showNotificationMessage(QString message);
|
||||
void showErrorMessage(const QString &errorMessage);
|
||||
void showNotificationMessage(const QString &message);
|
||||
|
||||
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__);
|
||||
}
|
||||
|
||||
void SettingsController::setAmneziaDns(bool enable)
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
{
|
||||
m_settings->setUseAmneziaDns(enable);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ void SettingsController::setSecondaryDns(const QString &dns)
|
|||
emit secondaryDnsChanged();
|
||||
}
|
||||
|
||||
bool SettingsController::isLoggingEnable()
|
||||
bool SettingsController::isLoggingEnabled()
|
||||
{
|
||||
return m_settings->isSaveLogs();
|
||||
}
|
||||
|
@ -111,3 +111,13 @@ void SettingsController::clearSettings()
|
|||
m_settings->clearSettings();
|
||||
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 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:
|
||||
void setAmneziaDns(bool enable);
|
||||
void toggleAmneziaDns(bool enable);
|
||||
bool isAmneziaDnsEnabled();
|
||||
|
||||
QString getPrimaryDns();
|
||||
|
@ -28,7 +28,7 @@ public slots:
|
|||
QString getSecondaryDns();
|
||||
void setSecondaryDns(const QString &dns);
|
||||
|
||||
bool isLoggingEnable();
|
||||
bool isLoggingEnabled();
|
||||
void toggleLogging(bool enable);
|
||||
|
||||
void openLogsFolder();
|
||||
|
@ -42,13 +42,16 @@ public slots:
|
|||
|
||||
void clearSettings();
|
||||
|
||||
bool isAutoConnectEnabled();
|
||||
void toggleAutoConnect(bool enable);
|
||||
|
||||
signals:
|
||||
void primaryDnsChanged();
|
||||
void secondaryDnsChanged();
|
||||
void loggingStateChanged();
|
||||
|
||||
void restoreBackupFinished();
|
||||
void changeSettingsErrorOccurred(QString errorMessage);
|
||||
void changeSettingsErrorOccurred(const QString &errorMessage);
|
||||
|
||||
private:
|
||||
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"
|
||||
|
||||
SitesModel::SitesModel(std::shared_ptr<Settings> settings, Settings::RouteMode mode, QObject *parent)
|
||||
: QAbstractListModel(parent),
|
||||
m_settings(settings),
|
||||
m_mode(mode)
|
||||
SitesModel::SitesModel(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: QAbstractListModel(parent), m_settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void SitesModel::resetCache()
|
||||
{
|
||||
beginResetModel();
|
||||
m_ipsCache.clear();
|
||||
m_cacheReady = false;
|
||||
endResetModel();
|
||||
m_currentRouteMode = m_settings->routeMode();
|
||||
fillSites();
|
||||
}
|
||||
|
||||
int SitesModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (!m_cacheReady) genCache();
|
||||
return m_ipsCache.size();
|
||||
return m_sites.size();
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
|
||||
if (!m_cacheReady) genCache();
|
||||
|
||||
if (role == SitesModel::UrlRole || role == SitesModel::IpRole) {
|
||||
if (m_ipsCache.isEmpty()) return QVariant();
|
||||
|
||||
if (role == SitesModel::UrlRole) {
|
||||
return m_ipsCache.at(index.row()).first;
|
||||
}
|
||||
if (role == SitesModel::IpRole) {
|
||||
return m_ipsCache.at(index.row()).second;
|
||||
}
|
||||
switch (role) {
|
||||
case UrlRole: {
|
||||
return m_sites.at(index.row()).first;
|
||||
break;
|
||||
}
|
||||
case IpRole: {
|
||||
return m_sites.at(index.row()).second;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if (role == Qt::TextAlignmentRole && index.column() == 1) {
|
||||
// return Qt::AlignRight;
|
||||
// }
|
||||
|
||||
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) {
|
||||
return QVariant();
|
||||
if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_cacheReady) genCache();
|
||||
|
||||
if (column == 0) {
|
||||
return m_ipsCache.at(row).first;
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
|
||||
m_sites[i].second = ip;
|
||||
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) {
|
||||
return m_ipsCache.at(row).second;
|
||||
}
|
||||
return QVariant();
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SitesModel::genCache() const
|
||||
void SitesModel::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
|
||||
{
|
||||
qDebug() << "SitesModel::genCache";
|
||||
m_ipsCache.clear();
|
||||
beginResetModel();
|
||||
|
||||
const QVariantMap &sites = m_settings->vpnSites(m_mode);
|
||||
auto i = sites.constBegin();
|
||||
while (i != sites.constEnd()) {
|
||||
m_ipsCache.append(qMakePair(i.key(), i.value().toString()));
|
||||
++i;
|
||||
if (replaceExisting) {
|
||||
m_settings->removeAllVpnSites(m_currentRouteMode);
|
||||
}
|
||||
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;
|
||||
roles[UrlRole] = "url_path";
|
||||
roles[UrlRole] = "url";
|
||||
roles[IpRole] = "ip";
|
||||
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
|
||||
|
||||
public:
|
||||
enum SiteRoles {
|
||||
enum Roles {
|
||||
UrlRole = Qt::UserRole + 1,
|
||||
IpRole
|
||||
};
|
||||
|
||||
explicit SitesModel(std::shared_ptr<Settings> settings, Settings::RouteMode mode, QObject *parent = nullptr);
|
||||
void resetCache();
|
||||
explicit SitesModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) 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:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
void genCache() const;
|
||||
void fillSites();
|
||||
|
||||
private:
|
||||
Settings::RouteMode m_mode;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
mutable QVector<QPair<QString, QString>> m_ipsCache;
|
||||
mutable bool m_cacheReady = false;
|
||||
Settings::RouteMode m_currentRouteMode;
|
||||
|
||||
QVector<QPair<QString, QString>> m_sites;
|
||||
};
|
||||
|
||||
#endif // SITESMODEL_H
|
||||
|
|
|
@ -9,23 +9,24 @@
|
|||
#include "vpnconnection.h"
|
||||
#include <functional>
|
||||
|
||||
#include "../uilogic.h"
|
||||
#include "../models/sites_model.h"
|
||||
#include "../uilogic.h"
|
||||
|
||||
SitesLogic::SitesLogic(UiLogic *logic, QObject *parent):
|
||||
PageLogicBase(logic, parent),
|
||||
m_labelSitesAddCustomText{},
|
||||
m_tableViewSitesModel{nullptr},
|
||||
m_lineEditSitesAddCustomText{}
|
||||
SitesLogic::SitesLogic(UiLogic *logic, QObject *parent)
|
||||
: PageLogicBase(logic, parent),
|
||||
m_labelSitesAddCustomText {},
|
||||
m_tableViewSitesModel { nullptr },
|
||||
m_lineEditSitesAddCustomText {}
|
||||
{
|
||||
sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites));
|
||||
sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites));
|
||||
// sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites));
|
||||
// sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites));
|
||||
}
|
||||
|
||||
void SitesLogic::onUpdatePage()
|
||||
{
|
||||
Settings::RouteMode m = m_settings->routeMode();
|
||||
if (m == Settings::VpnAllSites) return;
|
||||
if (m == Settings::VpnAllSites)
|
||||
return;
|
||||
|
||||
if (m == Settings::VpnOnlyForwardSites) {
|
||||
set_labelSitesAddCustomText(tr("These sites will be opened using VPN"));
|
||||
|
@ -35,7 +36,7 @@ void SitesLogic::onUpdatePage()
|
|||
}
|
||||
|
||||
set_tableViewSitesModel(sitesModels.value(m));
|
||||
sitesModels.value(m)->resetCache();
|
||||
// sitesModels.value(m)->resetCache();
|
||||
}
|
||||
|
||||
void SitesLogic::onPushButtonAddCustomSitesClicked()
|
||||
|
@ -47,8 +48,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
|
|||
|
||||
QString newSite = lineEditSitesAddCustomText();
|
||||
|
||||
if (newSite.isEmpty()) return;
|
||||
if (!newSite.contains(".")) return;
|
||||
if (newSite.isEmpty())
|
||||
return;
|
||||
if (!newSite.contains("."))
|
||||
return;
|
||||
|
||||
if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
|
||||
// get domain name if it present
|
||||
|
@ -65,8 +68,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
|
|||
if (!ip.isEmpty()) {
|
||||
uiLogic()->m_vpnConnection->addRoutes(QStringList() << ip);
|
||||
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->flushDns();
|
||||
}
|
||||
|
@ -74,10 +76,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
|
|||
onUpdatePage();
|
||||
};
|
||||
|
||||
const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo){
|
||||
const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) {
|
||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||
QString ipv4Addr;
|
||||
for (const QHostAddress &addr: hostInfo.addresses()) {
|
||||
for (const QHostAddress &addr : hostInfo.addresses()) {
|
||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||
cbProcess(hostInfo.hostName(), addr.toString());
|
||||
break;
|
||||
|
@ -90,8 +92,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked()
|
|||
if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) {
|
||||
cbProcess(newSite, "");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
cbProcess(newSite, "");
|
||||
onUpdatePage();
|
||||
QHostInfo::lookupHost(newSite, this, cbResolv);
|
||||
|
@ -102,7 +103,7 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items)
|
|||
{
|
||||
Settings::RouteMode mode = m_settings->routeMode();
|
||||
|
||||
auto siteModel = qobject_cast<SitesModel*> (tableViewSitesModel());
|
||||
auto siteModel = qobject_cast<SitesModel *>(tableViewSitesModel());
|
||||
if (!siteModel || items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -110,14 +111,15 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items)
|
|||
QStringList sites;
|
||||
QStringList ips;
|
||||
|
||||
for (const QString &s: items) {
|
||||
for (const QString &s : items) {
|
||||
bool ok;
|
||||
int row = s.toInt(&ok);
|
||||
if (!ok || row < 0 || row >= siteModel->rowCount()) return;
|
||||
sites.append(siteModel->data(row, 0).toString());
|
||||
if (!ok || row < 0 || row >= siteModel->rowCount())
|
||||
return;
|
||||
// sites.append(siteModel->data(row, 0).toString());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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)) {
|
||||
qDebug() << "Can't open file " << QUrl{fileName}.toLocalFile();
|
||||
qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,27 +166,24 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName)
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (line_sites.size() == 1 && line_ips.size() == 1) {
|
||||
sites.insert(line_sites.at(0), line_ips.at(0));
|
||||
}
|
||||
else if (line_sites.size() > 0 && line_ips.size() == 0) {
|
||||
for (const QString& site: line_sites) {
|
||||
} else if (line_sites.size() > 0 && line_ips.size() == 0) {
|
||||
for (const QString &site : line_sites) {
|
||||
sites.insert(site, "");
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const QString& site: line_sites) {
|
||||
} else {
|
||||
for (const QString &site : line_sites) {
|
||||
sites.insert(site, "");
|
||||
}
|
||||
for (const QString& ip: line_ips) {
|
||||
for (const QString &ip : line_ips) {
|
||||
ips.append(ip);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m_settings->addVpnIps(mode, ips);
|
||||
|
@ -208,4 +207,3 @@ void SitesLogic::onPushButtonSitesExportClicked()
|
|||
}
|
||||
uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ Button {
|
|||
|
||||
property string imageSource
|
||||
|
||||
property bool squareLeftSide: false
|
||||
|
||||
implicitHeight: 56
|
||||
|
||||
hoverEnabled: true
|
||||
|
@ -44,6 +46,32 @@ Button {
|
|||
Behavior on color {
|
||||
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 {
|
||||
|
|
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 rootButtonHoveredBorderColor: "#494B50"
|
||||
property string rootButtonDefaultBorderColor: "transparent"
|
||||
property string rootButtonDefaultBorderColor: "#2C2D30"
|
||||
property string rootButtonPressedBorderColor: "#D7D8DB"
|
||||
|
||||
property int rootButtonTextMargins: 16
|
||||
|
||||
property real drawerHeight: 0.9
|
||||
property Component listView
|
||||
|
||||
|
@ -74,7 +76,9 @@ Item {
|
|||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
Layout.leftMargin: 16
|
||||
Layout.leftMargin: rootButtonTextMargins
|
||||
Layout.topMargin: rootButtonTextMargins
|
||||
Layout.bottomMargin: rootButtonTextMargins
|
||||
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
|
@ -96,16 +100,10 @@ Item {
|
|||
|
||||
color: root.enabled ? root.textColor : root.textDisabledColor
|
||||
text: root.text
|
||||
|
||||
wrapMode: Text.NoWrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
Layout.leftMargin: 4
|
||||
Layout.rightMargin: 16
|
||||
|
||||
hoverEnabled: false
|
||||
image: rootButtonImage
|
||||
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 {
|
||||
id: backgroud
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 74
|
||||
Layout.preferredHeight: input.implicitHeight
|
||||
color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor
|
||||
radius: 16
|
||||
border.color: textField.focus ? root.borderFocusedColor : root.borderColor
|
||||
|
@ -52,16 +52,17 @@ Item {
|
|||
}
|
||||
|
||||
RowLayout {
|
||||
id: input
|
||||
anchors.fill: backgroud
|
||||
ColumnLayout {
|
||||
Layout.margins: 16
|
||||
LabelTextType {
|
||||
text: root.headerText
|
||||
color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor
|
||||
|
||||
visible: text !== ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.topMargin: 16
|
||||
}
|
||||
|
||||
TextField {
|
||||
|
@ -82,9 +83,7 @@ Item {
|
|||
|
||||
height: 24
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
|
@ -98,24 +97,37 @@ Item {
|
|||
onTextChanged: {
|
||||
root.errorText = ""
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: contextMenu.open()
|
||||
}
|
||||
|
||||
ContextMenuType {
|
||||
id: contextMenu
|
||||
textObj: textField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#D7D8DB"
|
||||
borderWidth: 0
|
||||
// defaultColor: "transparent"
|
||||
// hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
// pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
// disabledColor: "#878B91"
|
||||
// textColor: "#D7D8DB"
|
||||
// borderWidth: 0
|
||||
|
||||
text: root.buttonText
|
||||
imageSource: root.buttonImageSource
|
||||
|
||||
Layout.rightMargin: 24
|
||||
Layout.preferredHeight: 32
|
||||
// Layout.rightMargin: 24
|
||||
Layout.preferredHeight: content.implicitHeight
|
||||
Layout.preferredWidth: content.implicitHeight
|
||||
squareLeftSide: true
|
||||
|
||||
onClicked: {
|
||||
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
||||
|
|
|
@ -148,10 +148,11 @@ PageType {
|
|||
DropDownType {
|
||||
id: containersDropDown
|
||||
|
||||
implicitHeight: 40
|
||||
|
||||
rootButtonImageColor: "#0E0E11"
|
||||
rootButtonBackgroundColor: "#D7D8DB"
|
||||
rootButtonHoveredBorderColor: "transparent"
|
||||
rootButtonPressedBorderColor: "transparent"
|
||||
rootButtonTextMargins: 8
|
||||
|
||||
text: root.defaultContainerName
|
||||
textColor: "#0E0E11"
|
||||
|
|
|
@ -301,50 +301,18 @@ PageType {
|
|||
text: qsTr("Additional client configuration commands")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
TextAreaType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
height: 148
|
||||
color: "#1C1D21"
|
||||
border.width: 1
|
||||
border.color: "#2C2D30"
|
||||
radius: 16
|
||||
visible: additionalClientCommandsSwitcher.checked
|
||||
|
||||
FlickableType {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: additionalClientCommandsTextArea.implicitHeight
|
||||
TextArea {
|
||||
id: additionalClientCommandsTextArea
|
||||
text: additionalClientCommands
|
||||
placeholderText: qsTr("Commands:")
|
||||
|
||||
width: parent.width
|
||||
anchors.topMargin: 16
|
||||
anchors.bottomMargin: 16
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
onEditingFinished: {
|
||||
if (additionalClientCommands !== text) {
|
||||
additionalClientCommands = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,50 +327,18 @@ PageType {
|
|||
text: qsTr("Additional server configuration commands")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
TextAreaType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
height: 148
|
||||
color: "#1C1D21"
|
||||
border.width: 1
|
||||
border.color: "#2C2D30"
|
||||
radius: 16
|
||||
visible: additionalServerCommandsSwitcher.checked
|
||||
|
||||
FlickableType {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: additionalServerCommandsTextArea.implicitHeight
|
||||
TextArea {
|
||||
id: additionalServerCommandsTextArea
|
||||
text: additionalServerCommands
|
||||
placeholderText: qsTr("Commands:")
|
||||
|
||||
width: parent.width
|
||||
anchors.topMargin: 16
|
||||
anchors.bottomMargin: 16
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
onEditingFinished: {
|
||||
if (additionalServerCommands !== text) {
|
||||
additionalServerCommands = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ PageType {
|
|||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Logging")
|
||||
descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled")
|
||||
descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
|
|
|
@ -41,6 +41,24 @@ PageType {
|
|||
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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
@ -54,7 +72,7 @@ PageType {
|
|||
checked: SettingsController.isAmneziaDnsEnabled()
|
||||
onCheckedChanged: {
|
||||
if (checked !== SettingsController.isAmneziaDnsEnabled()) {
|
||||
SettingsController.setAmneziaDns(checked)
|
||||
SettingsController.toggleAmneziaDns(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +101,7 @@ PageType {
|
|||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
goToPage(PageEnum.PageSettingsSplitTunneling)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,10 +50,10 @@ PageType {
|
|||
|
||||
text: qsTr("Save logs")
|
||||
|
||||
checked: SettingsController.isLoggingEnable
|
||||
checked: SettingsController.isLoggingEnabled
|
||||
onCheckedChanged: {
|
||||
if (checked !== SettingsController.isLoggingEnable) {
|
||||
SettingsController.isLoggingEnable = checked
|
||||
if (checked !== SettingsController.isLoggingEnabled) {
|
||||
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