added separation for read/write and readonly servers for pageSettingsServerProtocols, PageSettingsServerServices, PageSettingsServerData

- added fields validations for pageSetupWizardCredentials
This commit is contained in:
vladimir.kuznetsov 2023-06-23 15:24:40 +09:00
parent 249be451f7
commit 2ef53c6df9
22 changed files with 466 additions and 325 deletions

View file

@ -1,29 +1,28 @@
#include "amnezia_application.h"
#include <QFontDatabase>
#include <QQuickStyle>
#include <QStandardPaths>
#include <QTimer>
#include <QTranslator>
#include <QQuickStyle>
#include "logger.h"
#include "defines.h"
#include "logger.h"
#include "platforms/ios/QRCodeReaderBase.h"
#include "ui/pages.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]):
AMNEZIA_BASE_CLASS(argc, argv)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary,
SingleApplication::Options options, int timeout, const QString &userData):
SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
int timeout, const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif
{
setQuitOnLastWindowClosed(false);
@ -51,12 +50,14 @@
AmneziaApplication::~AmneziaApplication()
{
if (m_engine) {
QObject::disconnect(m_engine, 0,0,0);
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
if (m_protocolProps) delete m_protocolProps;
if (m_containerProps) delete m_containerProps;
if (m_protocolProps)
delete m_protocolProps;
if (m_containerProps)
delete m_containerProps;
}
void AmneziaApplication::init()
@ -64,11 +65,13 @@ void AmneziaApplication::init()
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated,
this, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
@ -78,6 +81,8 @@ void AmneziaApplication::init()
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(),
&ContainersModel::setCurrentlyProcessedServerIndex);
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
@ -94,21 +99,19 @@ void AmneziaApplication::init()
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(
new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
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->load(url);
// if (m_engine->rootObjects().size() > 0) {
// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0));
// }
// if (m_engine->rootObjects().size() > 0) {
// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0));
// }
if (m_settings->isSaveLogs()) {
if (!Logger::init()) {
@ -116,24 +119,23 @@ void AmneziaApplication::init()
}
}
//#ifdef Q_OS_WIN
// if (m_parser.isSet("a")) m_uiLogic->showOnStartup();
// else emit m_uiLogic->show();
//#else
// 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
// #ifdef Q_OS_WIN
// if (m_parser.isSet("a")) m_uiLogic->showOnStartup();
// else emit m_uiLogic->show();
// #else
// 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
}
void AmneziaApplication::registerTypes()
@ -156,6 +158,9 @@ void AmneziaApplication::registerTypes()
m_protocolProps = new ProtocolProps;
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps);
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
//
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
@ -182,19 +187,17 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"};
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"};
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this]{
quit();
});
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
@ -205,4 +208,3 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}

View file

@ -269,5 +269,6 @@
<file>images/controls/mail.svg</file>
<file>images/controls/telegram.svg</file>
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
<file>ui/qml/Filters/ContainersModelFilters.qml</file>
</qresource>
</RCC>

View file

@ -16,14 +16,14 @@
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator,
QObject *parent)
: QObject(parent)
, m_serversModel(serversModel)
, m_containersModel(containersModel)
, m_settings(settings)
, m_configurator(configurator)
{}
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent)
: QObject(parent),
m_serversModel(serversModel),
m_containersModel(containersModel),
m_settings(settings),
m_configurator(configurator)
{
}
void ExportController::generateFullAccessConfig()
{
@ -33,8 +33,8 @@ void ExportController::generateFullAccessConfig()
QByteArray compressedConfig = QJsonDocument(config).toJson();
compressedConfig = qCompress(compressedConfig, 8);
m_amneziaCode = QString("vpn://%1")
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged();
@ -43,25 +43,21 @@ void ExportController::generateFullAccessConfig()
void ExportController::generateConnectionConfig()
{
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials = qvariant_cast<ServerCredentials>(
m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
DockerContainer container = static_cast<DockerContainer>(
m_containersModel->getCurrentlyProcessedContainerIndex());
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig = qvariant_cast<QJsonObject>(
m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
QJsonObject containerConfig =
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode errorCode = ErrorCode::NoError;
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol);
QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials,
container,
containerConfig,
protocol,
&errorCode);
QString vpnConfig =
m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
@ -75,7 +71,7 @@ void ExportController::generateConnectionConfig()
config.remove(config_key::userName);
config.remove(config_key::password);
config.remove(config_key::port);
config.insert(config_key::containers, QJsonArray{containerConfig});
config.insert(config_key::containers, QJsonArray { containerConfig });
config.insert(config_key::defaultContainer, ContainerProps::containerToString(container));
auto dns = m_configurator->getDnsForConfig(serverIndex);
@ -86,8 +82,8 @@ void ExportController::generateConnectionConfig()
QByteArray compressedConfig = QJsonDocument(config).toJson();
compressedConfig = qCompress(compressedConfig, 8);
m_amneziaCode = QString("vpn://%1")
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig);
emit exportConfigChanged();
@ -108,10 +104,8 @@ void ExportController::saveFile()
QString fileExtension = ".vpn";
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl fileName;
fileName = QFileDialog::getSaveFileUrl(nullptr,
tr("Save AmneziaVPN config"),
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"),
"*" + fileExtension);
fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"),
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension);
if (fileName.isEmpty())
return;
if (!fileName.toString().endsWith(fileExtension)) {
@ -139,10 +133,9 @@ QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &dat
for (int i = 0; i < data.size(); i = i + k) {
QByteArray chunk;
QDataStream s(&chunk, QIODevice::WriteOnly);
s << amnezia::qrMagicCode << chunksCount << (quint8) std::round(i / k) << data.mid(i, k);
s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k);
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals);
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 0));

View file

@ -5,40 +5,42 @@
#include "core/errorstrings.h"
namespace {
enum class ConfigTypes { Amnezia, OpenVpn, WireGuard };
ConfigTypes checkConfigFormat(const QString &config)
namespace
{
const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap";
enum class ConfigTypes {
Amnezia,
OpenVpn,
WireGuard
};
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
ConfigTypes checkConfigFormat(const QString &config)
{
const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap";
if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1)
|| config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1)
|| config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface)
&& config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard;
const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]";
if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface)
&& config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard;
}
return ConfigTypes::Amnezia;
}
return ConfigTypes::Amnezia;
}
} // namespace
ImportController::ImportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings)
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings)
{
}
void ImportController::extractConfigFromFile(const QUrl &fileUrl)
@ -88,8 +90,7 @@ void ImportController::importConfig()
m_serversModel->addServer(m_config);
if (!m_config.value(config_key::containers).toArray().isEmpty()) {
auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1);
m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole);
m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1);
}
emit importFinished();
@ -116,12 +117,12 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data)
return QJsonDocument::fromJson(ba).object();
}
//bool ImportController::importConnectionFromQr(const QByteArray &data)
// bool ImportController::importConnectionFromQr(const QByteArray &data)
//{
// QJsonObject dataObj = QJsonDocument::fromJson(data).object();
// if (!dataObj.isEmpty()) {
// return importConnection(dataObj);
// }
// QJsonObject dataObj = QJsonDocument::fromJson(data).object();
// if (!dataObj.isEmpty()) {
// return importConnection(dataObj);
// }
// QByteArray ba_uncompressed = qUncompress(data);
// if (!ba_uncompressed.isEmpty()) {
@ -159,7 +160,6 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
config[config_key::defaultContainer] = "amnezia-openvpn";
config[config_key::description] = m_settings->nextAvailableServerName();
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
if (dnsMatch.hasNext()) {
@ -206,7 +206,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
config[config_key::defaultContainer] = "amnezia-wireguard";
config[config_key::description] = m_settings->nextAvailableServerName();
const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
const static QRegularExpression dnsRegExp(
"DNS = "
"(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
QRegularExpressionMatch dnsMatch = dnsRegExp.match(data);
if (dnsMatch.hasMatch()) {
config[config_key::dns1] = dnsMatch.captured(1);

View file

@ -67,8 +67,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co
server.insert(config_key::defaultContainer, ContainerProps::containerToString(container));
m_serversModel->addServer(server);
auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1);
m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole);
m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1);
emit installServerFinished(isInstalledContainerFound);
return;

View file

@ -78,7 +78,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
return QVariant();
}
void ContainersModel::setCurrentlyProcessedServerIndex(int index)
void ContainersModel::setCurrentlyProcessedServerIndex(const int index)
{
beginResetModel();
m_currentlyProcessedServerIndex = index;

View file

@ -3,11 +3,11 @@
#include <QAbstractListModel>
#include <QJsonObject>
#include <vector>
#include <utility>
#include <vector>
#include "settings.h"
#include "containers/containers_defs.h"
#include "settings.h"
class ContainersModel : public QAbstractListModel
{
@ -44,7 +44,7 @@ public slots:
DockerContainer getDefaultContainer();
QString getDefaultContainerName();
void setCurrentlyProcessedServerIndex(int index);
void setCurrentlyProcessedServerIndex(const int index);
void setCurrentlyProcessedContainerIndex(int index);
int getCurrentlyProcessedContainerIndex();
@ -57,7 +57,6 @@ protected:
private:
QMap<DockerContainer, QJsonObject> m_containers;
int m_currentlyProcessedServerIndex;
int m_currentlyProcessedContainerIndex;
DockerContainer m_defaultContainerIndex;

View file

@ -5,6 +5,7 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
{
m_servers = m_settings->serversArray();
m_defaultServerIndex = m_settings->defaultServerIndex();
m_currenlyProcessedServerIndex = m_defaultServerIndex;
}
int ServersModel::rowCount(const QModelIndex &parent) const
@ -28,10 +29,6 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int
m_servers.replace(index.row(), server);
break;
}
case IsDefaultRole: {
setDefaultServerIndex(index.row());
break;
}
default: {
return true;
}
@ -62,6 +59,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName;
case IsDefaultRole: return index.row() == m_defaultServerIndex;
case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex;
case HasWriteAccess: {
auto credentials = m_settings->serverCredentials(index.row());
return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty());
}
}
return QVariant();
@ -73,6 +74,13 @@ QVariant ServersModel::data(const int index, int role) const
return data(modelIndex, role);
}
void ServersModel::setDefaultServerIndex(const int index)
{
m_settings->setDefaultServer(index);
m_defaultServerIndex = m_settings->defaultServerIndex();
emit defaultServerIndexChanged();
}
const int ServersModel::getDefaultServerIndex()
{
return m_defaultServerIndex;
@ -83,10 +91,10 @@ const int ServersModel::getServersCount()
return m_servers.count();
}
void ServersModel::setCurrentlyProcessedServerIndex(int index)
void ServersModel::setCurrentlyProcessedServerIndex(const int index)
{
m_currenlyProcessedServerIndex = index;
emit currentlyProcessedServerIndexChanged();
emit currentlyProcessedServerIndexChanged(m_currenlyProcessedServerIndex);
}
int ServersModel::getCurrentlyProcessedServerIndex()
@ -101,8 +109,7 @@ bool ServersModel::isDefaultServerCurrentlyProcessed()
bool ServersModel::isCurrentlyProcessedServerHasWriteAccess()
{
auto credentials = m_settings->serverCredentials(m_currenlyProcessedServerIndex);
return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty());
return qvariant_cast<bool>(data(m_currenlyProcessedServerIndex, HasWriteAccess));
}
void ServersModel::addServer(const QJsonObject &server)
@ -140,11 +147,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[CredentialsLoginRole] = "credentialsLogin";
roles[IsDefaultRole] = "isDefault";
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[HasWriteAccess] = "hasWriteAccess";
return roles;
}
void ServersModel::setDefaultServerIndex(const int index)
{
m_settings->setDefaultServer(index);
m_defaultServerIndex = m_settings->defaultServerIndex();
}

View file

@ -5,13 +5,6 @@
#include "settings.h"
struct ServerModelContent
{
QString desc;
QString address;
bool isDefault;
};
class ServersModel : public QAbstractListModel
{
Q_OBJECT
@ -22,7 +15,8 @@ public:
CredentialsRole,
CredentialsLoginRole,
IsDefaultRole,
IsCurrentlyProcessedRole
IsCurrentlyProcessedRole,
HasWriteAccess
};
ServersModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
@ -33,14 +27,20 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const int index, int role = Qt::DisplayRole) const;
Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged)
Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex
NOTIFY currentlyProcessedServerIndexChanged)
public slots:
void setDefaultServerIndex(const int index);
const int getDefaultServerIndex();
bool isDefaultServerCurrentlyProcessed();
bool isCurrentlyProcessedServerHasWriteAccess();
const int getServersCount();
void setCurrentlyProcessedServerIndex(int index);
void setCurrentlyProcessedServerIndex(const int index);
int getCurrentlyProcessedServerIndex();
void addServer(const QJsonObject &server);
@ -50,11 +50,10 @@ protected:
QHash<int, QByteArray> roleNames() const override;
signals:
void currentlyProcessedServerIndexChanged();
void currentlyProcessedServerIndexChanged(const int index);
void defaultServerIndexChanged();
private:
void setDefaultServerIndex(const int index);
QJsonArray m_servers;
std::shared_ptr<Settings> m_settings;

View file

@ -1,14 +1,12 @@
#include "ServerListLogic.h"
#include "vpnconnection.h"
#include "../models/servers_model.h"
#include "../uilogic.h"
#include "vpnconnection.h"
ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent):
PageLogicBase(logic, parent),
m_serverListModel{new ServersModel(m_settings, this)}
ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent)
: PageLogicBase(logic, parent), m_serverListModel { new ServersModel(m_settings, this) }
{
}
void ServerListLogic::onServerListPushbuttonDefaultClicked(int index)
@ -31,19 +29,19 @@ int ServerListLogic::currServerIdx() const
void ServerListLogic::onUpdatePage()
{
const QJsonArray &servers = m_settings->serversArray();
int defaultServer = m_settings->defaultServerIndex();
QVector<ServerModelContent> serverListContent;
for(int i = 0; i < servers.size(); i++) {
ServerModelContent c;
auto server = servers.at(i).toObject();
c.desc = server.value(config_key::description).toString();
c.address = server.value(config_key::hostName).toString();
if (c.desc.isEmpty()) {
c.desc = c.address;
}
c.isDefault = (i == defaultServer);
serverListContent.push_back(c);
}
// qobject_cast<ServersModel*>(m_serverListModel)->setContent(serverListContent);
// const QJsonArray &servers = m_settings->serversArray();
// int defaultServer = m_settings->defaultServerIndex();
// QVector<ServerModelContent> serverListContent;
// for(int i = 0; i < servers.size(); i++) {
// ServerModelContent c;
// auto server = servers.at(i).toObject();
// c.desc = server.value(config_key::description).toString();
// c.address = server.value(config_key::hostName).toString();
// if (c.desc.isEmpty()) {
// c.desc = c.address;
// }
// c.isDefault = (i == defaultServer);
// serverListContent.push_back(c);
// }
// qobject_cast<ServersModel*>(m_serverListModel)->setContent(serverListContent);
}

View file

@ -8,96 +8,115 @@ Item {
id: root
property string headerText
property string textFieldPlaceholderText
property bool textFieldEditable: true
property alias errorText: errorField.text
property string buttonText
property var clickedFunc
property alias textField: textField
property alias textFieldText: textField.text
property string textFieldPlaceholderText
property bool textFieldEditable: true
implicitHeight: 74
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
Rectangle {
id: backgroud
ColumnLayout {
id: content
anchors.fill: parent
color: "#1c1d21"
radius: 16
border.color: textField.focus ? "#d7d8db" : "#2C2D30"
border.width: 1
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
Rectangle {
id: backgroud
Layout.fillWidth: true
Layout.preferredHeight: 74
color: "#1c1d21"
radius: 16
border.color: textField.focus ? "#d7d8db" : "#2C2D30"
border.width: 1
RowLayout {
anchors.fill: backgroud
ColumnLayout {
LabelTextType {
text: root.headerText
color: "#878b91"
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
TextField {
id: textField
RowLayout {
anchors.fill: backgroud
ColumnLayout {
LabelTextType {
text: root.headerText
color: "#878b91"
enabled: root.textFieldEditable
color: "#d7d8db"
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
}
placeholderText: textFieldPlaceholderText
placeholderTextColor: "#494B50"
TextField {
id: textField
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
enabled: root.textFieldEditable
color: "#d7d8db"
font.pixelSize: 16
font.weight: 400
font.family: "PT Root UI VF"
placeholderText: textFieldPlaceholderText
placeholderTextColor: "#494B50"
height: 24
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
topPadding: 0
rightPadding: 0
leftPadding: 0
bottomPadding: 0
selectionColor: "#412102"
selectedTextColor: "#D7D8DB"
background: Rectangle {
anchors.fill: parent
color: "#1c1d21"
font.pixelSize: 16
font.weight: 400
font.family: "PT Root UI VF"
height: 24
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
topPadding: 0
rightPadding: 0
leftPadding: 0
bottomPadding: 0
background: Rectangle {
anchors.fill: parent
color: "#1c1d21"
}
onTextChanged: {
root.errorText = ""
}
}
}
BasicButtonType {
visible: root.buttonText !== ""
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: buttonText
Layout.rightMargin: 24
onClicked: {
if (clickedFunc && typeof clickedFunc === "function") {
clickedFunc()
}
}
}
}
}
BasicButtonType {
visible: root.buttonText !== ""
SmallTextType {
id: errorField
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: buttonText
Layout.rightMargin: 24
onClicked: {
if (clickedFunc && typeof clickedFunc === "function") {
clickedFunc()
}
}
text: root.errorText
visible: root.errorText !== ""
color: "#EB5757"
}
}
}

View file

@ -0,0 +1,47 @@
pragma Singleton
import QtQuick 2.15
import SortFilterProxyModel 0.2
import ProtocolEnum 1.0
Item {
ValueFilter {
id: vpnTypeFilter
roleName: "serviceType"
value: ProtocolEnum.Vpn
}
ValueFilter {
id: serviceTypeFilter
roleName: "serviceType"
value: ProtocolEnum.Other
}
ValueFilter {
id: supportedFilter
roleName: "isSupported"
value: true
}
ValueFilter {
id: installedFilter
roleName: "isInstalled"
value: true
}
function getWriteAccessProtocolsListFilters() {
return [vpnTypeFilter, supportedFilter]
}
function getReadAccessProtocolsListFilters() {
return [vpnTypeFilter, supportedFilter, installedFilter]
}
function getWriteAccessServicesListFilters() {
return [serviceTypeFilter, supportedFilter]
}
function getReadAccessServicesListFilters() {
return [serviceTypeFilter, supportedFilter, installedFilter]
}
}

View file

@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import ContainersModelFilters 1.0
import "./"
import "../Controls2"
@ -161,10 +162,7 @@ PageType {
headerBackButtonImage: "qrc:/images/controls/arrow-left.svg"
rootButtonClickedFunction: function() {
// todo check if server index changed before set Currently processed
// todo make signal slot for change server index in containersModel
ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex)
ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex)
ServersModel.currentlyProcessedIndex = serversMenuContent.currentIndex
containersDropDown.menuVisible = true
}
@ -177,39 +175,22 @@ PageType {
function onCurrentlyProcessedServerIndexChanged() {
updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = [serviceTypeFilter, supportedFilter]
} else {
proxyContainersModel.filters = installedFilter
}
function updateContainersModelFilters() {
if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()
}
}
ValueFilter {
id: serviceTypeFilter
roleName: "serviceType"
value: ProtocolEnum.Vpn
}
ValueFilter {
id: supportedFilter
roleName: "isSupported"
value: true
}
ValueFilter {
id: installedFilter
roleName: "isInstalled"
value: true
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
Component.onCompleted: updateContainersModelFilters()
}
Component.onCompleted: updateContainersModelFilters()
currentIndex: ContainersModel.getDefaultContainer()
}
}
@ -267,7 +248,7 @@ PageType {
height: serversMenuContent.contentItem.height
model: ServersModel
currentIndex: ServersModel.getDefaultServerIndex()
currentIndex: ServersModel.defaultIndex
clip: true
interactive: false
@ -305,8 +286,8 @@ PageType {
onClicked: {
serversMenuContent.currentIndex = index
isDefault = true
ContainersModel.setCurrentlyProcessedServerIndex(index)
ServersModel.currentlyProcessedIndex = index
ServersModel.defaultIndex = index
root.currentServerName = name
root.currentServerHostName = hostName
@ -328,8 +309,7 @@ PageType {
z: 1
onClicked: function() {
ServersModel.setCurrentlyProcessedServerIndex(index)
ContainersModel.setCurrentlyProcessedServerIndex(index)
ServersModel.currentlyProcessedIndex = index
goToPage(PageEnum.PageSettingsServerInfo)
menu.visible = false
}

View file

@ -29,6 +29,14 @@ PageType {
}
}
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
content.isServerWithWriteAccess = ServersModel.isCurrentlyProcessedServerHasWriteAccess()
}
}
FlickableType {
id: fl
anchors.top: parent.top
@ -42,7 +50,10 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() //todo make it property?
LabelWithButtonType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: qsTr("Clear Amnezia cache")
@ -65,9 +76,12 @@ PageType {
}
}
DividerType {}
DividerType {
visible: content.isServerWithWriteAccess
}
LabelWithButtonType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia")
@ -78,12 +92,14 @@ PageType {
}
}
DividerType {}
DividerType {
visible: content.isServerWithWriteAccess
}
LabelWithButtonType {
Layout.fillWidth: true
text: "Remove server from application"
text: qsTr("Remove server from application")
textColor: "#EB5757"
clickedFunction: function() {
@ -115,9 +131,10 @@ PageType {
DividerType {}
LabelWithButtonType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: "Clear server from Amnezia software"
text: qsTr("Clear server from Amnezia software")
textColor: "#EB5757"
clickedFunction: function() {
@ -142,7 +159,9 @@ PageType {
}
}
DividerType {}
DividerType {
visible: content.isServerWithWriteAccess
}
QuestionDrawer {
id: questionDrawer

View file

@ -54,7 +54,13 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: name
descriptionText: credentialsLogin + " · " + hostName
descriptionText: {
if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) {
return credentialsLogin + " · " + hostName
} else {
return hostName
}
}
actionButtonFunction: function() {
serverNameEditDrawer.visible = true
@ -123,10 +129,14 @@ PageType {
}
TabButtonType {
visible: protocolsPage.installedProtocolsCount
width: protocolsPage.installedProtocolsCount ? undefined : 0
isSelected: tabBar.currentIndex === 0
text: qsTr("Protocols")
}
TabButtonType {
visible: servicesPage.installedServicesCount
width: servicesPage.installedServicesCount ? undefined : 0
isSelected: tabBar.currentIndex === 1
text: qsTr("Services")
}
@ -143,9 +153,11 @@ PageType {
currentIndex: tabBar.currentIndex
PageSettingsServerProtocols {
id: protocolsPage
stackView: root.stackView
}
PageSettingsServerServices {
id: servicesPage
stackView: root.stackView
}
PageSettingsServerData {

View file

@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import ContainersModelFilters 1.0
import "./"
import "../Controls2"
@ -17,22 +18,31 @@ import "../Components"
PageType {
id: root
SortFilterProxyModel {
id: containersProxyModel
sourceModel: ContainersModel
filters: [
ValueFilter {
roleName: "serviceType"
value: ProtocolEnum.Vpn
},
ValueFilter {
roleName: "isSupported"
value: true
}
]
}
property var installedProtocolsCount
SettingsContainersListView {
model: containersProxyModel
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()
}
root.installedProtocolsCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
}
Component.onCompleted: updateContainersModelFilters()
}
}

View file

@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import ContainersModelFilters 1.0
import "./"
import "../Controls2"
@ -17,22 +18,31 @@ import "../Components"
PageType {
id: root
SortFilterProxyModel {
id: containersProxyModel
sourceModel: ContainersModel
filters: [
ValueFilter {
roleName: "serviceType"
value: ProtocolEnum.Other
},
ValueFilter {
roleName: "isSupported"
value: true
}
]
}
property var installedServicesCount
SettingsContainersListView {
model: containersProxyModel
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters()
}
root.installedServicesCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
}
Component.onCompleted: updateContainersModelFilters()
}
}

View file

@ -89,8 +89,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ServersModel.setCurrentlyProcessedServerIndex(index)
ContainersModel.setCurrentlyProcessedServerIndex(index)
ServersModel.currentlyProcessedIndex = index
goToPage(PageEnum.PageSettingsServerInfo)
}
}

View file

@ -79,6 +79,10 @@ PageType {
text: qsTr("Set up a server the easy way")
onClicked: function() {
if (!isCredentialsFilled()) {
return
}
InstallController.setShouldCreateServer(true)
InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text)
@ -100,6 +104,10 @@ PageType {
text: qsTr("Select protocol to install")
onClicked: function() {
if (!isCredentialsFilled()) {
return
}
InstallController.setShouldCreateServer(true)
InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text)
@ -108,4 +116,25 @@ PageType {
}
}
}
function isCredentialsFilled() {
var hasEmptyField = false
if (hostname.textFieldText === "") {
hostname.errorText = qsTr("ip address cannot be empty")
hasEmptyField = true
} else if (!hostname.textField.acceptableInput) {
hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88")
}
if (username.textFieldText === "") {
username.errorText = qsTr("login cannot be empty")
hasEmptyField = true
}
if (secretData.textFieldText === "") {
secretData.errorText = qsTr("password/private key cannot be empty")
hasEmptyField = true
}
return !hasEmptyField
}
}

View file

@ -61,7 +61,7 @@ PageType {
function onServerAlreadyExists(serverIndex) {
goToStartPage()
ServersModel.setCurrentlyProcessedServerIndex(serverIndex)
ServersModel.currentlyProcessedIndex = serverIndex
goToPage(PageEnum.PageSettingsServerInfo, false)
PageController.showErrorMessage(qsTr("The server has already been added to the application"))

View file

@ -147,18 +147,28 @@ PageType {
imageSource: "qrc:/images/controls/chevron-right.svg"
model: ServersModel
currentIndex: ServersModel.getDefaultServerIndex()
model: SortFilterProxyModel {
id: proxyServersModel
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "hasWriteAccess"
value: true
}
]
}
currentIndex: 0
clickedFunction: function() {
serverSelector.text = selectedText
ContainersModel.setCurrentlyProcessedServerIndex(currentIndex)
ServersModel.currentlyProcessedIndex = currentIndex
protocolSelector.visible = true
}
Component.onCompleted: {
serverSelector.text = selectedText
ContainersModel.setCurrentlyProcessedServerIndex(currentIndex)
ServersModel.currentlyProcessedIndex = currentIndex
}
}
@ -169,7 +179,7 @@ PageType {
height: parent.height * 0.5
ColumnLayout {
id: header
id: protocolSelectorHeader
anchors.top: parent.top
anchors.left: parent.left
@ -187,12 +197,12 @@ PageType {
}
FlickableType {
anchors.top: header.bottom
anchors.top: protocolSelectorHeader.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight
contentHeight: protocolSelectorContent.implicitHeight
Column {
id: col
id: protocolSelectorContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
@ -265,7 +275,7 @@ PageType {
}
DropDownType {
id: connectionTypeSelector
id: exportTypeSelector
property int currentIndex
@ -283,8 +293,6 @@ PageType {
headerText: qsTr("Connection format")
listView: ListViewType {
id: connectionTypeSelectorListView
rootWidth: root.width
dividerVisible: true
@ -294,14 +302,14 @@ PageType {
currentIndex: 0
clickedFunction: function() {
connectionTypeSelector.text = selectedText
connectionTypeSelector.currentIndex = currentIndex
connectionTypeSelector.menuVisible = false
exportTypeSelector.text = selectedText
exportTypeSelector.currentIndex = currentIndex
exportTypeSelector.menuVisible = false
}
Component.onCompleted: {
connectionTypeSelector.text = selectedText
connectionTypeSelector.currentIndex = currentIndex
exportTypeSelector.text = selectedText
exportTypeSelector.currentIndex = currentIndex
}
}
}

View file

@ -50,8 +50,7 @@ PageType {
Component.onCompleted: {
var pagePath = PageController.getPagePath(PageEnum.PageHome)
ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex())
ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex())
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
tabBarStackView.push(pagePath, { "objectName" : pagePath })
}
}
@ -65,8 +64,8 @@ PageType {
topPadding: 8
bottomPadding: 8//34
leftPadding: 96
rightPadding: 96
leftPadding: shareTabButton.visible ? 96 : 128
rightPadding: shareTabButton.visible ? 96 : 128
background: Rectangle {
border.width: 1
@ -78,11 +77,25 @@ PageType {
isSelected: tabBar.currentIndex === 0
image: "qrc:/images/controls/home.svg"
onClicked: {
ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex())
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
tabBarStackView.goToTabBarPage(PageEnum.PageHome)
}
}
TabImageButtonType {
id: shareTabButton
Connections {
target: ServersModel
function onDefaultServerIndexChanged() {
shareTabButton.visible = ServersModel.isCurrentlyProcessedServerHasWriteAccess()
shareTabButton.width = ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0
}
}
visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess()
width: visible ? undefined : 0
isSelected: tabBar.currentIndex === 1
image: "qrc:/images/controls/share-2.svg"
onClicked: {
@ -100,8 +113,8 @@ PageType {
MouseArea {
anchors.fill: tabBar
anchors.leftMargin: 96
anchors.rightMargin: 96
anchors.leftMargin: shareTabButton.visible ? 96 : 128
anchors.rightMargin: shareTabButton.visible ? 96 : 128
cursorShape: Qt.PointingHandCursor
enabled: false
}