(rowCount()))
return QVariant();
- QJsonObject service = m_services.at(index.row()).toObject();
- QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject();
- auto serviceType = service.value(configKey::serviceType).toString();
+ auto apiServiceData = m_services.at(index.row());
+ auto serviceType = apiServiceData.type;
+ auto isServiceAvailable = apiServiceData.isServiceAvailable;
switch (role) {
case NameRole: {
- return serviceInfo.value(configKey::name).toString();
+ return apiServiceData.serviceInfo.name;
}
case CardDescriptionRole: {
- auto speed = serviceInfo.value(configKey::speed).toString();
+ auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s")
.arg(speed);
- } else {
- return tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
+ } else if (serviceType == serviceType::amneziaFree){
+ QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
+ if (isServiceAvailable) {
+ description += tr("Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.");
+ }
+ return description;
}
}
case ServiceDescriptionRole: {
@@ -75,19 +84,26 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship");
}
}
- case SpeedRole: {
- auto speed = serviceInfo.value(configKey::speed).toString();
- return tr("%1 MBit/s").arg(speed);
+ case IsServiceAvailableRole: {
+ if (serviceType == serviceType::amneziaFree) {
+ if (isServiceAvailable) {
+ return false;
+ }
+ }
+ return true;
}
- case WorkPeriodRole: {
- auto timelimit = serviceInfo.value(configKey::timelimit).toString();
- if (timelimit == "0") {
+ case SpeedRole: {
+ return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed);
+ }
+ case TimeLimitRole: {
+ auto timeLimit = apiServiceData.serviceInfo.timeLimit;
+ if (timeLimit == "0") {
return "";
}
- return tr("%1 days").arg(timelimit);
+ return tr("%1 days").arg(timeLimit);
}
case RegionRole: {
- return serviceInfo.value(configKey::region).toString();
+ return apiServiceData.serviceInfo.region;
}
case FeaturesRole: {
if (serviceType == serviceType::amneziaPremium) {
@@ -99,12 +115,15 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
}
}
case PriceRole: {
- auto price = serviceInfo.value(configKey::price).toString();
+ auto price = apiServiceData.serviceInfo.price;
if (price == "free") {
return tr("Free");
}
return tr("%1 $/month").arg(price);
}
+ case EndDateRole: {
+ return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
+ }
}
return QVariant();
@@ -114,15 +133,18 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
{
beginResetModel();
- m_countryCode = data.value(configKey::userCountryCode).toString();
- m_services = data.value(configKey::services).toArray();
- if (m_services.isEmpty()) {
- QJsonObject service;
- service.insert(configKey::serviceInfo, data.value(configKey::serviceInfo));
- service.insert(configKey::serviceType, data.value(configKey::serviceType));
+ m_services.clear();
- m_services.push_back(service);
+ m_countryCode = data.value(configKey::userCountryCode).toString();
+ auto services = data.value(configKey::services).toArray();
+
+ if (services.isEmpty()) {
+ m_services.push_back(getApiServicesData(data));
m_selectedServiceIndex = 0;
+ } else {
+ for (const auto &service : services) {
+ m_services.push_back(getApiServicesData(service.toObject()));
+ }
}
endResetModel();
@@ -135,32 +157,32 @@ void ApiServicesModel::setServiceIndex(const int index)
QJsonObject ApiServicesModel::getSelectedServiceInfo()
{
- QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
- return service.value(configKey::serviceInfo).toObject();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.serviceInfo.object;
}
QString ApiServicesModel::getSelectedServiceType()
{
- QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
- return service.value(configKey::serviceType).toString();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.type;
}
QString ApiServicesModel::getSelectedServiceProtocol()
{
- QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
- return service.value(configKey::serviceProtocol).toString();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.protocol;
}
QString ApiServicesModel::getSelectedServiceName()
{
- auto modelIndex = index(m_selectedServiceIndex, 0);
- return data(modelIndex, ApiServicesModel::Roles::NameRole).toString();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.serviceInfo.name;
}
QJsonArray ApiServicesModel::getSelectedServiceCountries()
{
- QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
- return service.value(configKey::availableCountries).toArray();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.availableCountries;
}
QString ApiServicesModel::getCountryCode()
@@ -170,8 +192,8 @@ QString ApiServicesModel::getCountryCode()
QString ApiServicesModel::getStoreEndpoint()
{
- QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
- return service.value(configKey::storeEndpoint).toString();
+ auto service = m_services.at(m_selectedServiceIndex);
+ return service.storeEndpoint;
}
QVariant ApiServicesModel::getSelectedServiceData(const QString roleString)
@@ -193,11 +215,48 @@ QHash ApiServicesModel::roleNames() const
roles[NameRole] = "name";
roles[CardDescriptionRole] = "cardDescription";
roles[ServiceDescriptionRole] = "serviceDescription";
+ roles[IsServiceAvailableRole] = "isServiceAvailable";
roles[SpeedRole] = "speed";
- roles[WorkPeriodRole] = "workPeriod";
+ roles[TimeLimitRole] = "timeLimit";
roles[RegionRole] = "region";
roles[FeaturesRole] = "features";
roles[PriceRole] = "price";
+ roles[EndDateRole] = "endDate";
return roles;
}
+
+ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data)
+{
+ auto serviceInfo = data.value(configKey::serviceInfo).toObject();
+ auto serviceType = data.value(configKey::serviceType).toString();
+ auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
+ auto availableCountries = data.value(configKey::availableCountries).toArray();
+
+ auto subscriptionObject = data.value(configKey::subscription).toObject();
+
+ ApiServicesData serviceData;
+ serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString();
+ serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString();
+ serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString();
+ serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
+ serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
+
+ serviceData.type = serviceType;
+ serviceData.protocol = serviceProtocol;
+
+ serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();
+
+ if (serviceInfo.value(configKey::isAvailable).isBool()) {
+ serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
+ } else {
+ serviceData.isServiceAvailable = true;
+ }
+
+ serviceData.serviceInfo.object = serviceInfo;
+ serviceData.availableCountries = availableCountries;
+
+ serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString();
+
+ return serviceData;
+}
diff --git a/client/ui/models/apiServicesModel.h b/client/ui/models/apiServicesModel.h
index 64676be6..c96a49ab 100644
--- a/client/ui/models/apiServicesModel.h
+++ b/client/ui/models/apiServicesModel.h
@@ -3,6 +3,7 @@
#include
#include
+#include
class ApiServicesModel : public QAbstractListModel
{
@@ -13,11 +14,13 @@ public:
NameRole = Qt::UserRole + 1,
CardDescriptionRole,
ServiceDescriptionRole,
+ IsServiceAvailableRole,
SpeedRole,
- WorkPeriodRole,
+ TimeLimitRole,
RegionRole,
FeaturesRole,
- PriceRole
+ PriceRole,
+ EndDateRole
};
explicit ApiServicesModel(QObject *parent = nullptr);
@@ -47,8 +50,40 @@ protected:
QHash roleNames() const override;
private:
+ struct ServiceInfo
+ {
+ QString name;
+ QString speed;
+ QString timeLimit;
+ QString region;
+ QString price;
+
+ QJsonObject object;
+ };
+
+ struct Subscription
+ {
+ QString endDate;
+ };
+
+ struct ApiServicesData
+ {
+ bool isServiceAvailable;
+
+ QString type;
+ QString protocol;
+ QString storeEndpoint;
+
+ ServiceInfo serviceInfo;
+ Subscription subscription;
+
+ QJsonArray availableCountries;
+ };
+
+ ApiServicesData getApiServicesData(const QJsonObject &data);
+
QString m_countryCode;
- QJsonArray m_services;
+ QVector m_services;
int m_selectedServiceIndex;
};
diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp
index f2117f75..f07eae71 100644
--- a/client/ui/models/clientManagementModel.cpp
+++ b/client/ui/models/clientManagementModel.cpp
@@ -20,6 +20,7 @@ namespace
constexpr char latestHandshake[] = "latestHandshake";
constexpr char dataReceived[] = "dataReceived";
constexpr char dataSent[] = "dataSent";
+ constexpr char allowedIps[] = "allowedIps";
}
}
@@ -49,6 +50,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
case DataReceivedRole: return userData.value(configKey::dataReceived).toString();
case DataSentRole: return userData.value(configKey::dataSent).toString();
+ case AllowedIpsRole: return userData.value(configKey::allowedIps).toString();
}
return QVariant();
@@ -75,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
{
beginResetModel();
m_clientsTable = QJsonArray();
+ endResetModel();
ErrorCode error = ErrorCode::NoError;
@@ -88,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the clientsTable file from the server";
- endResetModel();
return error;
}
+ beginResetModel();
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) {
@@ -103,6 +106,8 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
error = getOpenVpnClients(container, credentials, serverController, count);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
error = getWireGuardClients(container, credentials, serverController, count);
+ } else if (container == DockerContainer::Xray) {
+ error = getXrayClients(container, credentials, serverController, count);
}
if (error != ErrorCode::NoError) {
endResetModel();
@@ -141,6 +146,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
userData[configKey::dataSent] = client.dataSent;
}
+ if (!client.allowedIps.isEmpty()) {
+ userData[configKey::allowedIps] = client.allowedIps;
+ }
+
obj[configKey::userData] = userData;
m_clientsTable.replace(i, obj);
break;
@@ -232,6 +241,68 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta
}
return error;
}
+ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
+ const QSharedPointer &serverController, int &count)
+{
+ ErrorCode error = ErrorCode::NoError;
+
+ const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
+ const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error);
+ if (error != ErrorCode::NoError) {
+ logger.error() << "Failed to get the xray server config file from the server";
+ return error;
+ }
+
+ QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
+ if (serverConfig.isNull()) {
+ logger.error() << "Failed to parse xray server config JSON";
+ return ErrorCode::InternalError;
+ }
+
+ if (!serverConfig.object().contains("inbounds") || serverConfig.object()["inbounds"].toArray().isEmpty()) {
+ logger.error() << "Invalid xray server config structure";
+ return ErrorCode::InternalError;
+ }
+
+ const QJsonObject inbound = serverConfig.object()["inbounds"].toArray()[0].toObject();
+ if (!inbound.contains("settings")) {
+ logger.error() << "Missing settings in xray inbound config";
+ return ErrorCode::InternalError;
+ }
+
+ const QJsonObject settings = inbound["settings"].toObject();
+ if (!settings.contains("clients")) {
+ logger.error() << "Missing clients in xray settings config";
+ return ErrorCode::InternalError;
+ }
+
+ const QJsonArray clients = settings["clients"].toArray();
+ for (const auto &clientValue : clients) {
+ const QJsonObject clientObj = clientValue.toObject();
+ if (!clientObj.contains("id")) {
+ logger.error() << "Missing id in xray client config";
+ continue;
+ }
+ QString clientId = clientObj["id"].toString();
+
+ QString xrayDefaultUuid = serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, error);
+ xrayDefaultUuid.replace("\n", "");
+
+ if (!isClientExists(clientId) && clientId != xrayDefaultUuid) {
+ QJsonObject client;
+ client[configKey::clientId] = clientId;
+
+ QJsonObject userData;
+ userData[configKey::clientName] = QString("Client %1").arg(count);
+ client[configKey::userData] = userData;
+
+ m_clientsTable.push_back(client);
+ count++;
+ }
+ }
+
+ return error;
+}
ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer &serverController, std::vector &data)
@@ -266,8 +337,9 @@ ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const S
const auto peerList = parts.filter("peer:");
const auto latestHandshakeList = parts.filter("latest handshake:");
const auto transferredDataList = parts.filter("transfer:");
+ const auto allowedIpsList = parts.filter("allowed ips:");
- if (latestHandshakeList.isEmpty() || transferredDataList.isEmpty() || peerList.isEmpty()) {
+ if (allowedIpsList.isEmpty() || latestHandshakeList.isEmpty() || transferredDataList.isEmpty() || peerList.isEmpty()) {
return error;
}
@@ -281,19 +353,20 @@ ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const S
}
};
- for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size(); ++i) {
+ for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size() && i < allowedIpsList.size(); ++i) {
const auto transferredData = getStrValue(transferredDataList[i]).split(",");
auto latestHandshake = getStrValue(latestHandshakeList[i]);
auto serverBytesReceived = transferredData.front().trimmed();
auto serverBytesSent = transferredData.back().trimmed();
+ auto allowedIps = getStrValue(allowedIpsList[i]);
changeHandshakeFormat(latestHandshake);
serverBytesReceived.chop(QStringLiteral(" received").length());
serverBytesSent.chop(QStringLiteral(" sent").length());
- data.push_back({ getStrValue(peerList[i]), latestHandshake, serverBytesSent, serverBytesReceived });
+ data.push_back({ getStrValue(peerList[i]), latestHandshake, serverBytesSent, serverBytesReceived, allowedIps });
}
return error;
@@ -317,17 +390,67 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c
const QSharedPointer &serverController)
{
Proto protocol;
- if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
- protocol = Proto::OpenVpn;
- } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
- protocol = ContainerProps::defaultProtocol(container);
- } else {
- return ErrorCode::NoError;
+ switch (container) {
+ case DockerContainer::ShadowSocks:
+ case DockerContainer::Cloak:
+ protocol = Proto::OpenVpn;
+ break;
+ case DockerContainer::OpenVpn:
+ case DockerContainer::WireGuard:
+ case DockerContainer::Awg:
+ case DockerContainer::Xray:
+ protocol = ContainerProps::defaultProtocol(container);
+ break;
+ default:
+ return ErrorCode::NoError;
}
auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig);
+ return appendClient(protocolConfig, clientName, container, credentials, serverController);
+}
- return appendClient(protocolConfig.value(config_key::clientId).toString(), clientName, container, credentials, serverController);
+ErrorCode ClientManagementModel::appendClient(QJsonObject &protocolConfig, const QString &clientName, const DockerContainer container,
+ const ServerCredentials &credentials, const QSharedPointer &serverController)
+{
+ QString clientId;
+ if (container == DockerContainer::Xray) {
+ if (!protocolConfig.contains("outbounds")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray outbounds = protocolConfig.value("outbounds").toArray();
+ if (outbounds.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject outbound = outbounds[0].toObject();
+ if (!outbound.contains("settings")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject settings = outbound["settings"].toObject();
+ if (!settings.contains("vnext")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray vnext = settings["vnext"].toArray();
+ if (vnext.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject vnextObj = vnext[0].toObject();
+ if (!vnextObj.contains("users")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray users = vnextObj["users"].toArray();
+ if (users.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject user = users[0].toObject();
+ if (!user.contains("id")) {
+ return ErrorCode::InternalError;
+ }
+ clientId = user["id"].toString();
+ } else {
+ clientId = protocolConfig.value(config_key::clientId).toString();
+ }
+
+ return appendClient(clientId, clientName, container, credentials, serverController);
}
ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
@@ -413,10 +536,27 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
- if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
- errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController);
- } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
- errorCode = revokeWireGuard(row, container, credentials, serverController);
+ switch(container)
+ {
+ case DockerContainer::OpenVpn:
+ case DockerContainer::ShadowSocks:
+ case DockerContainer::Cloak: {
+ errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController);
+ break;
+ }
+ case DockerContainer::WireGuard:
+ case DockerContainer::Awg: {
+ errorCode = revokeWireGuard(row, container, credentials, serverController);
+ break;
+ }
+ case DockerContainer::Xray: {
+ errorCode = revokeXray(row, container, credentials, serverController);
+ break;
+ }
+ default: {
+ logger.error() << "Internal error: received unexpected container type";
+ return ErrorCode::InternalError;
+ }
}
if (errorCode == ErrorCode::NoError) {
@@ -454,19 +594,69 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
}
Proto protocol;
- if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
- protocol = Proto::OpenVpn;
- } else if (container == DockerContainer::OpenVpn || container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
- protocol = ContainerProps::defaultProtocol(container);
- } else {
- return ErrorCode::NoError;
+
+ switch(container)
+ {
+ case DockerContainer::ShadowSocks:
+ case DockerContainer::Cloak: {
+ protocol = Proto::OpenVpn;
+ break;
+ }
+ case DockerContainer::OpenVpn:
+ case DockerContainer::WireGuard:
+ case DockerContainer::Awg:
+ case DockerContainer::Xray: {
+ protocol = ContainerProps::defaultProtocol(container);
+ break;
+ }
+ default: {
+ logger.error() << "Internal error: received unexpected container type";
+ return ErrorCode::InternalError;
+ }
}
auto protocolConfig = ContainerProps::getProtocolConfigFromContainer(protocol, containerConfig);
+ QString clientId;
+ if (container == DockerContainer::Xray) {
+ if (!protocolConfig.contains("outbounds")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray outbounds = protocolConfig.value("outbounds").toArray();
+ if (outbounds.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject outbound = outbounds[0].toObject();
+ if (!outbound.contains("settings")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject settings = outbound["settings"].toObject();
+ if (!settings.contains("vnext")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray vnext = settings["vnext"].toArray();
+ if (vnext.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject vnextObj = vnext[0].toObject();
+ if (!vnextObj.contains("users")) {
+ return ErrorCode::InternalError;
+ }
+ QJsonArray users = vnextObj["users"].toArray();
+ if (users.isEmpty()) {
+ return ErrorCode::InternalError;
+ }
+ QJsonObject user = users[0].toObject();
+ if (!user.contains("id")) {
+ return ErrorCode::InternalError;
+ }
+ clientId = user["id"].toString();
+ } else {
+ clientId = protocolConfig.value(config_key::clientId).toString();
+ }
+
int row;
bool clientExists = false;
- QString clientId = protocolConfig.value(config_key::clientId).toString();
for (row = 0; row < rowCount(); row++) {
auto client = m_clientsTable.at(row).toObject();
if (clientId == client.value(configKey::clientId).toString()) {
@@ -478,11 +668,28 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
return errorCode;
}
- if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
+ switch (container)
+ {
+ case DockerContainer::OpenVpn:
+ case DockerContainer::ShadowSocks:
+ case DockerContainer::Cloak: {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, serverController);
- } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
- errorCode = revokeWireGuard(row, container, credentials, serverController);
+ break;
}
+ case DockerContainer::WireGuard:
+ case DockerContainer::Awg: {
+ errorCode = revokeWireGuard(row, container, credentials, serverController);
+ break;
+ }
+ case DockerContainer::Xray: {
+ errorCode = revokeXray(row, container, credentials, serverController);
+ break;
+ }
+ default:
+ logger.error() << "Internal error: received unexpected container type";
+ return ErrorCode::InternalError;
+ }
+
return errorCode;
}
@@ -585,6 +792,117 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
return ErrorCode::NoError;
}
+ErrorCode ClientManagementModel::revokeXray(const int row,
+ const DockerContainer container,
+ const ServerCredentials &credentials,
+ const QSharedPointer &serverController)
+{
+ ErrorCode error = ErrorCode::NoError;
+
+ // Get server config
+ const QString serverConfigPath = amnezia::protocols::xray::serverConfigPath;
+ const QString configString = serverController->getTextFileFromContainer(container, credentials, serverConfigPath, error);
+ if (error != ErrorCode::NoError) {
+ logger.error() << "Failed to get the xray server config file";
+ return error;
+ }
+
+ QJsonDocument serverConfig = QJsonDocument::fromJson(configString.toUtf8());
+ if (serverConfig.isNull()) {
+ logger.error() << "Failed to parse xray server config JSON";
+ return ErrorCode::InternalError;
+ }
+
+ // Get client ID to remove
+ auto client = m_clientsTable.at(row).toObject();
+ QString clientId = client.value(configKey::clientId).toString();
+
+ // Remove client from server config
+ QJsonObject configObj = serverConfig.object();
+ if (!configObj.contains("inbounds")) {
+ logger.error() << "Missing inbounds in xray config";
+ return ErrorCode::InternalError;
+ }
+
+ QJsonArray inbounds = configObj["inbounds"].toArray();
+ if (inbounds.isEmpty()) {
+ logger.error() << "Empty inbounds array in xray config";
+ return ErrorCode::InternalError;
+ }
+
+ QJsonObject inbound = inbounds[0].toObject();
+ if (!inbound.contains("settings")) {
+ logger.error() << "Missing settings in xray inbound config";
+ return ErrorCode::InternalError;
+ }
+
+ QJsonObject settings = inbound["settings"].toObject();
+ if (!settings.contains("clients")) {
+ logger.error() << "Missing clients in xray settings";
+ return ErrorCode::InternalError;
+ }
+
+ QJsonArray clients = settings["clients"].toArray();
+ if (clients.isEmpty()) {
+ logger.error() << "Empty clients array in xray config";
+ return ErrorCode::InternalError;
+ }
+
+ for (int i = 0; i < clients.size(); ++i) {
+ QJsonObject clientObj = clients[i].toObject();
+ if (clientObj.contains("id") && clientObj["id"].toString() == clientId) {
+ clients.removeAt(i);
+ break;
+ }
+ }
+
+ // Update server config
+ settings["clients"] = clients;
+ inbound["settings"] = settings;
+ inbounds[0] = inbound;
+ configObj["inbounds"] = inbounds;
+
+ // Upload updated config
+ error = serverController->uploadTextFileToContainer(
+ container,
+ credentials,
+ QJsonDocument(configObj).toJson(),
+ serverConfigPath
+ );
+ if (error != ErrorCode::NoError) {
+ logger.error() << "Failed to upload updated xray config";
+ return error;
+ }
+
+ // Remove from local table
+ beginRemoveRows(QModelIndex(), row, row);
+ m_clientsTable.removeAt(row);
+ endRemoveRows();
+
+ // Update clients table file on server
+ const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
+ QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable")
+ .arg(ContainerProps::containerTypeToString(container));
+
+ error = serverController->uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
+ if (error != ErrorCode::NoError) {
+ logger.error() << "Failed to upload the clientsTable file";
+ }
+
+ // Restart container
+ QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
+ error = serverController->runScript(
+ credentials,
+ serverController->replaceVars(restartScript, serverController->genVarsForScript(credentials, container))
+ );
+ if (error != ErrorCode::NoError) {
+ logger.error() << "Failed to restart xray container";
+ return error;
+ }
+
+ return error;
+}
+
QHash ClientManagementModel::roleNames() const
{
QHash roles;
@@ -593,5 +911,6 @@ QHash ClientManagementModel::roleNames() const
roles[LatestHandshakeRole] = "latestHandshake";
roles[DataReceivedRole] = "dataReceived";
roles[DataSentRole] = "dataSent";
+ roles[AllowedIpsRole] = "allowedIps";
return roles;
-}
+}
\ No newline at end of file
diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h
index d64280a3..989120a9 100644
--- a/client/ui/models/clientManagementModel.h
+++ b/client/ui/models/clientManagementModel.h
@@ -17,7 +17,8 @@ public:
CreationDateRole,
LatestHandshakeRole,
DataReceivedRole,
- DataSentRole
+ DataSentRole,
+ AllowedIpsRole
};
struct WgShowData
@@ -26,6 +27,7 @@ public:
QString latestHandshake;
QString dataReceived;
QString dataSent;
+ QString allowedIps;
};
ClientManagementModel(std::shared_ptr settings, QObject *parent = nullptr);
@@ -38,6 +40,8 @@ public slots:
const QSharedPointer &serverController);
ErrorCode appendClient(const DockerContainer container, const ServerCredentials &credentials, const QJsonObject &containerConfig,
const QString &clientName, const QSharedPointer &serverController);
+ ErrorCode appendClient(QJsonObject &protocolConfig, const QString &clientName,const DockerContainer container,
+ const ServerCredentials &credentials, const QSharedPointer &serverController);
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
const ServerCredentials &credentials, const QSharedPointer &serverController);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials,
@@ -62,11 +66,15 @@ private:
const QSharedPointer &serverController);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer &serverController);
+ ErrorCode revokeXray(const int row, const DockerContainer container, const ServerCredentials &credentials,
+ const QSharedPointer &serverController);
ErrorCode getOpenVpnClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer &serverController, int &count);
ErrorCode getWireGuardClients(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer &serverController, int &count);
+ ErrorCode getXrayClients(const DockerContainer container, const ServerCredentials& credentials,
+ const QSharedPointer &serverController, int &count);
ErrorCode wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer &serverController, std::vector &data);
diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp
index 658658df..3a245ebe 100644
--- a/client/ui/models/protocols/awgConfigModel.cpp
+++ b/client/ui/models/protocols/awgConfigModel.cpp
@@ -21,17 +21,30 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
}
switch (role) {
- case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
- case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
- case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
- case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
- case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
- case Roles::InitPacketJunkSizeRole: m_protocolConfig.insert(config_key::initPacketJunkSize, value.toString()); break;
- case Roles::ResponsePacketJunkSizeRole: m_protocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); break;
- case Roles::InitPacketMagicHeaderRole: m_protocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break;
- case Roles::ResponsePacketMagicHeaderRole: m_protocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); break;
- case Roles::UnderloadPacketMagicHeaderRole: m_protocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString()); break;
- case Roles::TransportPacketMagicHeaderRole: m_protocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); break;
+ case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
+
+ case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
+ case Roles::ClientJunkPacketCountRole: m_clientProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
+ case Roles::ClientJunkPacketMinSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
+ case Roles::ClientJunkPacketMaxSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
+
+ case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
+ case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
+ case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
+ case Roles::ServerInitPacketJunkSizeRole: m_serverProtocolConfig.insert(config_key::initPacketJunkSize, value.toString()); break;
+ case Roles::ServerResponsePacketJunkSizeRole:
+ m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString());
+ break;
+ case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break;
+ case Roles::ServerResponsePacketMagicHeaderRole:
+ m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString());
+ break;
+ case Roles::ServerUnderloadPacketMagicHeaderRole:
+ m_serverProtocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString());
+ break;
+ case Roles::ServerTransportPacketMagicHeaderRole:
+ m_serverProtocolConfig.insert(config_key::transportPacketMagicHeader, value.toString());
+ break;
}
emit dataChanged(index, index, QList { role });
@@ -45,17 +58,22 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
}
switch (role) {
- case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
- case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
- case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount);
- case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize);
- case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize);
- case Roles::InitPacketJunkSizeRole: return m_protocolConfig.value(config_key::initPacketJunkSize);
- case Roles::ResponsePacketJunkSizeRole: return m_protocolConfig.value(config_key::responsePacketJunkSize);
- case Roles::InitPacketMagicHeaderRole: return m_protocolConfig.value(config_key::initPacketMagicHeader);
- case Roles::ResponsePacketMagicHeaderRole: return m_protocolConfig.value(config_key::responsePacketMagicHeader);
- case Roles::UnderloadPacketMagicHeaderRole: return m_protocolConfig.value(config_key::underloadPacketMagicHeader);
- case Roles::TransportPacketMagicHeaderRole: return m_protocolConfig.value(config_key::transportPacketMagicHeader);
+ case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
+
+ case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
+ case Roles::ClientJunkPacketCountRole: return m_clientProtocolConfig.value(config_key::junkPacketCount);
+ case Roles::ClientJunkPacketMinSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMinSize);
+ case Roles::ClientJunkPacketMaxSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMaxSize);
+
+ case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount);
+ case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize);
+ case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize);
+ case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize);
+ case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize);
+ case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader);
+ case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader);
+ case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader);
+ case Roles::ServerTransportPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::transportPacketMagicHeader);
}
return QVariant();
@@ -68,51 +86,63 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
m_fullConfig = config;
- QJsonObject protocolConfig = config.value(config_key::awg).toObject();
+ QJsonObject serverProtocolConfig = config.value(config_key::awg).toObject();
auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Awg), Proto::Awg);
- m_protocolConfig.insert(config_key::transport_proto, protocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
- m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
- m_protocolConfig[config_key::port] = protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
- m_protocolConfig[config_key::mtu] = protocolConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
- m_protocolConfig[config_key::junkPacketCount] =
- protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
- m_protocolConfig[config_key::junkPacketMinSize] =
- protocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
- m_protocolConfig[config_key::junkPacketMaxSize] =
- protocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
- m_protocolConfig[config_key::initPacketJunkSize] =
- protocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
- m_protocolConfig[config_key::responsePacketJunkSize] =
- protocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
- m_protocolConfig[config_key::initPacketMagicHeader] =
- protocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
- m_protocolConfig[config_key::responsePacketMagicHeader] =
- protocolConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader);
- m_protocolConfig[config_key::underloadPacketMagicHeader] =
- protocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
- m_protocolConfig[config_key::transportPacketMagicHeader] =
- protocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
+ m_serverProtocolConfig.insert(config_key::transport_proto,
+ serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
+ m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
+ m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
+ m_serverProtocolConfig[config_key::junkPacketCount] =
+ serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
+ m_serverProtocolConfig[config_key::junkPacketMinSize] =
+ serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
+ m_serverProtocolConfig[config_key::junkPacketMaxSize] =
+ serverProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
+ m_serverProtocolConfig[config_key::initPacketJunkSize] =
+ serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
+ m_serverProtocolConfig[config_key::responsePacketJunkSize] =
+ serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
+ m_serverProtocolConfig[config_key::initPacketMagicHeader] =
+ serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
+ m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
+ serverProtocolConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader);
+ m_serverProtocolConfig[config_key::underloadPacketMagicHeader] =
+ serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
+ m_serverProtocolConfig[config_key::transportPacketMagicHeader] =
+ serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
+ auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
+ QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
+ m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu);
+ m_clientProtocolConfig[config_key::junkPacketCount] =
+ clientProtocolConfig.value(config_key::junkPacketCount).toString(m_serverProtocolConfig[config_key::junkPacketCount].toString());
+ m_clientProtocolConfig[config_key::junkPacketMinSize] =
+ clientProtocolConfig.value(config_key::junkPacketMinSize).toString(m_serverProtocolConfig[config_key::junkPacketMinSize].toString());
+ m_clientProtocolConfig[config_key::junkPacketMaxSize] =
+ clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString());
endResetModel();
}
QJsonObject AwgConfigModel::getConfig()
{
const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
- const AwgConfig newConfig(m_protocolConfig);
+ const AwgConfig newConfig(m_serverProtocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
- m_protocolConfig.remove(config_key::last_config);
+ m_serverProtocolConfig.remove(config_key::last_config);
} else {
- auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
+ auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
- jsonConfig[config_key::mtu] = newConfig.mtu;
+ jsonConfig[config_key::mtu] = m_clientProtocolConfig[config_key::mtu];
+ jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount];
+ jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize];
+ jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize];
- m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
+ m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
- m_fullConfig.insert(config_key::awg, m_protocolConfig);
+ m_fullConfig.insert(config_key::awg, m_serverProtocolConfig);
return m_fullConfig;
}
@@ -126,50 +156,73 @@ bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2)
return (AwgConstant::messageInitiationSize + s1 == AwgConstant::messageResponseSize + s2);
}
+bool AwgConfigModel::isServerSettingsEqual()
+{
+ const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
+ const AwgConfig newConfig(m_serverProtocolConfig);
+
+ return oldConfig.hasEqualServerSettings(newConfig);
+}
+
QHash AwgConfigModel::roleNames() const
{
QHash roles;
roles[PortRole] = "port";
- roles[MtuRole] = "mtu";
- roles[JunkPacketCountRole] = "junkPacketCount";
- roles[JunkPacketMinSizeRole] = "junkPacketMinSize";
- roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize";
- roles[InitPacketJunkSizeRole] = "initPacketJunkSize";
- roles[ResponsePacketJunkSizeRole] = "responsePacketJunkSize";
- roles[InitPacketMagicHeaderRole] = "initPacketMagicHeader";
- roles[ResponsePacketMagicHeaderRole] = "responsePacketMagicHeader";
- roles[UnderloadPacketMagicHeaderRole] = "underloadPacketMagicHeader";
- roles[TransportPacketMagicHeaderRole] = "transportPacketMagicHeader";
+
+ roles[ClientMtuRole] = "clientMtu";
+ roles[ClientJunkPacketCountRole] = "clientJunkPacketCount";
+ roles[ClientJunkPacketMinSizeRole] = "clientJunkPacketMinSize";
+ roles[ClientJunkPacketMaxSizeRole] = "clientJunkPacketMaxSize";
+
+ roles[ServerJunkPacketCountRole] = "serverJunkPacketCount";
+ roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize";
+ roles[ServerJunkPacketMaxSizeRole] = "serverJunkPacketMaxSize";
+ roles[ServerInitPacketJunkSizeRole] = "serverInitPacketJunkSize";
+ roles[ServerResponsePacketJunkSizeRole] = "serverResponsePacketJunkSize";
+ roles[ServerInitPacketMagicHeaderRole] = "serverInitPacketMagicHeader";
+ roles[ServerResponsePacketMagicHeaderRole] = "serverResponsePacketMagicHeader";
+ roles[ServerUnderloadPacketMagicHeaderRole] = "serverUnderloadPacketMagicHeader";
+ roles[ServerTransportPacketMagicHeaderRole] = "serverTransportPacketMagicHeader";
return roles;
}
-AwgConfig::AwgConfig(const QJsonObject &jsonConfig)
+AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
{
- port = jsonConfig.value(config_key::port).toString(protocols::awg::defaultPort);
- mtu = jsonConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
- junkPacketCount = jsonConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
- junkPacketMinSize = jsonConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
- junkPacketMaxSize = jsonConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
- initPacketJunkSize = jsonConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
- responsePacketJunkSize = jsonConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
- initPacketMagicHeader = jsonConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
- responsePacketMagicHeader =
- jsonConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader);
- underloadPacketMagicHeader =
- jsonConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
- transportPacketMagicHeader =
- jsonConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
+ auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString();
+ QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
+ clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu);
+ clientJunkPacketCount = clientProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
+ clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
+ clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
+
+ port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
+ serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
+ serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
+ serverJunkPacketMaxSize = serverProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
+ serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
+ serverResponsePacketJunkSize =
+ serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
+ serverInitPacketMagicHeader =
+ serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
+ serverResponsePacketMagicHeader =
+ serverProtocolConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader);
+ serverUnderloadPacketMagicHeader =
+ serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
+ serverTransportPacketMagicHeader =
+ serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
}
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
{
- if (port != other.port || junkPacketCount != other.junkPacketCount || junkPacketMinSize != other.junkPacketMinSize
- || junkPacketMaxSize != other.junkPacketMaxSize || initPacketJunkSize != other.initPacketJunkSize
- || responsePacketJunkSize != other.responsePacketJunkSize || initPacketMagicHeader != other.initPacketMagicHeader
- || responsePacketMagicHeader != other.responsePacketMagicHeader || underloadPacketMagicHeader != other.underloadPacketMagicHeader
- || transportPacketMagicHeader != other.transportPacketMagicHeader) {
+ if (port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
+ || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
+ || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
+ || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader
+ || serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader
+ || serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader
+ || serverTransportPacketMagicHeader != other.serverTransportPacketMagicHeader) {
return false;
}
return true;
@@ -177,7 +230,8 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const
{
- if (mtu != other.mtu) {
+ if (clientMtu != other.clientMtu || clientJunkPacketCount != other.clientJunkPacketCount
+ || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize) {
return false;
}
return true;
diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h
index 80375d38..06475bf5 100644
--- a/client/ui/models/protocols/awgConfigModel.h
+++ b/client/ui/models/protocols/awgConfigModel.h
@@ -16,16 +16,21 @@ struct AwgConfig
AwgConfig(const QJsonObject &jsonConfig);
QString port;
- QString mtu;
- QString junkPacketCount;
- QString junkPacketMinSize;
- QString junkPacketMaxSize;
- QString initPacketJunkSize;
- QString responsePacketJunkSize;
- QString initPacketMagicHeader;
- QString responsePacketMagicHeader;
- QString underloadPacketMagicHeader;
- QString transportPacketMagicHeader;
+
+ QString clientMtu;
+ QString clientJunkPacketCount;
+ QString clientJunkPacketMinSize;
+ QString clientJunkPacketMaxSize;
+
+ QString serverJunkPacketCount;
+ QString serverJunkPacketMinSize;
+ QString serverJunkPacketMaxSize;
+ QString serverInitPacketJunkSize;
+ QString serverResponsePacketJunkSize;
+ QString serverInitPacketMagicHeader;
+ QString serverResponsePacketMagicHeader;
+ QString serverUnderloadPacketMagicHeader;
+ QString serverTransportPacketMagicHeader;
bool hasEqualServerSettings(const AwgConfig &other) const;
bool hasEqualClientSettings(const AwgConfig &other) const;
@@ -39,16 +44,21 @@ class AwgConfigModel : public QAbstractListModel
public:
enum Roles {
PortRole = Qt::UserRole + 1,
- MtuRole,
- JunkPacketCountRole,
- JunkPacketMinSizeRole,
- JunkPacketMaxSizeRole,
- InitPacketJunkSizeRole,
- ResponsePacketJunkSizeRole,
- InitPacketMagicHeaderRole,
- ResponsePacketMagicHeaderRole,
- UnderloadPacketMagicHeaderRole,
- TransportPacketMagicHeaderRole
+
+ ClientMtuRole,
+ ClientJunkPacketCountRole,
+ ClientJunkPacketMinSizeRole,
+ ClientJunkPacketMaxSizeRole,
+
+ ServerJunkPacketCountRole,
+ ServerJunkPacketMinSizeRole,
+ ServerJunkPacketMaxSizeRole,
+ ServerInitPacketJunkSizeRole,
+ ServerResponsePacketJunkSizeRole,
+ ServerInitPacketMagicHeaderRole,
+ ServerResponsePacketMagicHeaderRole,
+ ServerUnderloadPacketMagicHeaderRole,
+ ServerTransportPacketMagicHeaderRole
};
explicit AwgConfigModel(QObject *parent = nullptr);
@@ -65,12 +75,15 @@ public slots:
bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4);
bool isPacketSizeEqual(const int s1, const int s2);
+ bool isServerSettingsEqual();
+
protected:
QHash roleNames() const override;
private:
DockerContainer m_container;
- QJsonObject m_protocolConfig;
+ QJsonObject m_serverProtocolConfig;
+ QJsonObject m_clientProtocolConfig;
QJsonObject m_fullConfig;
};
diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp
index 65bf2bb3..555915de 100644
--- a/client/ui/models/protocols/wireguardConfigModel.cpp
+++ b/client/ui/models/protocols/wireguardConfigModel.cpp
@@ -21,8 +21,8 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
}
switch (role) {
- case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
- case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
+ case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
+ case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
}
emit dataChanged(index, index, QList { role });
@@ -36,8 +36,8 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
}
switch (role) {
- case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
- case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
+ case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
+ case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
}
return QVariant();
@@ -49,17 +49,18 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
m_container = ContainerProps::containerFromString(config.value(config_key::container).toString());
m_fullConfig = config;
- QJsonObject protocolConfig = config.value(config_key::wireguard).toObject();
+ QJsonObject serverProtocolConfig = config.value(config_key::wireguard).toObject();
- auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::WireGuard), Proto::WireGuard);
- m_protocolConfig.insert(config_key::transport_proto,
- protocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
- m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
- m_protocolConfig[config_key::port] =
- protocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
+ auto defaultTransportProto =
+ ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::WireGuard), Proto::WireGuard);
+ m_serverProtocolConfig.insert(config_key::transport_proto,
+ serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
+ m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
+ m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
- m_protocolConfig[config_key::mtu] =
- protocolConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
+ auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
+ QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
+ m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu);
endResetModel();
}
@@ -67,36 +68,47 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
QJsonObject WireGuardConfigModel::getConfig()
{
const WgConfig oldConfig(m_fullConfig.value(config_key::wireguard).toObject());
- const WgConfig newConfig(m_protocolConfig);
+ const WgConfig newConfig(m_serverProtocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
- m_protocolConfig.remove(config_key::last_config);
+ m_serverProtocolConfig.remove(config_key::last_config);
} else {
- auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
+ auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
- jsonConfig[config_key::mtu] = newConfig.mtu;
+ jsonConfig[config_key::mtu] = m_clientProtocolConfig[config_key::mtu];
- m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
+ m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
- m_fullConfig.insert(config_key::wireguard, m_protocolConfig);
+ m_fullConfig.insert(config_key::wireguard, m_serverProtocolConfig);
return m_fullConfig;
}
+bool WireGuardConfigModel::isServerSettingsEqual()
+{
+ const WgConfig oldConfig(m_fullConfig.value(config_key::wireguard).toObject());
+ const WgConfig newConfig(m_serverProtocolConfig);
+
+ return oldConfig.hasEqualServerSettings(newConfig);
+}
+
QHash WireGuardConfigModel::roleNames() const
{
QHash roles;
roles[PortRole] = "port";
- roles[MtuRole] = "mtu";
+ roles[ClientMtuRole] = "clientMtu";
return roles;
}
-WgConfig::WgConfig(const QJsonObject &jsonConfig)
+WgConfig::WgConfig(const QJsonObject &serverProtocolConfig)
{
- port = jsonConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
- mtu = jsonConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
+ auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString();
+ QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
+ clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu);
+
+ port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
}
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
@@ -109,7 +121,7 @@ bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
bool WgConfig::hasEqualClientSettings(const WgConfig &other) const
{
- if (mtu != other.mtu) {
+ if (clientMtu != other.clientMtu) {
return false;
}
return true;
diff --git a/client/ui/models/protocols/wireguardConfigModel.h b/client/ui/models/protocols/wireguardConfigModel.h
index 6cec76dd..a02bea5a 100644
--- a/client/ui/models/protocols/wireguardConfigModel.h
+++ b/client/ui/models/protocols/wireguardConfigModel.h
@@ -11,7 +11,7 @@ struct WgConfig
WgConfig(const QJsonObject &jsonConfig);
QString port;
- QString mtu;
+ QString clientMtu;
bool hasEqualServerSettings(const WgConfig &other) const;
bool hasEqualClientSettings(const WgConfig &other) const;
@@ -25,7 +25,7 @@ class WireGuardConfigModel : public QAbstractListModel
public:
enum Roles {
PortRole = Qt::UserRole + 1,
- MtuRole
+ ClientMtuRole
};
explicit WireGuardConfigModel(QObject *parent = nullptr);
@@ -39,12 +39,15 @@ public slots:
void updateModel(const QJsonObject &config);
QJsonObject getConfig();
+ bool isServerSettingsEqual();
+
protected:
QHash roleNames() const override;
private:
DockerContainer m_container;
- QJsonObject m_protocolConfig;
+ QJsonObject m_serverProtocolConfig;
+ QJsonObject m_clientProtocolConfig;
QJsonObject m_fullConfig;
};
diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp
index 32447cd4..019b2d2f 100644
--- a/client/ui/models/protocols_model.cpp
+++ b/client/ui/models/protocols_model.cpp
@@ -16,9 +16,11 @@ QHash ProtocolsModel::roleNames() const
QHash roles;
roles[ProtocolNameRole] = "protocolName";
- roles[ProtocolPageRole] = "protocolPage";
+ roles[ServerProtocolPageRole] = "serverProtocolPage";
+ roles[ClientProtocolPageRole] = "clientProtocolPage";
roles[ProtocolIndexRole] = "protocolIndex";
roles[RawConfigRole] = "rawConfig";
+ roles[IsClientProtocolExistsRole] = "isClientProtocolExists";
return roles;
}
@@ -34,8 +36,10 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
amnezia::Proto proto = ProtocolProps::protoFromString(m_content.keys().at(index.row()));
return ProtocolProps::protocolHumanNames().value(proto);
}
- case ProtocolPageRole:
- return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row()))));
+ case ServerProtocolPageRole:
+ return static_cast(serverProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row()))));
+ case ClientProtocolPageRole:
+ return static_cast(clientProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row()))));
case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row()));
case RawConfigRole: {
auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject();
@@ -50,6 +54,15 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
}
return rawConfig;
}
+ case IsClientProtocolExistsRole: {
+ auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject();
+ auto lastConfigJsonDoc =
+ QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8());
+ auto lastConfigJson = lastConfigJsonDoc.object();
+
+ auto configString = lastConfigJson.value(config_key::config).toString();
+ return !configString.isEmpty();
+ }
}
return QVariant();
@@ -70,7 +83,7 @@ QJsonObject ProtocolsModel::getConfig()
return config;
}
-PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const
+PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const
{
switch (protocol) {
case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
@@ -90,3 +103,12 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
}
}
+
+PageLoader::PageEnum ProtocolsModel::clientProtocolPage(Proto protocol) const
+{
+ switch (protocol) {
+ case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardClientSettings;
+ case Proto::Awg: return PageLoader::PageEnum::PageProtocolAwgClientSettings;
+ default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
+ }
+}
diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h
index 5ee8a3dd..5c52ee86 100644
--- a/client/ui/models/protocols_model.h
+++ b/client/ui/models/protocols_model.h
@@ -13,9 +13,11 @@ class ProtocolsModel : public QAbstractListModel
public:
enum Roles {
ProtocolNameRole = Qt::UserRole + 1,
- ProtocolPageRole,
+ ServerProtocolPageRole,
+ ClientProtocolPageRole,
ProtocolIndexRole,
- RawConfigRole
+ RawConfigRole,
+ IsClientProtocolExistsRole
};
ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr);
@@ -33,7 +35,8 @@ protected:
QHash roleNames() const override;
private:
- PageLoader::PageEnum protocolPage(Proto protocol) const;
+ PageLoader::PageEnum serverProtocolPage(Proto protocol) const;
+ PageLoader::PageEnum clientProtocolPage(Proto protocol) const;
std::shared_ptr m_settings;
diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp
index 85e5dae2..b72b10c3 100644
--- a/client/ui/models/servers_model.cpp
+++ b/client/ui/models/servers_model.cpp
@@ -22,7 +22,7 @@ namespace
constexpr char serviceProtocol[] = "service_protocol";
constexpr char publicKeyInfo[] = "public_key";
- constexpr char endDate[] = "end_date";
+ constexpr char expiresAt[] = "expires_at";
}
}
@@ -39,6 +39,9 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent)
emit ServersModel::defaultServerNameChanged();
updateDefaultServerContainersModel();
});
+
+ connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
+ connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
}
int ServersModel::rowCount(const QModelIndex &parent) const
@@ -79,6 +82,12 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int
return true;
}
+bool ServersModel::setData(const int index, const QVariant &value, int role)
+{
+ QModelIndex modelIndex = this->index(index);
+ return setData(modelIndex, value, role);
+}
+
QVariant ServersModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) {
@@ -679,6 +688,18 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
return {};
}
+bool ServersModel::setProcessedServerData(const QString &roleString, const QVariant &value)
+{
+ const auto roles = roleNames();
+ for (auto it = roles.begin(); it != roles.end(); it++) {
+ if (QString(it.value()) == roleString) {
+ return setData(m_processedServerIndex, value, it.key());
+ }
+ }
+
+ return false;
+}
+
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
{
auto server = m_servers.at(m_defaultServerIndex).toObject();
@@ -718,9 +739,9 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject();
- const QString endDate = publicKeyInfo.value(configKey::endDate).toString();
- if (endDate.isEmpty()) {
- publicKeyInfo.insert(configKey::endDate, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
+ const QString expiresAt = publicKeyInfo.value(configKey::expiresAt).toString();
+ if (expiresAt.isEmpty()) {
+ publicKeyInfo.insert(configKey::expiresAt, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo);
serverConfig.insert(configKey::apiConfig, apiConfig);
editServer(serverConfig, serverIndex);
@@ -728,8 +749,8 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
return false;
}
- auto endDateDateTime = QDateTime::fromString(endDate, Qt::ISODate).toUTC();
- if (endDateDateTime < QDateTime::currentDateTimeUtc()) {
+ auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC();
+ if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) {
return true;
}
return false;
@@ -771,5 +792,5 @@ const QString ServersModel::getDefaultServerImagePathCollapsed()
if (countryCode.isEmpty()) {
return "";
}
- return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode);
+ return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper());
}
diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h
index 0f18ea30..78bc22cc 100644
--- a/client/ui/models/servers_model.h
+++ b/client/ui/models/servers_model.h
@@ -46,6 +46,7 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ bool setData(const int index, const QVariant &value, int role = Qt::EditRole);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const int index, int role = Qt::DisplayRole) const;
@@ -115,6 +116,7 @@ public slots:
QVariant getDefaultServerData(const QString roleString);
QVariant getProcessedServerData(const QString roleString);
+ bool setProcessedServerData(const QString &roleString, const QVariant &value);
bool isDefaultServerDefaultContainerHasSplitTunneling();
@@ -127,6 +129,9 @@ protected:
signals:
void processedServerIndexChanged(const int index);
+ // emitted when the processed server index or processed server data is changed
+ void processedServerChanged();
+
void defaultServerIndexChanged(const int index);
void defaultServerNameChanged();
void defaultServerDescriptionChanged();
diff --git a/client/ui/property_helper.h b/client/ui/property_helper.h
deleted file mode 100644
index 927105b3..00000000
--- a/client/ui/property_helper.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef PROPERTY_HELPER_H
-#define PROPERTY_HELPER_H
-
-#include
-
-#define AUTO_PROPERTY(TYPE, NAME) \
- Q_PROPERTY(TYPE NAME READ NAME WRITE set_ ## NAME NOTIFY NAME ## Changed ) \
- public: \
- TYPE NAME() const { return m_ ## NAME ; } \
- void set_ ## NAME(TYPE value) { \
- if (m_ ## NAME == value) return; \
- m_ ## NAME = value; \
- emit NAME ## Changed(value); \
- } \
- Q_SIGNAL void NAME ## Changed(TYPE value);\
- private: \
- TYPE m_ ## NAME{};
-
-#define READONLY_PROPERTY(TYPE, NAME) \
- Q_PROPERTY(TYPE NAME READ NAME CONSTANT ) \
- public: \
- TYPE NAME() const { return m_ ## NAME ; } \
- private: \
- void NAME(TYPE value) {m_ ## NAME = value; } \
- TYPE m_ ## NAME{};
-
-#endif // PROPERTY_HELPER_H
diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml
index cb706158..fa18703b 100644
--- a/client/ui/qml/Components/ConnectButton.qml
+++ b/client/ui/qml/Components/ConnectButton.qml
@@ -14,6 +14,7 @@ Button {
property string defaultButtonColor: AmneziaStyle.color.paleGray
property string progressButtonColor: AmneziaStyle.color.paleGray
property string connectedButtonColor: AmneziaStyle.color.goldenApricot
+ property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
implicitWidth: 190
implicitHeight: 190
@@ -50,14 +51,14 @@ Button {
verticalOffset: 0
radius: 10
samples: 25
- color: root.activeFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
+ color: root.buttonActiveFocus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.goldenApricot
source: backgroundCircle
}
ShapePath {
fillColor: AmneziaStyle.color.transparent
strokeColor: AmneziaStyle.color.paleGray
- strokeWidth: root.activeFocus ? 1 : 0
+ strokeWidth: root.buttonActiveFocus ? 1 : 0
capStyle: ShapePath.RoundCap
PathAngleArc {
@@ -81,14 +82,14 @@ Button {
return defaultButtonColor
}
}
- strokeWidth: root.activeFocus ? 2 : 3
+ strokeWidth: root.buttonActiveFocus ? 2 : 3
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
- radiusX: 93 - (root.activeFocus ? 2 : 0)
- radiusY: 93 - (root.activeFocus ? 2 : 0)
+ radiusX: 93 - (root.buttonActiveFocus ? 2 : 0)
+ radiusY: 93 - (root.buttonActiveFocus ? 2 : 0)
startAngle: 0
sweepAngle: 360
}
diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml
index 3235ad0a..d2bf28ab 100644
--- a/client/ui/qml/Components/ShareConnectionDrawer.qml
+++ b/client/ui/qml/Components/ShareConnectionDrawer.qml
@@ -84,7 +84,7 @@ DrawerType2 {
Layout.topMargin: 16
text: qsTr("Share")
- imageSource: "qrc:/images/controls/share-2.svg"
+ leftImageSource: "qrc:/images/controls/share-2.svg"
KeyNavigation.tab: copyConfigTextButton
@@ -120,7 +120,7 @@ DrawerType2 {
borderWidth: 1
text: qsTr("Copy")
- imageSource: "qrc:/images/controls/copy.svg"
+ leftImageSource: "qrc:/images/controls/copy.svg"
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
@@ -143,7 +143,7 @@ DrawerType2 {
borderWidth: 1
text: qsTr("Copy config string")
- imageSource: "qrc:/images/controls/copy.svg"
+ leftImageSource: "qrc:/images/controls/copy.svg"
KeyNavigation.tab: showSettingsButton
}
diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml
index 5c599013..828c32bc 100644
--- a/client/ui/qml/Controls2/BasicButtonType.qml
+++ b/client/ui/qml/Controls2/BasicButtonType.qml
@@ -22,9 +22,10 @@ Button {
property int borderWidth: 0
property int borderFocusedWidth: 1
- property string imageSource
+ property string leftImageSource
property string rightImageSource
- property string leftImageColor: textColor
+ property string leftImageColor
+ property bool changeLeftImageSize: true
property bool squareLeftSide: false
@@ -127,18 +128,23 @@ Button {
anchors.centerIn: parent
Image {
- Layout.preferredHeight: 20
- Layout.preferredWidth: 20
-
- source: root.imageSource
- visible: root.imageSource === "" ? false : true
+ id: leftImage
+ source: root.leftImageSource
+ visible: root.leftImageSource === "" ? false : true
layer {
- enabled: true
+ enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
+
+ Component.onCompleted: {
+ if (root.changeLeftImageSize) {
+ leftImage.Layout.preferredHeight = 20
+ leftImage.Layout.preferredWidth = 20
+ }
+ }
}
ButtonTextType {
diff --git a/client/ui/qml/Controls2/BusyIndicatorType.qml b/client/ui/qml/Controls2/BusyIndicatorType.qml
index 55af280f..480f25c1 100644
--- a/client/ui/qml/Controls2/BusyIndicatorType.qml
+++ b/client/ui/qml/Controls2/BusyIndicatorType.qml
@@ -14,7 +14,7 @@ Popup {
visible: false
Overlay.modal: Rectangle {
- color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ color: AmneziaStyle.color.translucentMidnightBlack
}
background: Rectangle {
diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml
index 50f84dbf..f584a8fc 100644
--- a/client/ui/qml/Controls2/CardType.qml
+++ b/client/ui/qml/Controls2/CardType.qml
@@ -19,7 +19,7 @@ RadioButton {
property string textColor: AmneziaStyle.color.midnightBlack
- property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3)
+ property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
property string selectedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent
property int borderWidth: 0
diff --git a/client/ui/qml/Controls2/CardWithIconsType.qml b/client/ui/qml/Controls2/CardWithIconsType.qml
index 8630434b..18a29b87 100644
--- a/client/ui/qml/Controls2/CardWithIconsType.qml
+++ b/client/ui/qml/Controls2/CardWithIconsType.qml
@@ -79,6 +79,7 @@ Button {
visible: text !== ""
color: AmneziaStyle.color.mutedGray
+ textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16
@@ -144,6 +145,7 @@ Button {
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
+ enabled: root.enabled
onEntered: {
backgroundRect.color = root.hoveredColor
diff --git a/client/ui/qml/Controls2/DrawerType2.qml b/client/ui/qml/Controls2/DrawerType2.qml
index 6647bc88..c4b584c1 100644
--- a/client/ui/qml/Controls2/DrawerType2.qml
+++ b/client/ui/qml/Controls2/DrawerType2.qml
@@ -92,7 +92,7 @@ Item {
id: background
anchors.fill: parent
- color: root.isCollapsed ? AmneziaStyle.color.transparent : Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
Behavior on color {
PropertyAnimation { duration: 200 }
diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml
index 3b1609f7..41faf108 100644
--- a/client/ui/qml/Controls2/LabelWithButtonType.qml
+++ b/client/ui/qml/Controls2/LabelWithButtonType.qml
@@ -20,7 +20,8 @@ Item {
property string buttonImageSource
property string rightImageSource
property string leftImageSource
- property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3
+ property bool isLeftImageHoverEnabled: true
+ property bool isSmallLeftImage: false
property alias rightButton: rightImage
property alias eyeButton: eyeImage
@@ -114,9 +115,9 @@ Item {
visible: leftImageSource ? true : false
- Layout.preferredHeight: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitHeight : 56
- Layout.preferredWidth: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitWidth : 56
- Layout.rightMargin: rightImageSource || !isLeftImageHoverEnabled ? 16 : 0
+ Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56
+ Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56
+ Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0
radius: 12
color: AmneziaStyle.color.transparent
diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml
index bd4aa4fb..7a6a770e 100644
--- a/client/ui/qml/Controls2/PopupType.qml
+++ b/client/ui/qml/Controls2/PopupType.qml
@@ -24,7 +24,7 @@ Popup {
Overlay.modal: Rectangle {
visible: root.closeButtonVisible
- color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ color: AmneziaStyle.color.translucentMidnightBlack
}
onOpened: {
diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml
index 9b2885ea..43c35778 100644
--- a/client/ui/qml/Controls2/SwitcherType.qml
+++ b/client/ui/qml/Controls2/SwitcherType.qml
@@ -102,8 +102,7 @@ Switch {
contentItem: ColumnLayout {
id: content
- anchors.top: parent.top
- anchors.bottom: parent.bottom
+ anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
ListItemTitleType {
diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml
index 4ec0976b..365faa94 100644
--- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml
+++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml
@@ -183,7 +183,7 @@ Item {
focusPolicy: Qt.NoFocus
text: root.buttonText
- imageSource: root.buttonImageSource
+ leftImageSource: root.buttonImageSource
anchors.top: content.top
anchors.bottom: content.bottom
diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml
index 1bd7fef6..3a652da6 100644
--- a/client/ui/qml/Controls2/TopCloseButtonType.qml
+++ b/client/ui/qml/Controls2/TopCloseButtonType.qml
@@ -14,7 +14,7 @@ Popup {
visible: false
Overlay.modal: Rectangle {
- color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ color: AmneziaStyle.color.translucentMidnightBlack
}
background: Rectangle {
diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml
index c0038246..1abfbe3a 100644
--- a/client/ui/qml/Modules/Style/AmneziaStyle.qml
+++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml
@@ -22,5 +22,9 @@ QtObject {
readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12)
readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08)
readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05)
+ readonly property color translucentMidnightBlack: Qt.rgba(14/255, 14/255, 17/255, 0.8)
+ readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
+ readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
+ readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
}
}
diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml
index af6f773a..5da40eff 100644
--- a/client/ui/qml/Pages2/PageDevMenu.qml
+++ b/client/ui/qml/Pages2/PageDevMenu.qml
@@ -89,6 +89,21 @@ PageType {
// KeyNavigation.tab: saveButton
}
+
+ SwitcherType {
+ id: switcher
+
+ Layout.fillWidth: true
+ Layout.rightMargin: 16
+ Layout.leftMargin: 16
+ Layout.topMargin: 16
+
+ text: qsTr("Dev gateway environment")
+ checked: SettingsController.isDevGatewayEnv
+ onToggled: function() {
+ SettingsController.isDevGatewayEnv = checked
+ }
+ }
}
}
}
diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml
index 8074337a..8422a10f 100644
--- a/client/ui/qml/Pages2/PageHome.qml
+++ b/client/ui/qml/Pages2/PageHome.qml
@@ -98,7 +98,6 @@ PageType {
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.mutedGray
- leftImageColor: AmneziaStyle.color.transparent
borderWidth: 0
buttonTextLabel.lineHeight: 20
@@ -110,7 +109,7 @@ PageType {
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
- imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
+ leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
rightImageSource: "qrc:/images/controls/chevron-down.svg"
Keys.onEnterPressed: splitTunnelingButton.clicked()
@@ -166,6 +165,7 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
+ spacing: 0
Component.onCompleted: {
drawer.collapsedHeight = collapsed.implicitHeight
@@ -267,18 +267,39 @@ PageType {
RowLayout {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 89 : 44
+ Layout.topMargin: 8
+ Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
spacing: 0
- Image {
- Layout.rightMargin: 8
- visible: source !== ""
- source: ServersModel.defaultServerImagePathCollapsed
- }
+ BasicButtonType {
+ enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
+ hoverEnabled: enabled
+
+ implicitHeight: 36
+
+ leftPadding: 16
+ rightPadding: 16
+
+ defaultColor: AmneziaStyle.color.transparent
+ hoveredColor: AmneziaStyle.color.translucentWhite
+ pressedColor: AmneziaStyle.color.sheerWhite
+ disabledColor: AmneziaStyle.color.transparent
+ textColor: AmneziaStyle.color.mutedGray
+
+ buttonTextLabel.lineHeight: 16
+ buttonTextLabel.font.pixelSize: 13
+ buttonTextLabel.font.weight: 400
- LabelTextType {
- id: collapsedServerMenuDescription
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
+ leftImageSource: ServersModel.defaultServerImagePathCollapsed
+ changeLeftImageSize: false
+
+ rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
+
+ onClicked: {
+ ServersModel.processedIndex = ServersModel.defaultIndex
+ PageController.goToPage(PageEnum.PageSettingsServerInfo)
+ }
}
}
}
@@ -316,8 +337,8 @@ PageType {
rootButtonImageColor: AmneziaStyle.color.midnightBlack
rootButtonBackgroundColor: AmneziaStyle.color.paleGray
- rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8)
- rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65)
+ rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray
+ rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
rootButtonTextTopMargin: 8
diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml
new file mode 100644
index 00000000..2b912f18
--- /dev/null
+++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml
@@ -0,0 +1,312 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import SortFilterProxyModel 0.2
+
+import PageEnum 1.0
+
+import "./"
+import "../Controls2"
+import "../Controls2/TextTypes"
+import "../Config"
+import "../Components"
+
+
+PageType {
+ id: root
+
+ defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
+
+ Item {
+ id: focusItem
+ onFocusChanged: {
+ if (activeFocus) {
+ fl.ensureVisible(focusItem)
+ }
+ }
+ KeyNavigation.tab: backButton
+ }
+
+ ColumnLayout {
+ id: backButtonLayout
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.topMargin: 20
+
+ BackButtonType {
+ id: backButton
+ KeyNavigation.tab: listview.currentItem.mtuTextField.textField
+ }
+ }
+
+ FlickableType {
+ id: fl
+ anchors.top: backButtonLayout.bottom
+ anchors.bottom: parent.bottom
+ contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin
+
+ Column {
+ id: content
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ ListView {
+ id: listview
+
+ width: parent.width
+ height: listview.contentItem.height
+
+ clip: true
+ interactive: false
+
+ model: AwgConfigModel
+
+ delegate: Item {
+ id: delegateItem
+ implicitWidth: listview.width
+ implicitHeight: col.implicitHeight
+
+ property alias mtuTextField: mtuTextField
+ property bool isSaveButtonEnabled: mtuTextField.errorText === "" &&
+ junkPacketMaxSizeTextField.errorText === "" &&
+ junkPacketMinSizeTextField.errorText === "" &&
+ junkPacketCountTextField.errorText === ""
+
+ ColumnLayout {
+ id: col
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ spacing: 0
+
+ HeaderType {
+ Layout.fillWidth: true
+
+ headerText: qsTr("AmneziaWG settings")
+ }
+
+ TextFieldWithHeaderType {
+ id: mtuTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 40
+
+ headerText: qsTr("MTU")
+ textFieldText: clientMtu
+ textField.validator: IntValidator { bottom: 576; top: 65535 }
+
+ textField.onEditingFinished: {
+ if (textFieldText !== clientMtu) {
+ clientMtu = textFieldText
+ }
+ }
+ checkEmptyText: true
+ KeyNavigation.tab: junkPacketCountTextField.textField
+ }
+
+ TextFieldWithHeaderType {
+ id: junkPacketCountTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ headerText: "Jc - Junk packet count"
+ textFieldText: clientJunkPacketCount
+ textField.validator: IntValidator { bottom: 0 }
+ parentFlickable: fl
+
+ textField.onEditingFinished: {
+ if (textFieldText !== clientJunkPacketCount) {
+ clientJunkPacketCount = textFieldText
+ }
+ }
+
+ checkEmptyText: true
+
+ KeyNavigation.tab: junkPacketMinSizeTextField.textField
+ }
+
+ TextFieldWithHeaderType {
+ id: junkPacketMinSizeTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ headerText: "Jmin - Junk packet minimum size"
+ textFieldText: clientJunkPacketMinSize
+ textField.validator: IntValidator { bottom: 0 }
+ parentFlickable: fl
+
+ textField.onEditingFinished: {
+ if (textFieldText !== clientJunkPacketMinSize) {
+ clientJunkPacketMinSize = textFieldText
+ }
+ }
+
+ checkEmptyText: true
+
+ KeyNavigation.tab: junkPacketMaxSizeTextField.textField
+ }
+
+ TextFieldWithHeaderType {
+ id: junkPacketMaxSizeTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ headerText: "Jmax - Junk packet maximum size"
+ textFieldText: clientJunkPacketMaxSize
+ textField.validator: IntValidator { bottom: 0 }
+ parentFlickable: fl
+
+ textField.onEditingFinished: {
+ if (textFieldText !== clientJunkPacketMaxSize) {
+ clientJunkPacketMaxSize = textFieldText
+ }
+ }
+
+ checkEmptyText: true
+
+ Keys.onTabPressed: saveButton.forceActiveFocus()
+ }
+
+ Header2TextType {
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ text: qsTr("Server settings")
+ }
+
+ TextFieldWithHeaderType {
+ id: portTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+
+ enabled: false
+
+ headerText: qsTr("Port")
+ textFieldText: port
+ }
+
+ TextFieldWithHeaderType {
+ id: initPacketJunkSizeTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: false
+
+ headerText: "S1 - Init packet junk size"
+ textFieldText: serverInitPacketJunkSize
+ }
+
+ TextFieldWithHeaderType {
+ id: responsePacketJunkSizeTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: false
+
+ headerText: "S2 - Response packet junk size"
+ textFieldText: serverResponsePacketJunkSize
+ }
+
+ TextFieldWithHeaderType {
+ id: initPacketMagicHeaderTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: false
+
+ headerText: "H1 - Init packet magic header"
+ textFieldText: serverInitPacketMagicHeader
+ }
+
+ TextFieldWithHeaderType {
+ id: responsePacketMagicHeaderTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: false
+
+ headerText: "H2 - Response packet magic header"
+ textFieldText: serverResponsePacketMagicHeader
+ }
+
+ TextFieldWithHeaderType {
+ id: underloadPacketMagicHeaderTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+ parentFlickable: fl
+
+ enabled: false
+
+ headerText: "H3 - Underload packet magic header"
+ textFieldText: serverUnderloadPacketMagicHeader
+ }
+
+ TextFieldWithHeaderType {
+ id: transportPacketMagicHeaderTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ enabled: false
+
+ headerText: "H4 - Transport packet magic header"
+ textFieldText: serverTransportPacketMagicHeader
+ }
+ }
+ }
+ }
+ }
+ }
+
+ BasicButtonType {
+ id: saveButton
+
+ anchors.right: root.right
+ anchors.left: root.left
+ anchors.bottom: root.bottom
+
+ anchors.topMargin: 24
+ anchors.bottomMargin: 24
+ anchors.rightMargin: 16
+ anchors.leftMargin: 16
+
+ enabled: listview.currentItem.isSaveButtonEnabled
+
+ text: qsTr("Save")
+
+ Keys.onTabPressed: lastItemTabClicked(focusItem)
+
+ clickedFunc: function() {
+ forceActiveFocus()
+ var headerText = qsTr("Save settings?")
+ var descriptionText = qsTr("Only the settings for this device will be changed")
+ var yesButtonText = qsTr("Continue")
+ var noButtonText = qsTr("Cancel")
+
+ var yesButtonFunction = function() {
+ if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
+ PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
+ return
+ }
+
+ PageController.goToPage(PageEnum.PageSetupWizardInstalling);
+ InstallController.updateContainer(AwgConfigModel.getConfig())
+ }
+ var noButtonFunction = function() {
+ if (!GC.isMobile()) {
+ saveButton.forceActiveFocus()
+ }
+ }
+ showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
+ }
+ }
+}
diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml
index 8651fa27..27ea66f9 100644
--- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml
+++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml
@@ -57,8 +57,6 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
- enabled: ServersModel.isProcessedServerHasWriteAccess()
-
ListView {
id: listview
@@ -71,12 +69,12 @@ PageType {
model: AwgConfigModel
delegate: Item {
- id: _delegate
-
+ id: delegateItem
implicitWidth: listview.width
implicitHeight: col.implicitHeight
- property alias portTextField:portTextField
+ property alias portTextField: portTextField
+ property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
ColumnLayout {
id: col
@@ -101,6 +99,8 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 40
+ enabled: delegateItem.isEnabled
+
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
@@ -115,27 +115,6 @@ PageType {
checkEmptyText: true
- KeyNavigation.tab: mtuTextField.textField
- }
-
- TextFieldWithHeaderType {
- id: mtuTextField
- Layout.fillWidth: true
- Layout.topMargin: 16
-
- headerText: qsTr("MTU")
- textFieldText: mtu
- textField.validator: IntValidator { bottom: 576; top: 65535 }
-
- textField.onEditingFinished: {
- if (textFieldText === "") {
- textFieldText = "0"
- }
- if (textFieldText !== mtu) {
- mtu = textFieldText
- }
- }
- checkEmptyText: true
KeyNavigation.tab: junkPacketCountTextField.textField
}
@@ -145,7 +124,7 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jc - Junk packet count")
- textFieldText: junkPacketCount
+ textFieldText: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
@@ -154,8 +133,8 @@ PageType {
textFieldText = "0"
}
- if (textFieldText !== junkPacketCount) {
- junkPacketCount = textFieldText
+ if (textFieldText !== serverJunkPacketCount) {
+ serverJunkPacketCount = textFieldText
}
}
@@ -170,13 +149,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jmin - Junk packet minimum size")
- textFieldText: junkPacketMinSize
+ textFieldText: serverJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== junkPacketMinSize) {
- junkPacketMinSize = textFieldText
+ if (textFieldText !== serverJunkPacketMinSize) {
+ serverJunkPacketMinSize = textFieldText
}
}
@@ -191,13 +170,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size")
- textFieldText: junkPacketMaxSize
+ textFieldText: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== junkPacketMaxSize) {
- junkPacketMaxSize = textFieldText
+ if (textFieldText !== serverJunkPacketMaxSize) {
+ serverJunkPacketMaxSize = textFieldText
}
}
@@ -212,13 +191,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("S1 - Init packet junk size")
- textFieldText: initPacketJunkSize
+ textFieldText: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== initPacketJunkSize) {
- initPacketJunkSize = textFieldText
+ if (textFieldText !== serverInitPacketJunkSize) {
+ serverInitPacketJunkSize = textFieldText
}
}
@@ -233,13 +212,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("S2 - Response packet junk size")
- textFieldText: responsePacketJunkSize
+ textFieldText: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== responsePacketJunkSize) {
- responsePacketJunkSize = textFieldText
+ if (textFieldText !== serverResponsePacketJunkSize) {
+ serverResponsePacketJunkSize = textFieldText
}
}
@@ -254,13 +233,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H1 - Init packet magic header")
- textFieldText: initPacketMagicHeader
+ textFieldText: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== initPacketMagicHeader) {
- initPacketMagicHeader = textFieldText
+ if (textFieldText !== serverInitPacketMagicHeader) {
+ serverInitPacketMagicHeader = textFieldText
}
}
@@ -275,13 +254,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H2 - Response packet magic header")
- textFieldText: responsePacketMagicHeader
+ textFieldText: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== responsePacketMagicHeader) {
- responsePacketMagicHeader = textFieldText
+ if (textFieldText !== serverResponsePacketMagicHeader) {
+ serverResponsePacketMagicHeader = textFieldText
}
}
@@ -296,13 +275,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H4 - Transport packet magic header")
- textFieldText: transportPacketMagicHeader
+ textFieldText: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
- if (textFieldText !== transportPacketMagicHeader) {
- transportPacketMagicHeader = textFieldText
+ if (textFieldText !== serverTransportPacketMagicHeader) {
+ serverTransportPacketMagicHeader = textFieldText
}
}
@@ -318,12 +297,12 @@ PageType {
parentFlickable: fl
headerText: qsTr("H3 - Underload packet magic header")
- textFieldText: underloadPacketMagicHeader
+ textFieldText: serverUnderloadPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
- if (textFieldText !== underloadPacketMagicHeader) {
- underloadPacketMagicHeader = textFieldText
+ if (textFieldText !== serverUnderloadPacketMagicHeader) {
+ serverUnderloadPacketMagicHeader = textFieldText
}
}
@@ -356,18 +335,22 @@ PageType {
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
- if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text,
- transportPacketMagicHeaderTextField.textField.text,
- responsePacketMagicHeaderTextField.textField.text,
- initPacketMagicHeaderTextField.textField.text)) {
- PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique"))
- return
- }
+ forceActiveFocus()
- if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text),
- parseInt(responsePacketJunkSizeTextField.textField.text))) {
- PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)"))
- return
+ if (delegateItem.isEnabled) {
+ if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text,
+ transportPacketMagicHeaderTextField.textField.text,
+ responsePacketMagicHeaderTextField.textField.text,
+ initPacketMagicHeaderTextField.textField.text)) {
+ PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique"))
+ return
+ }
+
+ if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text),
+ parseInt(responsePacketJunkSizeTextField.textField.text))) {
+ PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)"))
+ return
+ }
}
var headerText = qsTr("Save settings?")
@@ -376,8 +359,6 @@ PageType {
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
- forceActiveFocus()
-
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml
new file mode 100644
index 00000000..007de5ca
--- /dev/null
+++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml
@@ -0,0 +1,179 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import SortFilterProxyModel 0.2
+
+import PageEnum 1.0
+
+import "./"
+import "../Controls2"
+import "../Controls2/TextTypes"
+import "../Config"
+import "../Components"
+
+
+PageType {
+ id: root
+
+ defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
+
+ Item {
+ id: focusItem
+ onFocusChanged: {
+ if (activeFocus) {
+ fl.ensureVisible(focusItem)
+ }
+ }
+ KeyNavigation.tab: backButton
+ }
+
+ ColumnLayout {
+ id: backButtonLayout
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.topMargin: 20
+
+ BackButtonType {
+ id: backButton
+ KeyNavigation.tab: listview.currentItem.mtuTextField.textField
+ }
+ }
+
+ FlickableType {
+ id: fl
+ anchors.top: backButtonLayout.bottom
+ anchors.bottom: parent.bottom
+ contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin
+
+ Column {
+ id: content
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ ListView {
+ id: listview
+
+ width: parent.width
+ height: listview.contentItem.height
+
+ clip: true
+ interactive: false
+
+ model: WireGuardConfigModel
+
+ delegate: Item {
+ id: delegateItem
+ implicitWidth: listview.width
+ implicitHeight: col.implicitHeight
+
+ property alias mtuTextField: mtuTextField
+ property bool isSaveButtonEnabled: mtuTextField.errorText === ""
+
+ ColumnLayout {
+ id: col
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ spacing: 0
+
+ HeaderType {
+ Layout.fillWidth: true
+
+ headerText: qsTr("WG settings")
+ }
+
+ TextFieldWithHeaderType {
+ id: mtuTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 40
+
+ headerText: qsTr("MTU")
+ textFieldText: clientMtu
+ textField.validator: IntValidator { bottom: 576; top: 65535 }
+
+ textField.onEditingFinished: {
+ if (textFieldText !== clientMtu) {
+ clientMtu = textFieldText
+ }
+ }
+ checkEmptyText: true
+ KeyNavigation.tab: saveButton
+ }
+
+ Header2TextType {
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ text: qsTr("Server settings")
+ }
+
+ TextFieldWithHeaderType {
+ id: portTextField
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+
+ enabled: false
+
+ headerText: qsTr("Port")
+ textFieldText: port
+ }
+ }
+ }
+ }
+ }
+ }
+
+ BasicButtonType {
+ id: saveButton
+
+ anchors.right: root.right
+ anchors.left: root.left
+ anchors.bottom: root.bottom
+
+ anchors.topMargin: 24
+ anchors.bottomMargin: 24
+ anchors.rightMargin: 16
+ anchors.leftMargin: 16
+
+ enabled: listview.currentItem.isSaveButtonEnabled
+
+ text: qsTr("Save")
+
+ Keys.onTabPressed: lastItemTabClicked(focusItem)
+
+ clickedFunc: function() {
+ forceActiveFocus()
+ var headerText = qsTr("Save settings?")
+ var descriptionText = qsTr("Only the settings for this device will be changed")
+ var yesButtonText = qsTr("Continue")
+ var noButtonText = qsTr("Cancel")
+
+ var yesButtonFunction = function() {
+ if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
+ PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
+ return
+ }
+
+ PageController.goToPage(PageEnum.PageSetupWizardInstalling);
+ InstallController.updateContainer(WireGuardConfigModel.getConfig())
+ }
+ var noButtonFunction = function() {
+ if (!GC.isMobile()) {
+ saveButton.forceActiveFocus()
+ }
+ }
+ showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
+ }
+ }
+}
diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml
index 758375b1..b5d08132 100644
--- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml
+++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml
@@ -72,7 +72,10 @@ PageType {
}
delegate: Item {
+ id: delegateItem
+
property alias focusItemId: portTextField.textField
+ property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
implicitWidth: listview.width
implicitHeight: col.implicitHeight
@@ -99,12 +102,14 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 40
+ enabled: delegateItem.isEnabled
+
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
- KeyNavigation.tab: mtuTextField.textField
+ KeyNavigation.tab: saveButton
textField.onEditingFinished: {
if (textFieldText !== port) {
@@ -115,52 +120,41 @@ PageType {
checkEmptyText: true
}
- TextFieldWithHeaderType {
- id: mtuTextField
- Layout.fillWidth: true
- Layout.topMargin: 16
-
- headerText: qsTr("MTU")
- textFieldText: mtu
- textField.validator: IntValidator { bottom: 576; top: 65535 }
-
- KeyNavigation.tab: saveButton
-
- textField.onEditingFinished: {
- if (textFieldText === "") {
- textFieldText = "0"
- }
- if (textFieldText !== mtu) {
- mtu = textFieldText
- }
- }
- checkEmptyText: true
- }
-
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
- enabled: mtuTextField.errorText === "" &&
- portTextField.errorText === ""
+ enabled: portTextField.errorText === ""
text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
- onClicked: {
+ onClicked: function() {
forceActiveFocus()
- if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
- PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
- return
- }
+ var headerText = qsTr("Save settings?")
+ var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
+ var yesButtonText = qsTr("Continue")
+ var noButtonText = qsTr("Cancel")
- PageController.goToPage(PageEnum.PageSetupWizardInstalling);
- InstallController.updateContainer(WireGuardConfigModel.getConfig())
- focusItem.forceActiveFocus()
+ var yesButtonFunction = function() {
+ if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
+ PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
+ return
+ }
+
+ PageController.goToPage(PageEnum.PageSetupWizardInstalling);
+ InstallController.updateContainer(WireGuardConfigModel.getConfig())
+ }
+ var noButtonFunction = function() {
+ if (!GC.isMobile()) {
+ saveRestartButton.forceActiveFocus()
+ }
+ }
+ showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
Keys.onEnterPressed: saveButton.clicked()
diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml
index 1e38a539..cde9ee20 100644
--- a/client/ui/qml/Pages2/PageSettingsAbout.qml
+++ b/client/ui/qml/Pages2/PageSettingsAbout.qml
@@ -120,7 +120,7 @@ PageType {
id: mailButton
Layout.fillWidth: true
- text: qsTr("Mail")
+ text: qsTr("support@amnezia.org")
descriptionText: qsTr("For reviews and bug reports")
leftImageSource: "qrc:/images/controls/mail.svg"
@@ -128,6 +128,8 @@ PageType {
parentFlickable: fl
clickedFunction: function() {
+ GC.copyToClipBoard(text)
+ PageController.showNotificationMessage(qsTr("Copied"))
}
}
diff --git a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
index 234e5142..600db85d 100644
--- a/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
+++ b/client/ui/qml/Pages2/PageSettingsApiLanguageList.qml
@@ -54,8 +54,14 @@ PageType {
imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex
+ checkable: !ConnectionController.isConnected
onClicked: {
+ if (ConnectionController.isConnected) {
+ PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
+ return
+ }
+
if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex
@@ -90,7 +96,7 @@ PageType {
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
- source: "qrc:/countriesFlags/images/flagKit/" + countryCode + ".svg"
+ source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
}
}
diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml
index f23e36d9..167e56e5 100644
--- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml
+++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml
@@ -56,12 +56,15 @@ PageType {
}
LabelWithImageType {
+ property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable")
+
Layout.fillWidth: true
Layout.margins: 16
imageSource: "qrc:/images/controls/history.svg"
- leftText: qsTr("Work period")
- rightText: ApiServicesModel.getSelectedServiceData("workPeriod")
+ leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period")
+ rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate")
+ : ApiServicesModel.getSelectedServiceData("workPeriod")
visible: rightText !== ""
}
@@ -132,8 +135,8 @@ PageType {
implicitHeight: 32
defaultColor: "transparent"
- hoveredColor: Qt.rgba(1, 1, 1, 0.08)
- pressedColor: Qt.rgba(1, 1, 1, 0.12)
+ hoveredColor: AmneziaStyle.color.translucentWhite
+ pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Reload API config")
@@ -172,8 +175,8 @@ PageType {
implicitHeight: 32
defaultColor: "transparent"
- hoveredColor: Qt.rgba(1, 1, 1, 0.08)
- pressedColor: Qt.rgba(1, 1, 1, 0.12)
+ hoveredColor: AmneziaStyle.color.translucentWhite
+ pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Remove from application")
diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml
index 3ab0df8a..9abfc453 100644
--- a/client/ui/qml/Pages2/PageSettingsLogging.qml
+++ b/client/ui/qml/Pages2/PageSettingsLogging.qml
@@ -16,18 +16,6 @@ import "../Controls2/TextTypes"
PageType {
id: root
- Connections {
- target: SettingsController
-
- function onLoggingStateChanged() {
- if (SettingsController.isLoggingEnabled) {
- var message = qsTr("Logging is enabled. Note that logs will be automatically \
-disabled after 14 days, and all log files will be deleted.")
- PageController.showNotificationMessage(message)
- }
- }
- }
-
defaultActiveFocusItem: focusItem
Item {
@@ -58,13 +46,12 @@ disabled after 14 days, and all log files will be deleted.")
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.leftMargin: 16
- anchors.rightMargin: 16
-
- spacing: 16
+ spacing: 0
HeaderType {
Layout.fillWidth: true
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
headerText: qsTr("Logging")
descriptionText: qsTr("Enabling this function will save application's logs automatically. " +
@@ -75,11 +62,13 @@ disabled after 14 days, and all log files will be deleted.")
id: switcher
Layout.fillWidth: true
Layout.topMargin: 16
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
- text: qsTr("Save logs")
+ text: qsTr("Enable logs")
checked: SettingsController.isLoggingEnabled
- KeyNavigation.tab: openFolderButton
+ //KeyNavigation.tab: openFolderButton
onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) {
SettingsController.isLoggingEnabled = checked
@@ -87,132 +76,200 @@ disabled after 14 days, and all log files will be deleted.")
}
}
- RowLayout {
+ DividerType {}
+
+ LabelWithButtonType {
+ // id: labelWithButton2
Layout.fillWidth: true
+ Layout.topMargin: -8
- ColumnLayout {
- Layout.alignment: Qt.AlignBaseline
- Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3
- visible: !GC.isMobile()
+ text: qsTr("Clear logs")
+ leftImageSource: "qrc:/images/controls/trash.svg"
+ isSmallLeftImage: true
- ImageButtonType {
- id: openFolderButton
- Layout.alignment: Qt.AlignHCenter
+ // KeyNavigation.tab: labelWithButton3
- implicitWidth: 56
- implicitHeight: 56
+ clickedFunction: function() {
+ var headerText = qsTr("Clear logs?")
+ var yesButtonText = qsTr("Continue")
+ var noButtonText = qsTr("Cancel")
- image: "qrc:/images/controls/folder-open.svg"
- KeyNavigation.tab: saveButton
-
- onClicked: SettingsController.openLogsFolder()
- Keys.onReturnPressed: openFolderButton.clicked()
- Keys.onEnterPressed: openFolderButton.clicked()
+ var yesButtonFunction = function() {
+ PageController.showBusyIndicator(true)
+ SettingsController.clearLogs()
+ PageController.showBusyIndicator(false)
+ PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
+ if (!GC.isMobile()) {
+ focusItem.forceActiveFocus()
+ }
}
-
- CaptionTextType {
- horizontalAlignment: Text.AlignHCenter
- Layout.fillWidth: true
-
- text: qsTr("Open folder with logs")
- color: AmneziaStyle.color.paleGray
- }
- }
-
- ColumnLayout {
- Layout.alignment: Qt.AlignBaseline
- Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
-
- ImageButtonType {
- id: saveButton
- Layout.alignment: Qt.AlignHCenter
-
- implicitWidth: 56
- implicitHeight: 56
-
- image: "qrc:/images/controls/save.svg"
- KeyNavigation.tab: clearButton
-
- Keys.onReturnPressed: saveButton.clicked()
- Keys.onEnterPressed: saveButton.clicked()
- onClicked: {
- var fileName = ""
- if (GC.isMobile()) {
- fileName = "AmneziaVPN.log"
- } else {
- fileName = SystemController.getFileName(qsTr("Save"),
- qsTr("Logs files (*.log)"),
- StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
- true,
- ".log")
- }
- if (fileName !== "") {
- PageController.showBusyIndicator(true)
- SettingsController.exportLogsFile(fileName)
- PageController.showBusyIndicator(false)
- PageController.showNotificationMessage(qsTr("Logs file saved"))
- }
+ var noButtonFunction = function() {
+ if (!GC.isMobile()) {
+ focusItem.forceActiveFocus()
}
}
- CaptionTextType {
- horizontalAlignment: Text.AlignHCenter
- Layout.fillWidth: true
+ showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
+ }
+ }
- text: qsTr("Save logs to file")
- color: AmneziaStyle.color.paleGray
+ ListItemTitleType {
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+
+ text: qsTr("Client logs")
+ }
+
+ ParagraphTextType {
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+
+ color: AmneziaStyle.color.mutedGray
+ text: qsTr("AmneziaVPN logs")
+ }
+
+ LabelWithButtonType {
+ // id: labelWithButton2
+ Layout.fillWidth: true
+ Layout.topMargin: -8
+ Layout.bottomMargin: -8
+
+ text: qsTr("Open logs folder")
+ leftImageSource: "qrc:/images/controls/folder-open.svg"
+ isSmallLeftImage: true
+
+ // KeyNavigation.tab: labelWithButton3
+
+ clickedFunction: function() {
+ SettingsController.openLogsFolder()
+ }
+ }
+
+ DividerType {}
+
+ LabelWithButtonType {
+ // id: labelWithButton2
+ Layout.fillWidth: true
+ Layout.topMargin: -8
+ Layout.bottomMargin: -8
+
+ text: qsTr("Export logs")
+ leftImageSource: "qrc:/images/controls/save.svg"
+ isSmallLeftImage: true
+
+ // KeyNavigation.tab: labelWithButton3
+
+ clickedFunction: function() {
+ var fileName = ""
+ if (GC.isMobile()) {
+ fileName = "AmneziaVPN.log"
+ } else {
+ fileName = SystemController.getFileName(qsTr("Save"),
+ qsTr("Logs files (*.log)"),
+ StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
+ true,
+ ".log")
+ }
+ if (fileName !== "") {
+ PageController.showBusyIndicator(true)
+ SettingsController.exportLogsFile(fileName)
+ PageController.showBusyIndicator(false)
+ PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
+ }
- ColumnLayout {
- Layout.alignment: Qt.AlignBaseline
- Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
+ DividerType {}
- ImageButtonType {
- id: clearButton
- Layout.alignment: Qt.AlignHCenter
+ ListItemTitleType {
+ visible: !GC.isMobile()
- implicitWidth: 56
- implicitHeight: 56
+ Layout.fillWidth: true
+ Layout.topMargin: 32
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
- image: "qrc:/images/controls/delete.svg"
- Keys.onTabPressed: lastItemTabClicked(focusItem)
+ text: qsTr("Service logs")
+ }
- Keys.onReturnPressed: clearButton.clicked()
- Keys.onEnterPressed: clearButton.clicked()
- onClicked: function() {
- var headerText = qsTr("Clear logs?")
- var yesButtonText = qsTr("Continue")
- var noButtonText = qsTr("Cancel")
+ ParagraphTextType {
+ visible: !GC.isMobile()
- var yesButtonFunction = function() {
- PageController.showBusyIndicator(true)
- SettingsController.clearLogs()
- PageController.showBusyIndicator(false)
- PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
- if (!GC.isMobile()) {
- focusItem.forceActiveFocus()
- }
- }
- var noButtonFunction = function() {
- if (!GC.isMobile()) {
- focusItem.forceActiveFocus()
- }
- }
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
- showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
- }
+ color: AmneziaStyle.color.mutedGray
+ text: qsTr("AmneziaVPN-service logs")
+ }
+
+ LabelWithButtonType {
+ // id: labelWithButton2
+
+ visible: !GC.isMobile()
+
+ Layout.fillWidth: true
+ Layout.topMargin: -8
+ Layout.bottomMargin: -8
+
+ text: qsTr("Open logs folder")
+ leftImageSource: "qrc:/images/controls/folder-open.svg"
+ isSmallLeftImage: true
+
+ // KeyNavigation.tab: labelWithButton3
+
+ clickedFunction: function() {
+ SettingsController.openServiceLogsFolder()
+ }
+ }
+
+ DividerType {
+ visible: !GC.isMobile()
+ }
+
+ LabelWithButtonType {
+ // id: labelWithButton2
+
+ visible: !GC.isMobile()
+
+ Layout.fillWidth: true
+ Layout.topMargin: -8
+ Layout.bottomMargin: -8
+
+ text: qsTr("Export logs")
+ leftImageSource: "qrc:/images/controls/save.svg"
+ isSmallLeftImage: true
+
+ // KeyNavigation.tab: labelWithButton3
+
+ clickedFunction: function() {
+ var fileName = ""
+ if (GC.isMobile()) {
+ fileName = "AmneziaVPN-service.log"
+ } else {
+ fileName = SystemController.getFileName(qsTr("Save"),
+ qsTr("Logs files (*.log)"),
+ StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
+ true,
+ ".log")
}
-
- CaptionTextType {
- horizontalAlignment: Text.AlignHCenter
- Layout.fillWidth: true
-
- text: qsTr("Clear logs")
- color: AmneziaStyle.color.paleGray
+ if (fileName !== "") {
+ PageController.showBusyIndicator(true)
+ SettingsController.exportServiceLogsFile(fileName)
+ PageController.showBusyIndicator(false)
+ PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
}
+
+ DividerType {
+ visible: !GC.isMobile()
+ }
}
}
}
diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml
index 95ae5c8a..ffcfb441 100644
--- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml
+++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml
@@ -25,6 +25,8 @@ PageType {
property int pageSettingsApiServerInfo: 3
property int pageSettingsApiLanguageList: 4
+ property var processedServer
+
defaultActiveFocusItem: focusItem
Connections {
@@ -35,8 +37,18 @@ PageType {
}
}
+ Connections {
+ target: ServersModel
+
+ function onProcessedServerChanged() {
+ root.processedServer = proxyServersModel.get(0)
+ }
+ }
+
SortFilterProxyModel {
id: proxyServersModel
+ objectName: "proxyServersModel"
+
sourceModel: ServersModel
filters: [
ValueFilter {
@@ -44,147 +56,139 @@ PageType {
value: true
}
]
+
+ Component.onCompleted: {
+ root.processedServer = proxyServersModel.get(0)
+ }
}
Item {
id: focusItem
- KeyNavigation.tab: header
+ //KeyNavigation.tab: header
}
ColumnLayout {
anchors.fill: parent
- spacing: 16
+ spacing: 4
- Repeater {
- id: header
- model: proxyServersModel
+ BackButtonType {
+ id: backButton
- activeFocusOnTab: true
- onFocusChanged: {
- header.itemAt(0).focusItem.forceActiveFocus()
+ Layout.topMargin: 20
+ KeyNavigation.tab: headerContent.actionButton
+
+ backButtonFunction: function() {
+ if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
+ root.processedServer.isCountrySelectionAvailable) {
+ nestedStackView.currentIndex = root.pageSettingsApiLanguageList
+ } else {
+ PageController.closePage()
+ }
+ }
+ }
+
+ HeaderType {
+ id: headerContent
+ Layout.fillWidth: true
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+
+ actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg"
+ : "qrc:/images/controls/edit-3.svg"
+
+ headerText: root.processedServer.name
+ descriptionText: {
+ if (root.processedServer.isServerFromGatewayApi) {
+ if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
+ return qsTr("Subscription is valid until ") + ApiServicesModel.getSelectedServiceData("endDate")
+ } else {
+ return ApiServicesModel.getSelectedServiceData("serviceDescription")
+ }
+ } else if (root.processedServer.isServerFromTelegramApi) {
+ return root.processedServer.serverDescription
+ } else if (root.processedServer.hasWriteAccess) {
+ return root.processedServer.credentialsLogin + " · " + root.processedServer.hostName
+ } else {
+ return root.processedServer.hostName
+ }
}
- delegate: ColumnLayout {
+ KeyNavigation.tab: tabBar
- property alias focusItem: backButton
+ actionButtonFunction: function() {
+ if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
+ nestedStackView.currentIndex = root.pageSettingsApiServerInfo
+ } else {
+ serverNameEditDrawer.open()
+ }
+ }
+ }
- id: content
+ DrawerType2 {
+ id: serverNameEditDrawer
- Layout.topMargin: 20
+ parent: root
- BackButtonType {
- id: backButton
- KeyNavigation.tab: headerContent.actionButton
+ anchors.fill: parent
+ expandedHeight: root.height * 0.35
- backButtonFunction: function() {
- if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
- ServersModel.getProcessedServerData("isCountrySelectionAvailable")) {
- nestedStackView.currentIndex = root.pageSettingsApiLanguageList
- } else {
- PageController.closePage()
- }
+ onClosed: {
+ if (!GC.isMobile()) {
+ headerContent.actionButton.forceActiveFocus()
+ }
+ }
+
+ expandedContent: ColumnLayout {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 32
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+
+ Connections {
+ target: serverNameEditDrawer
+ enabled: !GC.isMobile()
+ function onOpened() {
+ serverName.textField.forceActiveFocus()
}
}
- HeaderType {
- id: headerContent
+ Item {
+ id: focusItem1
+ KeyNavigation.tab: serverName.textField
+ }
+
+ TextFieldWithHeaderType {
+ id: serverName
+
Layout.fillWidth: true
- Layout.leftMargin: 16
- Layout.rightMargin: 16
+ headerText: qsTr("Server name")
+ textFieldText: root.processedServer.name
+ textField.maximumLength: 30
+ checkEmptyText: true
- actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" : "qrc:/images/controls/edit-3.svg"
-
- headerText: name
- descriptionText: {
- if (ServersModel.getProcessedServerData("isServerFromGatewayApi")) {
- return ApiServicesModel.getSelectedServiceData("serviceDescription")
- } else if (ServersModel.getProcessedServerData("isServerFromTelegramApi")) {
- return serverDescription
- } else if (ServersModel.isProcessedServerHasWriteAccess()) {
- return credentialsLogin + " · " + hostName
- } else {
- return hostName
- }
- }
-
- KeyNavigation.tab: tabBar
-
- actionButtonFunction: function() {
- if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
- nestedStackView.currentIndex = root.pageSettingsApiServerInfo
- } else {
- serverNameEditDrawer.open()
- }
- }
+ KeyNavigation.tab: saveButton
}
- DrawerType2 {
- id: serverNameEditDrawer
+ BasicButtonType {
+ id: saveButton
- parent: root
+ Layout.fillWidth: true
- anchors.fill: parent
- expandedHeight: root.height * 0.35
+ text: qsTr("Save")
+ KeyNavigation.tab: focusItem1
- onClosed: {
- if (!GC.isMobile()) {
- headerContent.actionButton.forceActiveFocus()
- }
- }
-
- expandedContent: ColumnLayout {
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: 32
- anchors.leftMargin: 16
- anchors.rightMargin: 16
-
- Connections {
- target: serverNameEditDrawer
- enabled: !GC.isMobile()
- function onOpened() {
- serverName.textField.forceActiveFocus()
- }
+ clickedFunc: function() {
+ if (serverName.textFieldText === "") {
+ return
}
- Item {
- id: focusItem1
- KeyNavigation.tab: serverName.textField
- }
-
- TextFieldWithHeaderType {
- id: serverName
-
- Layout.fillWidth: true
- headerText: qsTr("Server name")
- textFieldText: name
- textField.maximumLength: 30
- checkEmptyText: true
-
- KeyNavigation.tab: saveButton
- }
-
- BasicButtonType {
- id: saveButton
-
- Layout.fillWidth: true
-
- text: qsTr("Save")
- KeyNavigation.tab: focusItem1
-
- clickedFunc: function() {
- if (serverName.textFieldText === "") {
- return
- }
-
- if (serverName.textFieldText !== name) {
- name = serverName.textFieldText
- }
- serverNameEditDrawer.close()
- }
+ if (serverName.textFieldText !== root.processedServer.name) {
+ ServersModel.setProcessedServerData("name", serverName.textFieldText);
}
+ serverNameEditDrawer.close()
}
}
}
@@ -257,8 +261,7 @@ PageType {
StackLayout {
id: nestedStackView
- Layout.preferredWidth: root.width
- Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight
+ Layout.fillWidth: true
currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ?
(ServersModel.getProcessedServerData("isCountrySelectionAvailable") ?
diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml
index 6410156d..dcdf01af 100644
--- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml
+++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml
@@ -79,7 +79,7 @@ PageType {
}
delegate: Item {
- property var focusItem: button.rightButton
+ property var focusItem: clientSettings.rightButton
implicitWidth: protocols.width
implicitHeight: delegateContent.implicitHeight
@@ -89,13 +89,49 @@ PageType {
anchors.fill: parent
+ property bool isClientSettingsVisible: protocolIndex === ProtocolEnum.WireGuard || protocolIndex === ProtocolEnum.Awg
+ property bool isServerSettingsVisible: ServersModel.isProcessedServerHasWriteAccess()
+
LabelWithButtonType {
- id: button
+ id: clientSettings
Layout.fillWidth: true
- text: protocolName
+ text: protocolName + qsTr(" connection settings")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
+ visible: delegateContent.isClientSettingsVisible
+
+ clickedFunction: function() {
+ if (isClientProtocolExists) {
+ switch (protocolIndex) {
+ case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break;
+ case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break;
+ }
+ PageController.goToPage(clientProtocolPage);
+ } else {
+ PageController.showNotificationMessage(qsTr("Click the \"connect\" button to create a connection configuration"))
+ }
+ }
+
+ MouseArea {
+ anchors.fill: clientSettings
+ cursorShape: Qt.PointingHandCursor
+ enabled: false
+ }
+ }
+
+ DividerType {
+ visible: delegateContent.isClientSettingsVisible
+ }
+
+ LabelWithButtonType {
+ id: serverSettings
+
+ Layout.fillWidth: true
+
+ text: protocolName + qsTr(" server settings")
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+ visible: delegateContent.isServerSettingsVisible
clickedFunction: function() {
switch (protocolIndex) {
@@ -109,17 +145,19 @@ PageType {
case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break;
case ProtocolEnum.Socks5Proxy: Socks5ProxyConfigModel.updateModel(ProtocolsModel.getConfig()); break;
}
- PageController.goToPage(protocolPage);
+ PageController.goToPage(serverProtocolPage);
}
MouseArea {
- anchors.fill: button
+ anchors.fill: serverSettings
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
- DividerType {}
+ DividerType {
+ visible: delegateContent.isServerSettingsVisible
+ }
}
}
}
@@ -132,11 +170,11 @@ PageType {
visible: root.isClearCacheVisible
KeyNavigation.tab: removeButton
- text: qsTr("Clear %1 profile").arg(ContainersModel.getProcessedContainerName())
+ text: qsTr("Clear profile")
clickedFunction: function() {
var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName())
- var descriptionText = qsTr("")
+ var descriptionText = qsTr("The connection configuration will be deleted for this device only")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
@@ -183,7 +221,7 @@ PageType {
visible: ServersModel.isProcessedServerHasWriteAccess()
Keys.onTabPressed: lastItemTabClicked(focusItem)
- text: qsTr("Remove ") + ContainersModel.getProcessedContainerName()
+ text: qsTr("Remove ")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function() {
diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml
index cb79f19e..f726cd49 100644
--- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml
@@ -16,81 +16,82 @@ PageType {
defaultActiveFocusItem: focusItem
- FlickableType {
- id: fl
+ ColumnLayout {
+ id: header
+
anchors.top: parent.top
- anchors.bottom: parent.bottom
- contentHeight: content.height
+ anchors.left: parent.left
+ anchors.right: parent.right
- ColumnLayout {
- id: content
+ spacing: 0
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
+ Item {
+ id: focusItem
+ KeyNavigation.tab: backButton
+ }
- spacing: 0
-
- Item {
- id: focusItem
- KeyNavigation.tab: backButton
- }
-
- BackButtonType {
- id: backButton
- Layout.topMargin: 20
+ BackButtonType {
+ id: backButton
+ Layout.topMargin: 20
// KeyNavigation.tab: fileButton.rightButton
- }
+ }
- HeaderType {
- Layout.fillWidth: true
- Layout.topMargin: 8
- Layout.rightMargin: 16
- Layout.leftMargin: 16
- Layout.bottomMargin: 32
+ HeaderType {
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+ Layout.rightMargin: 16
+ Layout.leftMargin: 16
+ Layout.bottomMargin: 16
- headerText: qsTr("VPN by Amnezia")
- descriptionText: qsTr("Choose a VPN service that suits your needs.")
- }
+ headerText: qsTr("VPN by Amnezia")
+ descriptionText: qsTr("Choose a VPN service that suits your needs.")
+ }
+ }
- ListView {
- id: containers
- width: parent.width
- height: containers.contentItem.height
- spacing: 16
+ ListView {
+ id: servicesListView
+ anchors.top: header.bottom
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.topMargin: 16
+ spacing: 0
- currentIndex: 1
- interactive: false
- model: ApiServicesModel
+ currentIndex: 1
+ clip: true
+ model: ApiServicesModel
- delegate: Item {
- implicitWidth: containers.width
- implicitHeight: delegateContent.implicitHeight
+ ScrollBar.vertical: ScrollBar {}
- ColumnLayout {
- id: delegateContent
+ delegate: Item {
+ implicitWidth: servicesListView.width
+ implicitHeight: delegateContent.implicitHeight
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
+ ColumnLayout {
+ id: delegateContent
- CardWithIconsType {
- id: card
+ anchors.fill: parent
- Layout.fillWidth: true
- Layout.rightMargin: 16
- Layout.leftMargin: 16
+ CardWithIconsType {
+ id: card
- headerText: name
- bodyText: cardDescription
- footerText: price
+ Layout.fillWidth: true
+ Layout.rightMargin: 16
+ Layout.leftMargin: 16
+ Layout.bottomMargin: 16
- rightImageSource: "qrc:/images/controls/chevron-right.svg"
+ headerText: name
+ bodyText: cardDescription
+ footerText: price
- onClicked: {
- ApiServicesModel.setServiceIndex(index)
- PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
- }
+ rightImageSource: "qrc:/images/controls/chevron-right.svg"
+
+ enabled: isServiceAvailable
+
+ onClicked: {
+ if (isServiceAvailable) {
+ ApiServicesModel.setServiceIndex(index)
+ PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
}
}
}
diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
index 3febca4c..f973c89c 100644
--- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml
@@ -47,14 +47,86 @@ PageType {
KeyNavigation.tab: textKey.textField
}
-
HeaderType {
+ property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
+
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Connection")
+
+ actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
+ actionButtonFunction: function() {
+ moreActionsDrawer.open()
+ }
+
+ DrawerType2 {
+ id: moreActionsDrawer
+
+ parent: root
+
+ anchors.fill: parent
+ expandedHeight: root.height * 0.5
+
+ expandedContent: ColumnLayout {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 0
+
+ HeaderType {
+ Layout.fillWidth: true
+ Layout.topMargin: 32
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+
+ headerText: qsTr("Settings")
+ }
+
+ SwitcherType {
+ id: switcher
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+
+ text: qsTr("Enable logs")
+
+ visible: PageController.isStartPageVisible()
+ checked: SettingsController.isLoggingEnabled
+ onCheckedChanged: {
+ if (checked !== SettingsController.isLoggingEnabled) {
+ SettingsController.isLoggingEnabled = checked
+ }
+ }
+ }
+
+ LabelWithButtonType {
+ id: supportUuid
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+
+ text: qsTr("Support tag")
+ descriptionText: SettingsController.getInstallationUuid()
+
+ descriptionOnTop: true
+
+ rightImageSource: "qrc:/images/controls/copy.svg"
+ rightImageColor: AmneziaStyle.color.paleGray
+
+ visible: SettingsController.getInstallationUuid() !== ""
+ clickedFunction: function() {
+ GC.copyToClipBoard(descriptionText)
+ PageController.showNotificationMessage(qsTr("Copied"))
+ if (!GC.isMobile()) {
+ this.rightButton.forceActiveFocus()
+ }
+ }
+ }
+ }
+ }
}
ParagraphTextType {
@@ -119,8 +191,6 @@ PageType {
CardWithIconsType {
id: apiInstalling
- visible: false
-
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml
index 7f1c3eed..aced12b1 100644
--- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml
@@ -60,9 +60,6 @@ PageType {
Layout.fillWidth: true
headerText: qsTr("Server IP address [:port]")
textFieldPlaceholderText: qsTr("255.255.255.255:22")
- textField.validator: RegularExpressionValidator {
- regularExpression: InstallController.ipAddressPortRegExp()
- }
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
index 3aac1555..92048f36 100644
--- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
+++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml
@@ -37,7 +37,7 @@ PageType {
Connections {
target: ImportController
- function onImportErrorOccurred(errorMessage, goToPageHome) {
+ function onImportErrorOccurred(error, goToPageHome) {
if (goToPageHome) {
PageController.goToStartPage()
} else {
diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml
index 33577d74..d6ce7848 100644
--- a/client/ui/qml/Pages2/PageShare.qml
+++ b/client/ui/qml/Pages2/PageShare.qml
@@ -92,7 +92,7 @@ PageType {
break
}
case PageShare.ConfigType.Xray: {
- ExportController.generateXrayConfig()
+ ExportController.generateXrayConfig(clientNameTextField.textFieldText)
shareConnectionDrawer.configCaption = qsTr("Save XRay config")
shareConnectionDrawer.configExtension = ".json"
shareConnectionDrawer.configFileName = "amnezia_for_xray"
@@ -573,7 +573,7 @@ PageType {
visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share")
- imageSource: "qrc:/images/controls/share-2.svg"
+ leftImageSource: "qrc:/images/controls/share-2.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem)
@@ -772,7 +772,8 @@ PageType {
}
}
- anchors.fill: parent
+ width: root.width
+ height: root.height
expandedContent: ColumnLayout {
id: expandedContent
@@ -783,8 +784,6 @@ PageType {
anchors.leftMargin: 16
anchors.rightMargin: 16
- spacing: 8
-
onImplicitHeightChanged: {
clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32
}
@@ -797,49 +796,54 @@ PageType {
}
}
- Header2Type {
- Layout.fillWidth: true
-
- headerText: clientName
- }
-
- ColumnLayout
- {
- id: textColumn
- property string textColor: AmneziaStyle.color.mutedGray
+ Header2TextType {
+ Layout.maximumWidth: parent.width
Layout.bottomMargin: 24
- ParagraphTextType {
- color: textColumn.textColor
- visible: creationDate
- Layout.fillWidth: true
+ text: clientName
+ maximumLineCount: 2
+ wrapMode: Text.Wrap
+ elide: Qt.ElideRight
+ }
- text: qsTr("Creation date: %1").arg(creationDate)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: creationDate
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: latestHandshake
- Layout.fillWidth: true
+ text: qsTr("Creation date: %1").arg(creationDate)
+ }
- text: qsTr("Latest handshake: %1").arg(latestHandshake)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: latestHandshake
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: dataReceived
- Layout.fillWidth: true
+ text: qsTr("Latest handshake: %1").arg(latestHandshake)
+ }
- text: qsTr("Data received: %1").arg(dataReceived)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: dataReceived
+ Layout.fillWidth: true
- ParagraphTextType {
- color: textColumn.textColor
- visible: dataSent
- Layout.fillWidth: true
+ text: qsTr("Data received: %1").arg(dataReceived)
+ }
- text: qsTr("Data sent: %1").arg(dataSent)
- }
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: dataSent
+ Layout.fillWidth: true
+
+ text: qsTr("Data sent: %1").arg(dataSent)
+ }
+
+ ParagraphTextType {
+ color: AmneziaStyle.color.mutedGray
+ visible: allowedIps
+ Layout.fillWidth: true
+
+ text: qsTr("Allowed IPs: %1").arg(allowedIps)
}
Item {
@@ -944,6 +948,7 @@ PageType {
BasicButtonType {
id: revokeButton
Layout.fillWidth: true
+ Layout.topMargin: 8
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml
index 4807c030..404ba563 100644
--- a/client/ui/qml/Pages2/PageShareFullAccess.qml
+++ b/client/ui/qml/Pages2/PageShareFullAccess.qml
@@ -135,27 +135,28 @@ PageType {
Layout.topMargin: 40
text: qsTr("Share")
- imageSource: "qrc:/images/controls/share-2.svg"
+ leftImageSource: "qrc:/images/controls/share-2.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
+ PageController.showBusyIndicator(true)
+
+ if (Qt.platform.os === "android" && !SystemController.isAuthenticated()) {
+ PageController.showBusyIndicator(false)
+ ExportController.exportErrorOccurred(qsTr("Access error!"))
+ return
+ } else {
+ ExportController.generateFullAccessConfig()
+ }
+
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.open()
- shareConnectionDrawer.contentVisible = false
- PageController.showBusyIndicator(true)
-
- if (Qt.platform.os === "android") {
- ExportController.generateFullAccessConfigAndroid();
- } else {
- ExportController.generateFullAccessConfig();
- }
+ shareConnectionDrawer.contentVisible = true
PageController.showBusyIndicator(false)
-
- shareConnectionDrawer.contentVisible = true
}
}
}
diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml
index 770347ca..640c61ef 100644
--- a/client/ui/qml/Pages2/PageStart.qml
+++ b/client/ui/qml/Pages2/PageStart.qml
@@ -123,6 +123,10 @@ PageType {
}
}
+ function onWrongInstallationUser(message) {
+ onInstallationErrorOccurred(message)
+ }
+
function onUpdateContainerFinished(message) {
PageController.showNotificationMessage(message)
PageController.closePage()
@@ -202,6 +206,14 @@ PageType {
PageController.showNotificationMessage(qsTr("Settings restored from backup file"))
PageController.goToPageHome()
}
+
+ function onLoggingStateChanged() {
+ if (SettingsController.isLoggingEnabled) {
+ var message = qsTr("Logging is enabled. Note that logs will be automatically" +
+ "disabled after 14 days, and all log files will be deleted.")
+ PageController.showNotificationMessage(message)
+ }
+ }
}
StackViewType {
diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml
index a5a47e2c..fb99559f 100644
--- a/client/ui/qml/main2.qml
+++ b/client/ui/qml/main2.qml
@@ -80,7 +80,8 @@ Window {
}
PageStart {
- anchors.fill: parent
+ width: root.width
+ height: root.height
}
Item {
diff --git a/client/utilities.cpp b/client/utilities.cpp
old mode 100644
new mode 100755
index a2f3d021..1cc69aeb
--- a/client/utilities.cpp
+++ b/client/utilities.cpp
@@ -10,18 +10,72 @@
#include
#include "utilities.h"
-#include "version.h"
+
+#ifdef Q_OS_WINDOWS
+QString printErrorMessage(DWORD errorCode) {
+ LPVOID lpMsgBuf;
+
+ DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
+
+ FormatMessageW(
+ dwFlags,
+ NULL,
+ errorCode,
+ dwLanguageId,
+ (LPWSTR)&lpMsgBuf,
+ 0,
+ NULL
+ );
+
+ QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ return errorMsg.trimmed();
+}
+
+QString Utils::getNextDriverLetter()
+{
+ DWORD drivesBitmask = GetLogicalDrives();
+ if (drivesBitmask == 0) {
+ DWORD error = GetLastError();
+ qDebug() << "GetLogicalDrives failed. Error code:" << error;
+ return "";
+ }
+
+ QString letters = "FGHIJKLMNOPQRSTUVWXYZ";
+ QString availableLetter;
+
+ for (int i = letters.size() - 1; i >= 0; --i) {
+ QChar letterChar = letters.at(i);
+ int driveIndex = letterChar.toLatin1() - 'A';
+
+ if ((drivesBitmask & (1 << driveIndex)) == 0) {
+ availableLetter = letterChar;
+ break;
+ }
+ }
+
+ if (availableLetter.isEmpty()) {
+ qDebug() << "Can't find free drive letter";
+ return "";
+ }
+
+ return availableLetter;
+}
+#endif
QString Utils::getRandomString(int len)
{
- const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
-
+ const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString;
+
for (int i = 0; i < len; ++i) {
- quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
- QChar nextChar = possibleCharacters.at(index);
- randomString.append(nextChar);
+ randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
}
+
return randomString;
}
@@ -69,22 +123,6 @@ QString Utils::JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat f
return doc.toJson(format);
}
-QString Utils::systemLogPath()
-{
-#ifdef Q_OS_WIN
- QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
- QString primaryLocation = "ProgramData";
- foreach (const QString &location, locationList) {
- if (location.contains(primaryLocation)) {
- return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME);
- }
- }
- return QString();
-#else
- return QString("/var/log/%1").arg(APPLICATION_NAME);
-#endif
-}
-
bool Utils::initializePath(const QString &path)
{
QDir dir;
@@ -125,30 +163,34 @@ QString Utils::usrExecutable(const QString &baseName)
bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
{
#ifdef Q_OS_WIN
- QProcess process;
- process.setReadChannel(QProcess::StandardOutput);
- process.setProcessChannelMode(QProcess::MergedChannels);
- process.start("wmic.exe",
- QStringList() << "/OUTPUT:STDOUT"
- << "PROCESS"
- << "get"
- << "Caption");
- process.waitForStarted();
- process.waitForFinished();
- QString processData(process.readAll());
- QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
- foreach (const QString &rawLine, processList) {
- const QString line = rawLine.simplified();
- if (line.isEmpty()) {
- continue;
- }
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE) {
+ qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot";
+ return false;
+ }
- if (line == fileName) {
+ PROCESSENTRY32W pe32;
+ pe32.dwSize = sizeof(PROCESSENTRY32W);
+
+ if (!Process32FirstW(hSnapshot, &pe32)) {
+ CloseHandle(hSnapshot);
+ qWarning() << "Utils::processIsRunning error Process32FirstW";
+ return false;
+ }
+
+ do {
+ QString exeFile = QString::fromWCharArray(pe32.szExeFile);
+
+ if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) {
+ CloseHandle(hSnapshot);
return true;
}
- }
+ } while (Process32NextW(hSnapshot, &pe32));
+
+ CloseHandle(hSnapshot);
return false;
-#elif defined(Q_OS_IOS)
+
+#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
return false;
#else
QProcess process;
@@ -166,13 +208,45 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
#endif
}
-void Utils::killProcessByName(const QString &name)
+bool Utils::killProcessByName(const QString &name)
{
qDebug().noquote() << "Kill process" << name;
#ifdef Q_OS_WIN
- QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F");
-#elif defined Q_OS_IOS
- return;
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ return false;
+
+ PROCESSENTRY32W pe32;
+ pe32.dwSize = sizeof(PROCESSENTRY32W);
+
+ bool success = false;
+
+ if (Process32FirstW(hSnapshot, &pe32)) {
+ do {
+ QString exeFile = QString::fromWCharArray(pe32.szExeFile);
+
+ if (exeFile.compare(name, Qt::CaseInsensitive) == 0) {
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
+ if (hProcess != NULL) {
+ if (TerminateProcess(hProcess, 0)) {
+ success = true;
+ } else {
+ DWORD error = GetLastError();
+ qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
+ }
+ CloseHandle(hProcess);
+ } else {
+ DWORD error = GetLastError();
+ qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
+ }
+ }
+ } while (Process32NextW(hSnapshot, &pe32));
+ }
+
+ CloseHandle(hSnapshot);
+ return success;
+#elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
+ return false;
#else
QProcess::execute(QString("pkill %1").arg(name));
#endif
@@ -260,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
}
#endif
+
+void Utils::logException(const std::exception &e)
+{
+ qCritical() << e.what();
+ try {
+ std::rethrow_if_nested(e);
+ } catch (const std::exception &nested) {
+ logException(nested);
+ } catch (...) {}
+}
+
+void Utils::logException(const std::exception_ptr &eptr)
+{
+ try {
+ if (eptr) std::rethrow_exception(eptr);
+ } catch (const std::exception &e) {
+ logException(e);
+ } catch (...) {}
+}
diff --git a/client/utilities.h b/client/utilities.h
old mode 100644
new mode 100755
index b85c5b3b..4a1985b1
--- a/client/utilities.h
+++ b/client/utilities.h
@@ -7,7 +7,8 @@
#include
#ifdef Q_OS_WIN
- #include "Windows.h"
+#include
+#include
#endif
class Utils : public QObject
@@ -23,20 +24,23 @@ public:
static QJsonObject JsonFromString(const QString &string);
static QString executable(const QString &baseName, bool absPath);
static QString usrExecutable(const QString &baseName);
- static QString systemLogPath();
static bool createEmptyFile(const QString &path);
static bool initializePath(const QString &path);
static bool processIsRunning(const QString &fileName, const bool fullFlag = false);
- static void killProcessByName(const QString &name);
+ static bool killProcessByName(const QString &name);
static QString openVpnExecPath();
static QString wireguardExecPath();
static QString certUtilPath();
static QString tun2socksPath();
+ static void logException(const std::exception &e);
+ static void logException(const std::exception_ptr &eptr = std::current_exception());
+
#ifdef Q_OS_WIN
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
+ static QString getNextDriverLetter();
#endif
};
diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp
index daff1187..ac881bd7 100644
--- a/client/vpnconnection.cpp
+++ b/client/vpnconnection.cpp
@@ -1,16 +1,16 @@
#include "qtimer.h"
#include
+#include
#include
#include
#include
-#include
+#include "core/controllers/serverController.h"
#include