mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d798344d2 | |||
| dec1f51fd6 | |||
| 832fea3d50 | |||
| 43b51db132 | |||
| d391abb0cc | |||
| 153e33d5df | |||
| 1cb171698a | |||
| f02128796a | |||
| 71705f435b | |||
| f821bcb01f | |||
| 9a5e437f7b | |||
| 3d2105a439 | |||
| 16d92ddb7c |
@@ -154,6 +154,9 @@ void CoreController::initControllers()
|
|||||||
|
|
||||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
|
|
||||||
|
m_transferController.reset(new TransferController(m_settings, m_serversModel, m_exportController.get()));
|
||||||
|
m_engine->rootContext()->setContextProperty("TransferController", m_transferController.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "ui/controllers/settingsController.h"
|
#include "ui/controllers/settingsController.h"
|
||||||
#include "ui/controllers/sitesController.h"
|
#include "ui/controllers/sitesController.h"
|
||||||
#include "ui/controllers/systemController.h"
|
#include "ui/controllers/systemController.h"
|
||||||
|
#include "ui/controllers/transferController.h"
|
||||||
|
|
||||||
#include "ui/models/allowed_dns_model.h"
|
#include "ui/models/allowed_dns_model.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
@@ -145,6 +146,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||||
|
QScopedPointer<TransferController> m_transferController;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CORECONTROLLER_H
|
#endif // CORECONTROLLER_H
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
#include "QRsa.h"
|
#include "QRsa.h"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||||
|
<!-- Monitor -->
|
||||||
|
<rect x="2.5" y="3.5" width="14" height="10" rx="2" ry="2" stroke="#D7D8DB" stroke-width="1.8"/>
|
||||||
|
<line x1="9.5" y1="13.5" x2="9.5" y2="16.5" stroke="#D7D8DB" stroke-width="1.8" stroke-linecap="round"/>
|
||||||
|
<rect x="6" y="16.5" width="7" height="1.8" rx="0.9" ry="0.9" fill="#D7D8DB"/>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<rect x="17.5" y="6" width="4.5" height="9.5" rx="1.2" ry="1.2" stroke="#D7D8DB" stroke-width="1.8"/>
|
||||||
|
<circle cx="19.75" cy="14.2" r="0.6" fill="#D7D8DB"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 628 B |
@@ -229,6 +229,7 @@
|
|||||||
<file>ui/qml/Pages2/PageShare.qml</file>
|
<file>ui/qml/Pages2/PageShare.qml</file>
|
||||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||||
<file>ui/qml/Pages2/PageShareConnection.qml</file>
|
<file>ui/qml/Pages2/PageShareConnection.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageTransferConfigViaQR.qml</file>
|
||||||
<file>ui/qml/Pages2/PageStart.qml</file>
|
<file>ui/qml/Pages2/PageStart.qml</file>
|
||||||
<file>ui/qml/Components/RenameServerDrawer.qml</file>
|
<file>ui/qml/Components/RenameServerDrawer.qml</file>
|
||||||
<file>ui/qml/Controls2/ListViewType.qml</file>
|
<file>ui/qml/Controls2/ListViewType.qml</file>
|
||||||
@@ -236,7 +237,11 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsApiDevicesLimit.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsApiAddDeviceScan.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsApiAddDeviceConfirm.qml</file>
|
||||||
<file>images/controls/monitor.svg</file>
|
<file>images/controls/monitor.svg</file>
|
||||||
|
<file>images/controls/monitor-with-phone.svg</file>
|
||||||
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
|
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
|
||||||
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
|
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
|
||||||
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||||
|
|||||||
@@ -584,7 +584,26 @@ bool ImportController::decodeQrCode(const QString &code)
|
|||||||
mInstance->m_totalQrCodeChunksCount = 0;
|
mInstance->m_totalQrCodeChunksCount = 0;
|
||||||
mInstance->m_receivedQrCodeChunksCount = 0;
|
mInstance->m_receivedQrCodeChunksCount = 0;
|
||||||
}
|
}
|
||||||
return mInstance->parseQrCodeChunk(code);
|
|
||||||
|
// First, try transferController QR (plain JSON payload).
|
||||||
|
QJsonParseError err;
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
|
||||||
|
if (err.error == QJsonParseError::NoError && doc.isObject()) {
|
||||||
|
const QJsonObject obj = doc.object();
|
||||||
|
if (obj.contains(QStringLiteral("gw"))
|
||||||
|
&& (obj.contains(QStringLiteral("uuid")) || obj.contains(QStringLiteral("u")))) {
|
||||||
|
// это наш QR для передачи устройства
|
||||||
|
mInstance->m_isQrCodeProcessed = false;
|
||||||
|
emit mInstance->transferQrDecoded(code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mInstance->parseQrCodeChunk(code)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ signals:
|
|||||||
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||||
|
|
||||||
void qrDecodingFinished();
|
void qrDecodingFinished();
|
||||||
|
void transferQrDecoded(const QString &code);
|
||||||
|
|
||||||
void restoreAppConfig(const QByteArray &data);
|
void restoreAppConfig(const QByteArray &data);
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ namespace PageLoader
|
|||||||
PageSettingsApiInstructions,
|
PageSettingsApiInstructions,
|
||||||
PageSettingsApiNativeConfigs,
|
PageSettingsApiNativeConfigs,
|
||||||
PageSettingsApiDevices,
|
PageSettingsApiDevices,
|
||||||
|
PageSettingsApiDevicesLimit,
|
||||||
|
PageSettingsApiAddDeviceConfirm,
|
||||||
|
PageSettingsApiAddDeviceScan,
|
||||||
PageSettingsApiSubscriptionKey,
|
PageSettingsApiSubscriptionKey,
|
||||||
PageSettingsKillSwitchExceptions,
|
PageSettingsKillSwitchExceptions,
|
||||||
|
|
||||||
@@ -73,6 +76,7 @@ namespace PageLoader
|
|||||||
|
|
||||||
PageShareFullAccess,
|
PageShareFullAccess,
|
||||||
PageShareConnection,
|
PageShareConnection,
|
||||||
|
PageTransferConfigViaQR,
|
||||||
|
|
||||||
PageDevMenu
|
PageDevMenu
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,344 @@
|
|||||||
|
#include "transferController.h"
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QJsonParseError>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <qeventloop.h>
|
||||||
|
#include <QNetworkProxyFactory>
|
||||||
|
#include <QNetworkProxyQuery>
|
||||||
|
#include <QUrl>
|
||||||
|
#include "core/api/apiUtils.h"
|
||||||
|
#include "core/qrCodeUtils.h"
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "ui/models/servers_model.h"
|
||||||
|
#include "ui/controllers/exportController.h"
|
||||||
|
#include "ui/controllers/importController.h"
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "core/controllers/gatewayController.h"
|
||||||
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void logSystemProxiesForUrl(const QString &urlStr)
|
||||||
|
{
|
||||||
|
const QUrl url(urlStr);
|
||||||
|
const QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(QNetworkProxyQuery(url));
|
||||||
|
QStringList proxyDesc;
|
||||||
|
proxyDesc.reserve(proxies.size());
|
||||||
|
for (const auto &p : proxies) {
|
||||||
|
proxyDesc << QStringLiteral("%1 %2:%3")
|
||||||
|
.arg(p.type() == QNetworkProxy::NoProxy ? QStringLiteral("NoProxy")
|
||||||
|
: p.type() == QNetworkProxy::HttpProxy ? QStringLiteral("HttpProxy")
|
||||||
|
: p.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral("Socks5Proxy")
|
||||||
|
: QStringLiteral("Proxy"))
|
||||||
|
.arg(p.hostName())
|
||||||
|
.arg(p.port());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferController::TransferController(const std::shared_ptr<Settings> &settings,
|
||||||
|
const QSharedPointer<ServersModel> &serversModel,
|
||||||
|
ExportController *exportController,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent), m_settings(settings), m_serversModel(serversModel), m_exportController(exportController)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferController::handleImportControllerDestroyed()
|
||||||
|
{
|
||||||
|
m_importController = nullptr;
|
||||||
|
stopWaitForConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferController::~TransferController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["gw"] = gatewayUrl;
|
||||||
|
obj["uuid"] = uuid;
|
||||||
|
// Used on the sender side for human-friendly notifications (same style as "Active Devices" list).
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
obj["name"] = QStringLiteral("Android");
|
||||||
|
#elif defined(Q_OS_IOS)
|
||||||
|
obj["name"] = QStringLiteral("iOS");
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
obj["name"] = QStringLiteral("Windows");
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
obj["name"] = QStringLiteral("macOS");
|
||||||
|
#elif defined(Q_OS_LINUX)
|
||||||
|
obj["name"] = QStringLiteral("Linux");
|
||||||
|
#else
|
||||||
|
obj["name"] = QStringLiteral("Device");
|
||||||
|
#endif
|
||||||
|
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferController::generateNewQrCode()
|
||||||
|
{
|
||||||
|
QString gw = m_settings->getGatewayEndpoint();
|
||||||
|
if (!gw.endsWith('/')) {
|
||||||
|
gw.append('/');
|
||||||
|
}
|
||||||
|
m_currentUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
|
|
||||||
|
m_currentPayload = buildQrPayloadJson(gw, m_currentUuid);
|
||||||
|
|
||||||
|
auto qr = qrCodeUtils::generateQrCode(m_currentPayload.toUtf8());
|
||||||
|
const QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||||
|
m_qrCodeUrl = qrCodeUtils::svgToBase64(svg);
|
||||||
|
emit qrCodeUpdated();
|
||||||
|
emit currentUuidChanged();
|
||||||
|
emit currentPayloadChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferController::stopScanner()
|
||||||
|
{
|
||||||
|
emit scannerShouldStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
|
||||||
|
{
|
||||||
|
const int idx = m_serversModel ? m_serversModel->getProcessedServerIndex() : -1;
|
||||||
|
if (idx < 0 || !m_serversModel) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject server = m_serversModel->getServerConfig(idx);
|
||||||
|
|
||||||
|
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
|
||||||
|
|
||||||
|
const QString apiKey = authData.value(QStringLiteral("api_key")).toString();
|
||||||
|
|
||||||
|
if (vpnKeyOut) {
|
||||||
|
QString vpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
|
||||||
|
if (vpnKey.isEmpty()) {
|
||||||
|
vpnKey = apiUtils::getPremiumV1VpnKey(server);
|
||||||
|
}
|
||||||
|
*vpnKeyOut = vpnKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferController::onTransferQrScanned(const QString &code)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
|
||||||
|
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||||
|
qWarning() << "TransferController::onTransferQrScanned: invalid QR JSON " << err.errorString();
|
||||||
|
emit postFailed(QStringLiteral("Invalid QR JSON"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject obj = doc.object();
|
||||||
|
QString gw = obj.value("gw").toString();
|
||||||
|
const QString uuid = obj.value("uuid").toString();
|
||||||
|
|
||||||
|
if (gw.isEmpty() || uuid.isEmpty()) {
|
||||||
|
qWarning() << "TransferController::onTransferQrScanned: QR missing gw or uuid";
|
||||||
|
emit postFailed(QStringLiteral("QR missing gw or uuid"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!gw.endsWith('/')) {
|
||||||
|
gw.append('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
int chosenServerIdx = -1;
|
||||||
|
QString apiKey;
|
||||||
|
QString vpnKey;
|
||||||
|
|
||||||
|
auto tryServerIndex = [&](int idx) -> bool {
|
||||||
|
if (!m_serversModel || idx < 0 || idx >= m_serversModel->getServersCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject server = m_serversModel->getServerConfig(idx);
|
||||||
|
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
|
||||||
|
|
||||||
|
const QString candidateApiKey = authData.value(QStringLiteral("api_key")).toString();
|
||||||
|
QString candidateVpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
|
||||||
|
if (candidateVpnKey.isEmpty()) {
|
||||||
|
// Fallback for older Premium V1 configs where vpn_key may be derived.
|
||||||
|
candidateVpnKey = apiUtils::getPremiumV1VpnKey(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool candidateIsPremium = apiUtils::isPremiumServer(server);
|
||||||
|
const bool candidateIsFromGatewayApi = m_serversModel->data(idx, ServersModel::IsServerFromGatewayApiRole).toBool();
|
||||||
|
|
||||||
|
if (candidateApiKey.isEmpty() || candidateVpnKey.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!candidateIsPremium && !candidateIsFromGatewayApi) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
chosenServerIdx = idx;
|
||||||
|
apiKey = candidateApiKey;
|
||||||
|
vpnKey = candidateVpnKey;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (m_serversModel) {
|
||||||
|
tryServerIndex(m_serversModel->getProcessedServerIndex());
|
||||||
|
if (chosenServerIdx < 0) {
|
||||||
|
tryServerIndex(m_serversModel->getDefaultServerIndex());
|
||||||
|
}
|
||||||
|
if (chosenServerIdx < 0) {
|
||||||
|
const int n = m_serversModel->getServersCount();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (tryServerIndex(i)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chosenServerIdx < 0) {
|
||||||
|
qWarning() << "TransferController::onTransferQrScanned: no suitable subscription key/config found to send";
|
||||||
|
emit postFailed(QStringLiteral("No subscription key or config to send"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit postStarted();
|
||||||
|
|
||||||
|
const int sendTimeoutMs = 60000;
|
||||||
|
GatewayController gatewayController(gw,
|
||||||
|
m_settings->isDevGatewayEnv(),
|
||||||
|
sendTimeoutMs,
|
||||||
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
|
|
||||||
|
QJsonObject payload;
|
||||||
|
payload.insert(QStringLiteral("uuid"), uuid);
|
||||||
|
payload.insert(QStringLiteral("api_key"), apiKey);
|
||||||
|
payload.insert(QStringLiteral("config"), vpnKey);
|
||||||
|
|
||||||
|
const QString endpoint = QStringLiteral("%1v1/sendConfig");
|
||||||
|
QByteArray responseBody;
|
||||||
|
const QString fullUrl = endpoint.arg(gw);
|
||||||
|
qDebug() << "TransferController::onTransferQrScanned: POST" << fullUrl
|
||||||
|
<< "uuid:" << uuid;
|
||||||
|
logSystemProxiesForUrl(fullUrl);
|
||||||
|
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
|
||||||
|
qDebug() << "TransferController::onTransferQrScanned: sendConfig finished with code"
|
||||||
|
<< static_cast<int>(errorCode)
|
||||||
|
<< "response size:" << responseBody.size();
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
qWarning() << "TransferController::onTransferQrScanned: sendConfig failed with code" << static_cast<int>(errorCode);
|
||||||
|
emit postFailed(QStringLiteral("sendConfig failed: %1").arg(errorString(errorCode)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError parseErr;
|
||||||
|
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
|
||||||
|
if (parseErr.error == QJsonParseError::NoError && respDoc.isObject()
|
||||||
|
&& respDoc.object().value(QStringLiteral("status")).toString() == QStringLiteral("success")) {
|
||||||
|
emit postSucceeded();
|
||||||
|
stopScanner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "TransferController::onTransferQrScanned: unexpected gateway response:" << responseBody;
|
||||||
|
emit postFailed(QStringLiteral("Gateway response error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TransferController::qrCodeUrl() const
|
||||||
|
{
|
||||||
|
return m_qrCodeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransferController::startWaitForConfig(ImportController *importController)
|
||||||
|
{
|
||||||
|
QString gw = m_settings->getGatewayEndpoint();
|
||||||
|
if (!gw.endsWith(QLatin1Char('/'))) {
|
||||||
|
gw.append(QLatin1Char('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString uuid = m_currentUuid;
|
||||||
|
|
||||||
|
if (uuid.isEmpty()) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: no uuid";
|
||||||
|
emit waitError(QStringLiteral("No UUID"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_importController = importController;
|
||||||
|
if (m_importController) {
|
||||||
|
connect(m_importController, &ImportController::destroyed,
|
||||||
|
this,
|
||||||
|
&TransferController::handleImportControllerDestroyed,
|
||||||
|
Qt::UniqueConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int waitTimeoutMs = 60000;
|
||||||
|
|
||||||
|
QJsonObject payload;
|
||||||
|
payload.insert(QStringLiteral("uuid"), uuid);
|
||||||
|
|
||||||
|
GatewayController gatewayController(gw,
|
||||||
|
m_settings->isDevGatewayEnv(),
|
||||||
|
waitTimeoutMs,
|
||||||
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
|
|
||||||
|
const QString endpoint = QStringLiteral("%1v1/waitConfig");
|
||||||
|
QByteArray responseBody;
|
||||||
|
const QString fullUrl = endpoint.arg(gw);
|
||||||
|
logSystemProxiesForUrl(fullUrl);
|
||||||
|
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: waitConfig failed with code" << static_cast<int>(errorCode);
|
||||||
|
emit waitError(QStringLiteral("waitConfig failed (%1)").arg(static_cast<int>(errorCode)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_importController) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: import controller is null";
|
||||||
|
emit waitError(QStringLiteral("Import Controller destroyed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError parseErr;
|
||||||
|
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
|
||||||
|
if (parseErr.error != QJsonParseError::NoError || !respDoc.isObject()) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: invalid JSON response:" << responseBody;
|
||||||
|
emit waitError(QStringLiteral("Invalid gateway response"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QJsonObject respObj = respDoc.object();
|
||||||
|
const QString status = respObj.value(QStringLiteral("status")).toString();
|
||||||
|
const QString configStr = respObj.value(QStringLiteral("config")).toString();
|
||||||
|
if (status != QStringLiteral("success")) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: gateway status not success:" << status;
|
||||||
|
emit waitError(QStringLiteral("Gateway error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (configStr.isEmpty()) {
|
||||||
|
emit waitError(QStringLiteral("Empty config"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (configStr == QStringLiteral("timeout")) {
|
||||||
|
emit waitError(QStringLiteral("Timeout"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_importController->extractConfigFromData(configStr)) {
|
||||||
|
qWarning() << "TransferController::startWaitForConfig: failed to parse config string";
|
||||||
|
emit waitError(QStringLiteral("Invalid config payload"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_importController->importConfig();
|
||||||
|
emit configApplied();
|
||||||
|
}
|
||||||
|
void TransferController::stopWaitForConfig()
|
||||||
|
{
|
||||||
|
qDebug() << "TransferController::stopWaitForConfig: stop flag set";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#ifndef TRANSFERCONTROLLER_H
|
||||||
|
#define TRANSFERCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
class Settings;
|
||||||
|
class ServersModel;
|
||||||
|
class ExportController;
|
||||||
|
class ImportController;
|
||||||
|
|
||||||
|
class TransferController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QString qrCodeUrl READ qrCodeUrl NOTIFY qrCodeUpdated)
|
||||||
|
Q_PROPERTY(QString pendingQrCode READ pendingQrCode WRITE setPendingQrCode NOTIFY pendingQrCodeChanged)
|
||||||
|
Q_PROPERTY(QString currentUuid READ currentUuid NOTIFY currentUuidChanged)
|
||||||
|
Q_PROPERTY(QString currentPayload READ currentPayload NOTIFY currentPayloadChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TransferController(const std::shared_ptr<Settings> &settings,
|
||||||
|
const QSharedPointer<ServersModel> &serversModel,
|
||||||
|
ExportController *exportController,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~TransferController() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void generateNewQrCode();
|
||||||
|
|
||||||
|
Q_INVOKABLE void stopScanner();
|
||||||
|
Q_INVOKABLE void onTransferQrScanned(const QString &code);
|
||||||
|
|
||||||
|
Q_INVOKABLE void setPendingQrCode(const QString &code) { m_pendingQrCode = code; emit pendingQrCodeChanged(); }
|
||||||
|
QString pendingQrCode() const { return m_pendingQrCode; }
|
||||||
|
|
||||||
|
Q_INVOKABLE void startWaitForConfig(ImportController *importController);
|
||||||
|
Q_INVOKABLE void stopWaitForConfig();
|
||||||
|
|
||||||
|
QString qrCodeUrl() const;
|
||||||
|
QString currentUuid() const { return m_currentUuid; }
|
||||||
|
QString currentPayload() const { return m_currentPayload; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void qrCodeUpdated();
|
||||||
|
void scannerShouldStop();
|
||||||
|
void pendingQrCodeChanged();
|
||||||
|
void currentUuidChanged();
|
||||||
|
void currentPayloadChanged();
|
||||||
|
|
||||||
|
void waitError(const QString &message);
|
||||||
|
void configApplied();
|
||||||
|
|
||||||
|
void postStarted();
|
||||||
|
void postSucceeded();
|
||||||
|
void postFailed(const QString &message);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleImportControllerDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const;
|
||||||
|
QString m_pendingQrCode;
|
||||||
|
QString getCurrentApiKey(QString *vpnKeyOut = nullptr) const;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
ExportController *m_exportController { nullptr };
|
||||||
|
ImportController *m_importController { nullptr };
|
||||||
|
|
||||||
|
QString m_qrCodeUrl;
|
||||||
|
QString m_currentUuid;
|
||||||
|
QString m_currentPayload;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TRANSFERCONTROLLER_H
|
||||||
@@ -34,7 +34,7 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButtonLayout.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: saveButton.top
|
anchors.bottom: saveButton.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ PageType {
|
|||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.top: backButtonLayout.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|||||||
@@ -17,428 +17,414 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
ColumnLayout {
|
BackButtonType {
|
||||||
id: backButtonLayout
|
id: backButton
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
|
|
||||||
BackButtonType {
|
onActiveFocusChanged: {
|
||||||
id: backButton
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
listView.positionViewAtBeginning()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FlickableType {
|
ListViewType {
|
||||||
id: fl
|
id: listView
|
||||||
anchors.top: backButtonLayout.bottom
|
|
||||||
|
anchors.top: backButton.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
contentHeight: content.implicitHeight
|
anchors.right: parent.right
|
||||||
|
anchors.left: parent.left
|
||||||
|
|
||||||
Column {
|
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
id: content
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
header: ColumnLayout {
|
||||||
anchors.left: parent.left
|
width: listView.width
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
BaseHeaderType {
|
||||||
|
id: header
|
||||||
|
|
||||||
ListView {
|
Layout.fillWidth: true
|
||||||
id: listview
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
width: parent.width
|
headerText: qsTr("OpenVPN Settings")
|
||||||
height: listview.contentItem.height
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clip: true
|
model: OpenVpnConfigModel
|
||||||
interactive: false
|
|
||||||
|
|
||||||
model: OpenVpnConfigModel
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
delegate: Item {
|
spacing: 0
|
||||||
id: delegateItem
|
|
||||||
|
|
||||||
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
|
TextFieldWithHeaderType {
|
||||||
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
id: vpnAddressSubnetTextField
|
||||||
|
|
||||||
implicitWidth: listview.width
|
Layout.fillWidth: true
|
||||||
implicitHeight: col.implicitHeight
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
ColumnLayout {
|
enabled: listView.enabled
|
||||||
id: col
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
headerText: qsTr("VPN address subnet")
|
||||||
anchors.left: parent.left
|
textField.text: subnetAddress
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
anchors.leftMargin: 16
|
textField.onEditingFinished: {
|
||||||
anchors.rightMargin: 16
|
if (textField.text !== subnetAddress) {
|
||||||
|
subnetAddress = textField.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
spacing: 0
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
BaseHeaderType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("OpenVPN settings")
|
Layout.topMargin: 32
|
||||||
}
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
text: qsTr("Network protocol")
|
||||||
id: vpnAddressSubnetTextField
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
TransportProtoSelector {
|
||||||
Layout.topMargin: 32
|
id: transportProtoSelector
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
rootWidth: root.width
|
||||||
|
|
||||||
headerText: qsTr("VPN address subnet")
|
enabled: isTransportProtoEditable
|
||||||
textField.text: subnetAddress
|
|
||||||
|
|
||||||
parentFlickable: fl
|
currentIndex: {
|
||||||
|
return transportProto === "tcp" ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
onCurrentIndexChanged: {
|
||||||
if (textField.text !== subnetAddress) {
|
if (transportProto === "tcp" && currentIndex === 0) {
|
||||||
subnetAddress = textField.text
|
transportProto = "udp"
|
||||||
}
|
} else if (transportProto === "udp" && currentIndex === 1) {
|
||||||
|
transportProto = "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: portTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 40
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: listView.enabled
|
||||||
|
|
||||||
|
headerText: qsTr("Port")
|
||||||
|
textField.text: port
|
||||||
|
textField.maximumLength: 5
|
||||||
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
if (textField.text !== port) {
|
||||||
|
port = textField.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEmptyText: true
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: autoNegotiateEncryprionSwitcher
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Auto-negotiate encryption")
|
||||||
|
checked: autoNegotiateEncryprion
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== autoNegotiateEncryprion) {
|
||||||
|
autoNegotiateEncryprion = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DropDownType {
|
||||||
|
id: hashDropDown
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||||
|
|
||||||
|
descriptionText: qsTr("Hash")
|
||||||
|
headerText: qsTr("Hash")
|
||||||
|
|
||||||
|
drawerParent: root
|
||||||
|
|
||||||
|
listView: ListViewWithRadioButtonType {
|
||||||
|
id: hashListView
|
||||||
|
|
||||||
|
rootWidth: root.width
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { name : qsTr("SHA512") }
|
||||||
|
ListElement { name : qsTr("SHA384") }
|
||||||
|
ListElement { name : qsTr("SHA256") }
|
||||||
|
ListElement { name : qsTr("SHA3-512") }
|
||||||
|
ListElement { name : qsTr("SHA3-384") }
|
||||||
|
ListElement { name : qsTr("SHA3-256") }
|
||||||
|
ListElement { name : qsTr("whirlpool") }
|
||||||
|
ListElement { name : qsTr("BLAKE2b512") }
|
||||||
|
ListElement { name : qsTr("BLAKE2s256") }
|
||||||
|
ListElement { name : qsTr("SHA1") }
|
||||||
|
}
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
hashDropDown.text = selectedText
|
||||||
|
hash = hashDropDown.text
|
||||||
|
hashDropDown.closeTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
hashDropDown.text = hash
|
||||||
|
|
||||||
|
for (var i = 0; i < hashListView.model.count; i++) {
|
||||||
|
if (hashListView.model.get(i).name === hashDropDown.text) {
|
||||||
|
currentIndex = i
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEmptyText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ParagraphTextType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
|
|
||||||
text: qsTr("Network protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportProtoSelector {
|
|
||||||
id: transportProtoSelector
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
enabled: isTransportProtoEditable
|
|
||||||
|
|
||||||
currentIndex: {
|
|
||||||
return transportProto === "tcp" ? 1 : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
if (transportProto === "tcp" && currentIndex === 0) {
|
|
||||||
transportProto = "udp"
|
|
||||||
} else if (transportProto === "udp" && currentIndex === 1) {
|
|
||||||
transportProto = "tcp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
|
||||||
id: portTextField
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 40
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
|
||||||
textField.text: port
|
|
||||||
textField.maximumLength: 5
|
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
|
||||||
if (textField.text !== port) {
|
|
||||||
port = textField.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkEmptyText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: autoNegotiateEncryprionSwitcher
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 24
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
text: qsTr("Auto-negotiate encryption")
|
|
||||||
checked: autoNegotiateEncryprion
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== autoNegotiateEncryprion) {
|
|
||||||
autoNegotiateEncryprion = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DropDownType {
|
|
||||||
id: hashDropDown
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 20
|
|
||||||
|
|
||||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
|
||||||
|
|
||||||
descriptionText: qsTr("Hash")
|
|
||||||
headerText: qsTr("Hash")
|
|
||||||
|
|
||||||
drawerParent: root
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
|
||||||
id: hashListView
|
|
||||||
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
ListElement { name : qsTr("SHA512") }
|
|
||||||
ListElement { name : qsTr("SHA384") }
|
|
||||||
ListElement { name : qsTr("SHA256") }
|
|
||||||
ListElement { name : qsTr("SHA3-512") }
|
|
||||||
ListElement { name : qsTr("SHA3-384") }
|
|
||||||
ListElement { name : qsTr("SHA3-256") }
|
|
||||||
ListElement { name : qsTr("whirlpool") }
|
|
||||||
ListElement { name : qsTr("BLAKE2b512") }
|
|
||||||
ListElement { name : qsTr("BLAKE2s256") }
|
|
||||||
ListElement { name : qsTr("SHA1") }
|
|
||||||
}
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
hashDropDown.text = selectedText
|
|
||||||
hash = hashDropDown.text
|
|
||||||
hashDropDown.closeTriggered()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
hashDropDown.text = hash
|
|
||||||
|
|
||||||
for (var i = 0; i < hashListView.model.count; i++) {
|
|
||||||
if (hashListView.model.get(i).name === hashDropDown.text) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DropDownType {
|
|
||||||
id: cipherDropDown
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
|
||||||
|
|
||||||
descriptionText: qsTr("Cipher")
|
|
||||||
headerText: qsTr("Cipher")
|
|
||||||
|
|
||||||
drawerParent: root
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
|
||||||
id: cipherListView
|
|
||||||
|
|
||||||
rootWidth: root.width
|
|
||||||
|
|
||||||
model: ListModel {
|
|
||||||
ListElement { name : qsTr("AES-256-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-192-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-128-GCM") }
|
|
||||||
ListElement { name : qsTr("AES-256-CBC") }
|
|
||||||
ListElement { name : qsTr("AES-192-CBC") }
|
|
||||||
ListElement { name : qsTr("AES-128-CBC") }
|
|
||||||
ListElement { name : qsTr("ChaCha20-Poly1305") }
|
|
||||||
ListElement { name : qsTr("ARIA-256-CBC") }
|
|
||||||
ListElement { name : qsTr("CAMELLIA-256-CBC") }
|
|
||||||
ListElement { name : qsTr("none") }
|
|
||||||
}
|
|
||||||
|
|
||||||
clickedFunction: function() {
|
|
||||||
cipherDropDown.text = selectedText
|
|
||||||
cipher = cipherDropDown.text
|
|
||||||
cipherDropDown.closeTriggered()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
cipherDropDown.text = cipher
|
|
||||||
|
|
||||||
for (var i = 0; i < cipherListView.model.count; i++) {
|
|
||||||
if (cipherListView.model.get(i).name === cipherDropDown.text) {
|
|
||||||
currentIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: contentRect
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
Layout.preferredHeight: checkboxLayout.implicitHeight
|
|
||||||
color: AmneziaStyle.color.onyxBlack
|
|
||||||
radius: 16
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: tlsAuthCheckBox
|
|
||||||
enabled: !GC.isMobile()
|
|
||||||
|
|
||||||
function onFocusChanged() {
|
|
||||||
if (tlsAuthCheckBox.activeFocus) {
|
|
||||||
fl.ensureVisible(contentRect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: checkboxLayout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
CheckBoxType {
|
|
||||||
id: tlsAuthCheckBox
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("TLS auth")
|
|
||||||
checked: tlsAuth
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== tlsAuth) {
|
|
||||||
console.log("tlsAuth changed to: " + checked)
|
|
||||||
tlsAuth = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {}
|
|
||||||
|
|
||||||
CheckBoxType {
|
|
||||||
id: blockDnsCheckBox
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("Block DNS requests outside of VPN")
|
|
||||||
checked: blockDns
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (checked !== blockDns) {
|
|
||||||
blockDns = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: additionalClientCommandsSwitcher
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 32
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
checked: additionalClientCommands !== ""
|
|
||||||
|
|
||||||
text: qsTr("Additional client configuration commands")
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (!checked) {
|
|
||||||
additionalClientCommands = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextAreaType {
|
|
||||||
id: additionalClientCommandsTextArea
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
visible: additionalClientCommandsSwitcher.checked
|
|
||||||
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
textAreaText: additionalClientCommands
|
|
||||||
placeholderText: qsTr("Commands:")
|
|
||||||
|
|
||||||
textArea.onEditingFinished: {
|
|
||||||
if (additionalClientCommands !== textAreaText) {
|
|
||||||
additionalClientCommands = textAreaText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwitcherType {
|
|
||||||
id: additionalServerCommandsSwitcher
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
checked: additionalServerCommands !== ""
|
|
||||||
|
|
||||||
text: qsTr("Additional server configuration commands")
|
|
||||||
|
|
||||||
onCheckedChanged: {
|
|
||||||
if (!checked) {
|
|
||||||
additionalServerCommands = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextAreaType {
|
|
||||||
id: additionalServerCommandsTextArea
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 16
|
|
||||||
|
|
||||||
visible: additionalServerCommandsSwitcher.checked
|
|
||||||
|
|
||||||
textAreaText: additionalServerCommands
|
|
||||||
placeholderText: qsTr("Commands:")
|
|
||||||
parentFlickable: fl
|
|
||||||
textArea.onEditingFinished: {
|
|
||||||
if (additionalServerCommands !== textAreaText) {
|
|
||||||
additionalServerCommands = textAreaText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicButtonType {
|
|
||||||
id: saveButton
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 24
|
|
||||||
Layout.bottomMargin: 24
|
|
||||||
|
|
||||||
enabled: vpnAddressSubnetTextField.errorText === "" &&
|
|
||||||
portTextField.errorText === ""
|
|
||||||
|
|
||||||
text: qsTr("Save")
|
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
onClicked: function() {
|
|
||||||
forceActiveFocus()
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
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(OpenVpnConfigModel.getConfig())
|
|
||||||
}
|
|
||||||
var noButtonFunction = function() {
|
|
||||||
if (!GC.isMobile()) {
|
|
||||||
saveButton.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEnterPressed: saveButton.clicked()
|
|
||||||
Keys.onReturnPressed: saveButton.clicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DropDownType {
|
||||||
|
id: cipherDropDown
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||||
|
|
||||||
|
descriptionText: qsTr("Cipher")
|
||||||
|
headerText: qsTr("Cipher")
|
||||||
|
|
||||||
|
drawerParent: root
|
||||||
|
|
||||||
|
listView: ListViewWithRadioButtonType {
|
||||||
|
id: cipherListView
|
||||||
|
|
||||||
|
rootWidth: root.width
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
ListElement { name : qsTr("AES-256-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-192-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-128-GCM") }
|
||||||
|
ListElement { name : qsTr("AES-256-CBC") }
|
||||||
|
ListElement { name : qsTr("AES-192-CBC") }
|
||||||
|
ListElement { name : qsTr("AES-128-CBC") }
|
||||||
|
ListElement { name : qsTr("ChaCha20-Poly1305") }
|
||||||
|
ListElement { name : qsTr("ARIA-256-CBC") }
|
||||||
|
ListElement { name : qsTr("CAMELLIA-256-CBC") }
|
||||||
|
ListElement { name : qsTr("none") }
|
||||||
|
}
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
cipherDropDown.text = selectedText
|
||||||
|
cipher = cipherDropDown.text
|
||||||
|
cipherDropDown.closeTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
cipherDropDown.text = cipher
|
||||||
|
|
||||||
|
for (var i = 0; i < cipherListView.model.count; i++) {
|
||||||
|
if (cipherListView.model.get(i).name === cipherDropDown.text) {
|
||||||
|
currentIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: contentRect
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
Layout.preferredHeight: checkboxLayout.implicitHeight
|
||||||
|
color: AmneziaStyle.color.onyxBlack
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: checkboxLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
CheckBoxType {
|
||||||
|
id: tlsAuthCheckBox
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("TLS auth")
|
||||||
|
checked: tlsAuth
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== tlsAuth) {
|
||||||
|
console.log("tlsAuth changed to: " + checked)
|
||||||
|
tlsAuth = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
CheckBoxType {
|
||||||
|
id: blockDnsCheckBox
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Block DNS requests outside of VPN")
|
||||||
|
checked: blockDns
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== blockDns) {
|
||||||
|
blockDns = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: additionalClientCommandsSwitcher
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
checked: additionalClientCommands !== ""
|
||||||
|
|
||||||
|
text: qsTr("Additional client configuration commands")
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (!checked) {
|
||||||
|
additionalClientCommands = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaType {
|
||||||
|
id: additionalClientCommandsTextArea
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
visible: additionalClientCommandsSwitcher.checked
|
||||||
|
|
||||||
|
textAreaText: additionalClientCommands
|
||||||
|
placeholderText: qsTr("Commands:")
|
||||||
|
|
||||||
|
textArea.onEditingFinished: {
|
||||||
|
if (additionalClientCommands !== textAreaText) {
|
||||||
|
additionalClientCommands = textAreaText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: additionalServerCommandsSwitcher
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
checked: additionalServerCommands !== ""
|
||||||
|
|
||||||
|
text: qsTr("Additional server configuration commands")
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (!checked) {
|
||||||
|
additionalServerCommands = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaType {
|
||||||
|
id: additionalServerCommandsTextArea
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
visible: additionalServerCommandsSwitcher.checked
|
||||||
|
|
||||||
|
textAreaText: additionalServerCommands
|
||||||
|
placeholderText: qsTr("Commands:")
|
||||||
|
|
||||||
|
textArea.onEditingFinished: {
|
||||||
|
if (additionalServerCommands !== textAreaText) {
|
||||||
|
additionalServerCommands = textAreaText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: saveButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
enabled: vpnAddressSubnetTextField.errorText === "" &&
|
||||||
|
portTextField.errorText === ""
|
||||||
|
|
||||||
|
text: qsTr("Save")
|
||||||
|
|
||||||
|
onClicked: function() {
|
||||||
|
forceActiveFocus()
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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(OpenVpnConfigModel.getConfig())
|
||||||
|
}
|
||||||
|
var noButtonFunction = function() {
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
saveButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEnterPressed: saveButton.clicked()
|
||||||
|
Keys.onReturnPressed: saveButton.clicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("VPN address subnet")
|
headerText: qsTr("VPN address subnet")
|
||||||
textField.text: subnetAddress
|
textField.text: subnetAddress
|
||||||
|
|
||||||
@@ -87,8 +85,6 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textField.text: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ PageType {
|
|||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
visible: isVisible
|
visible: isVisible
|
||||||
|
|
||||||
@@ -68,8 +66,6 @@ PageType {
|
|||||||
|
|
||||||
visible: GC.isDesktop()
|
visible: GC.isDesktop()
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
text: qsTr("Close application")
|
text: qsTr("Close application")
|
||||||
leftImageSource: "qrc:/images/controls/x-circle.svg"
|
leftImageSource: "qrc:/images/controls/x-circle.svg"
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool isSending: false
|
||||||
|
|
||||||
|
function getAddedDeviceName() {
|
||||||
|
try {
|
||||||
|
var obj = JSON.parse(TransferController.pendingQrCode)
|
||||||
|
if (obj && obj.name && obj.name.length > 0) {
|
||||||
|
return obj.name
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return qsTr("Device")
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvailableCount() {
|
||||||
|
var max = ApiAccountInfoModel.data("maxDeviceCount")
|
||||||
|
var active = ApiAccountInfoModel.data("activeDeviceCount")
|
||||||
|
if (!max || max <= 0) max = 7
|
||||||
|
if (!active || active < 0) active = 0
|
||||||
|
var remain = max - active
|
||||||
|
return remain > 0 ? remain : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
backButtonFunction: function() {
|
||||||
|
if (root.isSending) {
|
||||||
|
TransferController.stopWaitForConfig()
|
||||||
|
}
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Add a new device to the subscription?")
|
||||||
|
descriptionText: qsTr("Devices available with Amnezia Premium: (%1)").arg(getAvailableCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Add Device")
|
||||||
|
enabled: !root.isSending && root.getAvailableCount() > 0 && TransferController.pendingQrCode !== ""
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
if (TransferController.pendingQrCode !== "") {
|
||||||
|
root.isSending = true
|
||||||
|
TransferController.onTransferQrScanned(TransferController.pendingQrCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 8
|
||||||
|
|
||||||
|
defaultColor: AmneziaStyle.color.transparent
|
||||||
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
|
textColor: AmneziaStyle.color.paleGray
|
||||||
|
borderColor: AmneziaStyle.color.paleGray
|
||||||
|
borderWidth: 1
|
||||||
|
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
enabled: !root.isSending
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TransferController
|
||||||
|
|
||||||
|
function onPostStarted() {
|
||||||
|
PageController.showNotificationMessage(qsTr("Sending configuration..."))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostSucceeded() {
|
||||||
|
root.isSending = false
|
||||||
|
PageController.showNotificationMessage(qsTr("%1 has been added to your subscription").arg(root.getAddedDeviceName()))
|
||||||
|
PageController.closePage()
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostFailed(message) {
|
||||||
|
root.isSending = false
|
||||||
|
PageController.showErrorMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import QRCodeReader 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool scanCompleted: false
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: cameraArea
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: iosQrLoader
|
||||||
|
anchors.fill: cameraArea
|
||||||
|
active: Qt.platform.os === "ios"
|
||||||
|
|
||||||
|
sourceComponent: Component {
|
||||||
|
QRCodeReader {
|
||||||
|
id: qrCodeReader
|
||||||
|
|
||||||
|
function updateCameraSize() {
|
||||||
|
qrCodeReader.setCameraSize(Qt.rect(cameraArea.x,
|
||||||
|
cameraArea.y,
|
||||||
|
cameraArea.width,
|
||||||
|
cameraArea.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
onCodeReaded: function(code) {
|
||||||
|
if (!code || code.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = null
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(code)
|
||||||
|
} catch (e) {
|
||||||
|
obj = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj || !obj.gw || !(obj.uuid || obj.u)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
|
||||||
|
if (obj.name && obj.name.length > 0) {
|
||||||
|
normalizedObj.name = obj.name
|
||||||
|
}
|
||||||
|
var normalized = JSON.stringify(normalizedObj)
|
||||||
|
TransferController.setPendingQrCode(normalized)
|
||||||
|
qrCodeReader.stopReading()
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
updateCameraSize()
|
||||||
|
qrCodeReader.startReading()
|
||||||
|
}
|
||||||
|
Component.onDestruction: qrCodeReader.stopReading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onWidthChanged: {
|
||||||
|
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
|
||||||
|
iosQrLoader.item.updateCameraSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onHeightChanged: {
|
||||||
|
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
|
||||||
|
iosQrLoader.item.updateCameraSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TransferController
|
||||||
|
function onScannerShouldStop() {
|
||||||
|
if (iosQrLoader.item && iosQrLoader.item.stopReading) {
|
||||||
|
iosQrLoader.item.stopReading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ImportController
|
||||||
|
|
||||||
|
function onTransferQrDecoded(code) {
|
||||||
|
if (!code || code.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = null
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(code)
|
||||||
|
} catch (e) {
|
||||||
|
obj = null
|
||||||
|
}
|
||||||
|
if (obj && obj.gw && (obj.uuid || obj.u)) {
|
||||||
|
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
|
||||||
|
if (obj.name && obj.name.length > 0) {
|
||||||
|
normalizedObj.name = obj.name
|
||||||
|
}
|
||||||
|
code = JSON.stringify(normalizedObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
root.scanCompleted = true
|
||||||
|
TransferController.setPendingQrCode(code)
|
||||||
|
Qt.callLater(function() {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onQrDecodingFinished() {
|
||||||
|
if (Qt.platform.os === "android" && !root.scanCompleted) {
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
TransferController.setPendingQrCode("")
|
||||||
|
root.scanCompleted = false
|
||||||
|
|
||||||
|
if (Qt.platform.os === "android") {
|
||||||
|
ImportController.startDecodingQr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import QtQuick.Layouts
|
|||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
import QtCore
|
import QtCore
|
||||||
|
import QRCodeReader 1.0
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
@@ -19,6 +20,29 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
function isAtDeviceLimit() {
|
||||||
|
var maxDeviceCount = ApiAccountInfoModel.data("maxDeviceCount")
|
||||||
|
return listView.count >= maxDeviceCount
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigFilesCount() {
|
||||||
|
try {
|
||||||
|
var arr = ApiAccountInfoModel.getIssuedConfigsInfo()
|
||||||
|
if (!arr) return 0
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
var item = arr[i]
|
||||||
|
if (item && item["source_type"] === "country_config") {
|
||||||
|
++count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
} catch (e) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
@@ -46,16 +70,41 @@ PageType {
|
|||||||
descriptionText: qsTr("Manage currently connected devices")
|
descriptionText: qsTr("Manage currently connected devices")
|
||||||
}
|
}
|
||||||
|
|
||||||
WarningType {
|
BasicButtonType {
|
||||||
|
id: addDeviceQrButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
|
visible: GC.isMobile()
|
||||||
|
|
||||||
|
defaultColor: AmneziaStyle.color.transparent
|
||||||
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
|
textColor: AmneziaStyle.color.paleGray
|
||||||
|
borderColor: AmneziaStyle.color.paleGray
|
||||||
|
borderWidth: 1
|
||||||
|
|
||||||
|
text: qsTr("Add device by QR code")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
if (root.isAtDeviceLimit()) {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiDevicesLimit)
|
||||||
|
} else {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceScan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallTextType {
|
||||||
|
Layout.topMargin: 8
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
|
text: qsTr("On the other device, tap + at the bottom → Connect to Amnezia Premium")
|
||||||
+ "by tapping '+' and then the three dots at the top of the page.")
|
|
||||||
|
|
||||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +142,58 @@ PageType {
|
|||||||
|
|
||||||
DividerType {}
|
DividerType {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
text: qsTr("Configuration Files: {%1}").arg(root.getConfigFilesCount())
|
||||||
|
descriptionText: qsTr("Generated configuration files also count towards the device limit")
|
||||||
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
ApiSettingsController.updateApiCountryModel()
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DividerType {}
|
||||||
|
|
||||||
|
WarningType {
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
textString: qsTr("The Support tag is available on the Support page. In older versions: tap + in the bottom bar, then More (...) in the top-right.")
|
||||||
|
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TransferController
|
||||||
|
|
||||||
|
function onPostStarted() {
|
||||||
|
PageController.showBusyIndicator(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostSucceeded() {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
ApiSettingsController.getAccountInfo(true)
|
||||||
|
PageController.showNotificationMessage(qsTr("New device added to subscription"))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPostFailed(message) {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
PageController.showErrorMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScannerShouldStop() {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deactivateExternalDevice(supportTag, countryCode) {
|
function deactivateExternalDevice(supportTag, countryCode) {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
|
BackButtonType {}
|
||||||
|
|
||||||
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("Your Amnezia Premium subscription already has the maximum number of devices — ") + ApiAccountInfoModel.data("connectedDevices")
|
||||||
|
descriptionText: qsTr("Remove one of the previously connected devices to add a new one")
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Show all devices")
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
PageController.goToPage(PageEnum.PageSettingsApiDevices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ PageType {
|
|||||||
|
|
||||||
actionButtonImage: "qrc:/images/controls/edit-3.svg"
|
actionButtonImage: "qrc:/images/controls/edit-3.svg"
|
||||||
|
|
||||||
headerText: root.processedServer.name
|
headerText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
|
||||||
descriptionText: ApiAccountInfoModel.data("serviceDescription")
|
descriptionText: ApiAccountInfoModel.data("serviceDescription")
|
||||||
|
|
||||||
actionButtonFunction: function() {
|
actionButtonFunction: function() {
|
||||||
@@ -214,9 +214,6 @@ PageType {
|
|||||||
ApiConfigsController.prepareVpnKeyExport()
|
ApiConfigsController.prepareVpnKeyExport()
|
||||||
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
|
|
||||||
// Navigate to PageShareConnection page
|
|
||||||
//PageController.goToPage(PageEnum.PageShareConnection)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,6 +413,6 @@ PageType {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
expandedHeight: parent.height * 0.35
|
expandedHeight: parent.height * 0.35
|
||||||
|
|
||||||
serverNameText: root.processedServer.name
|
serverNameText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ PageType {
|
|||||||
|
|
||||||
text: qsTr("If AmneziaDNS is not used or installed")
|
text: qsTr("If AmneziaDNS is not used or installed")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model: 1 // fake model to force the ListView to be created without a model
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: primaryDns
|
id: primaryDns
|
||||||
@@ -96,13 +103,6 @@ PageType {
|
|||||||
regularExpression: InstallController.ipAddressRegExp()
|
regularExpression: InstallController.ipAddressRegExp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
model: 1 // fake model to force the ListView to be created without a model
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: restoreDefaultButton
|
id: restoreDefaultButton
|
||||||
@@ -139,10 +139,6 @@ PageType {
|
|||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
footer: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: saveButton
|
id: saveButton
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ PageType {
|
|||||||
|
|
||||||
signal lastItemTabClickedSignal()
|
signal lastItemTabClickedSignal()
|
||||||
|
|
||||||
|
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: InstallController
|
target: InstallController
|
||||||
|
|
||||||
@@ -59,15 +61,13 @@ PageType {
|
|||||||
target: ServersModel
|
target: ServersModel
|
||||||
|
|
||||||
function onProcessedServerIndexChanged() {
|
function onProcessedServerIndexChanged() {
|
||||||
listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
|
root.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
model: serverActions
|
model: serverActions
|
||||||
@@ -107,7 +107,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: check
|
id: check
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
|
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
|
||||||
readonly property string description: qsTr("Add them to the application if they were not displayed")
|
readonly property string description: qsTr("Add them to the application if they were not displayed")
|
||||||
readonly property var tColor: AmneziaStyle.color.paleGray
|
readonly property var tColor: AmneziaStyle.color.paleGray
|
||||||
@@ -121,7 +121,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: reboot
|
id: reboot
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Reboot server")
|
readonly property string title: qsTr("Reboot server")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
@@ -181,7 +181,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: clear
|
id: clear
|
||||||
|
|
||||||
property bool isVisible: true
|
property bool isVisible: root.isServerWithWriteAccess
|
||||||
readonly property string title: qsTr("Clear server from Amnezia software")
|
readonly property string title: qsTr("Clear server from Amnezia software")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
@@ -240,7 +240,7 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: switch_to_premium
|
id: switch_to_premium
|
||||||
|
|
||||||
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
|
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium
|
||||||
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
|
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
|
||||||
readonly property string description: ""
|
readonly property string description: ""
|
||||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Connection")
|
headerText: qsTr("New Connection")
|
||||||
|
|
||||||
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
|
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
|
||||||
actionButtonFunction: function() {
|
actionButtonFunction: function() {
|
||||||
@@ -156,7 +156,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.bottomMargin: 24
|
Layout.bottomMargin: 24
|
||||||
|
|
||||||
text: qsTr("Insert the key, add a configuration file or scan the QR-code")
|
text: qsTr("Enter a connection key, import a configuration file, or scan a QR code")
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
@@ -166,8 +166,8 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Insert key")
|
headerText: qsTr("Connection key")
|
||||||
buttonText: qsTr("Insert")
|
buttonText: qsTr("Paste")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
textField.text = ""
|
textField.text = ""
|
||||||
@@ -250,7 +250,7 @@ PageType {
|
|||||||
disabledColor: AmneziaStyle.color.mutedGray
|
disabledColor: AmneziaStyle.color.mutedGray
|
||||||
textColor: AmneziaStyle.color.goldenApricot
|
textColor: AmneziaStyle.color.goldenApricot
|
||||||
|
|
||||||
text: qsTr("Site Amnezia")
|
text: qsTr("Amnezia Website")
|
||||||
|
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
|
|
||||||
@@ -263,18 +263,17 @@ PageType {
|
|||||||
|
|
||||||
property list<QtObject> variants: [
|
property list<QtObject> variants: [
|
||||||
amneziaVpn,
|
amneziaVpn,
|
||||||
|
shareViaDevice,
|
||||||
selfHostVpn,
|
selfHostVpn,
|
||||||
backupRestore,
|
|
||||||
fileOpen,
|
fileOpen,
|
||||||
qrScan,
|
qrScan
|
||||||
siteLink
|
|
||||||
]
|
]
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: amneziaVpn
|
id: amneziaVpn
|
||||||
|
|
||||||
property string title: qsTr("VPN by Amnezia")
|
property string title: qsTr("Choose a VPN from Amnezia")
|
||||||
property string description: qsTr("Connect to classic paid and free VPN services from Amnezia")
|
property string description: qsTr("Paid and free VPN services from Amnezia")
|
||||||
property string imageSource: "qrc:/images/controls/amnezia.svg"
|
property string imageSource: "qrc:/images/controls/amnezia.svg"
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
property var handler: function() {
|
property var handler: function() {
|
||||||
@@ -287,11 +286,23 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: shareViaDevice
|
||||||
|
|
||||||
|
property string title: qsTr("Connect to Amnezia Premium")
|
||||||
|
property string description: qsTr("Via QR code from a phone with an active subscription")
|
||||||
|
property string imageSource: "qrc:/images/controls/monitor-with-phone.svg"
|
||||||
|
property bool isVisible: true
|
||||||
|
property var handler: function() {
|
||||||
|
PageController.goToPage(PageEnum.PageTransferConfigViaQR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: selfHostVpn
|
id: selfHostVpn
|
||||||
|
|
||||||
property string title: qsTr("Self-hosted VPN")
|
property string title: qsTr("Set up a Self-hosted VPN")
|
||||||
property string description: qsTr("Configure Amnezia VPN on your own server")
|
property string description: qsTr("On a private server")
|
||||||
property string imageSource: "qrc:/images/controls/server.svg"
|
property string imageSource: "qrc:/images/controls/server.svg"
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
property var handler: function() {
|
property var handler: function() {
|
||||||
@@ -320,8 +331,8 @@ PageType {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: fileOpen
|
id: fileOpen
|
||||||
|
|
||||||
property string title: qsTr("File with connection settings")
|
property string title: qsTr("Use a configuration file")
|
||||||
property string description: qsTr("")
|
property string description: qsTr("Supported formats: .conf, .vpn, .ovpn, .json")
|
||||||
property string imageSource: "qrc:/images/controls/folder-search-2.svg"
|
property string imageSource: "qrc:/images/controls/folder-search-2.svg"
|
||||||
property bool isVisible: true
|
property bool isVisible: true
|
||||||
property var handler: function() {
|
property var handler: function() {
|
||||||
@@ -334,13 +345,13 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: qrScan
|
id: qrScan
|
||||||
|
|
||||||
property string title: qsTr("QR code")
|
property string title: qsTr("Scan a QR code")
|
||||||
property string description: qsTr("")
|
property string description: qsTr("To connect to a self-hosted server")
|
||||||
property string imageSource: "qrc:/images/controls/scan-line.svg"
|
property string imageSource: "qrc:/images/controls/scan-line.svg"
|
||||||
property bool isVisible: SettingsController.isCameraPresent()
|
property bool isVisible: SettingsController.isCameraPresent()
|
||||||
property var handler: function() {
|
property var handler: function() {
|
||||||
@@ -349,7 +360,7 @@ PageType {
|
|||||||
PageController.goToPage(PageEnum.PageSetupWizardQrReader)
|
PageController.goToPage(PageEnum.PageSetupWizardQrReader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: siteLink
|
id: siteLink
|
||||||
|
|||||||
@@ -161,10 +161,4 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareConnectionDrawer {
|
|
||||||
id: shareConnectionDrawer
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Components"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
objectName: "PageTransferConfigViaQR"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: AmneziaStyle.color.midnightBlack
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
z: 1
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 24
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
Layout.topMargin: 20
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contentArea
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 16
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: qrContent
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
SmallTextType {
|
||||||
|
id: topHint
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: qsTr("Scan this QR code with a phone that has an active\nAmnezia Premium subscription")
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: qrFrame
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
property real maxByHeight: qrContent.height
|
||||||
|
- topHint.implicitHeight
|
||||||
|
- bottomHint.implicitHeight
|
||||||
|
- (qrContent.spacing * 2)
|
||||||
|
property real qrSize: Math.max(180, Math.min(qrContent.width, Math.max(0, maxByHeight)))
|
||||||
|
|
||||||
|
Layout.preferredWidth: qrSize
|
||||||
|
Layout.preferredHeight: qrSize
|
||||||
|
radius: 16
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: qrImage
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: false
|
||||||
|
sourceSize: Qt.size(Math.round(width), Math.round(height))
|
||||||
|
source: TransferController.qrCodeUrl
|
||||||
|
visible: TransferController.qrCodeUrl !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: TransferController.qrCodeUrl === ""
|
||||||
|
visible: TransferController.qrCodeUrl === ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallTextType {
|
||||||
|
id: bottomHint
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: qsTr("AmneziaVPN → Amnezia Premium →\nPersonal Dashboard → Active Devices →\nAdd Device via QR Code")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TransferController
|
||||||
|
function onConfigApplied() {
|
||||||
|
PageController.showNotificationMessage(qsTr("Device has been added to subscription"))
|
||||||
|
PageController.closePage()
|
||||||
|
PageController.goToPageHome()
|
||||||
|
}
|
||||||
|
function onWaitError(message) {
|
||||||
|
PageController.showErrorMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ImportController
|
||||||
|
function onImportErrorOccurred(errorCode, goToPageHome) {
|
||||||
|
PageController.showErrorMessage(errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
TransferController.generateNewQrCode()
|
||||||
|
TransferController.startWaitForConfig(ImportController)
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: TransferController.stopWaitForConfig()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user