added exportController and PageShare

- added a blank PageSettingsProtocol
This commit is contained in:
vladimir.kuznetsov 2023-06-13 20:03:20 +09:00
parent 3034019d5a
commit be7386f0d7
38 changed files with 1080 additions and 115 deletions

View file

@ -46,7 +46,6 @@
#endif #endif
m_settings = std::shared_ptr<Settings>(new Settings); m_settings = std::shared_ptr<Settings>(new Settings);
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
} }
AmneziaApplication::~AmneziaApplication() AmneziaApplication::~AmneziaApplication()
@ -80,10 +79,10 @@ void AmneziaApplication::init()
m_serversModel.reset(new ServersModel(m_settings, this)); m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
m_pageController.reset(new PageController(m_serversModel)); m_pageController.reset(new PageController(m_serversModel));
@ -95,6 +94,10 @@ void AmneziaApplication::init()
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(
new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
// //
m_engine->load(url); m_engine->load(url);

View file

@ -13,12 +13,13 @@
#include "configurators/vpn_configurator.h" #include "configurators/vpn_configurator.h"
#include "ui/models/servers_model.h"
#include "ui/models/containers_model.h"
#include "ui/controllers/connectionController.h" #include "ui/controllers/connectionController.h"
#include "ui/controllers/pageController.h" #include "ui/controllers/exportController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@ -71,7 +72,7 @@ private:
QScopedPointer<PageController> m_pageController; QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController; QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController; QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="23" height="23" rx="11.5" stroke="#A85809"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="23" height="23" rx="11.5" stroke="#878B91"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

View file

@ -6,10 +6,6 @@
<file>images/favorites_disabled.png</file> <file>images/favorites_disabled.png</file>
<file>images/favorites_enabled.png</file> <file>images/favorites_enabled.png</file>
<file>images/favorites_hover.png</file> <file>images/favorites_hover.png</file>
<file>images/controls/check_off.png</file>
<file>images/controls/check_on.png</file>
<file>images/controls/radio_off.png</file>
<file>images/controls/radio_on.png</file>
<file>images/download.png</file> <file>images/download.png</file>
<file>images/upload.png</file> <file>images/upload.png</file>
<file>images/tray/active.png</file> <file>images/tray/active.png</file>
@ -257,5 +253,11 @@
<file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file> <file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file>
<file>ui/qml/Components/Protocols/OpenVpnSettings.qml</file> <file>ui/qml/Components/Protocols/OpenVpnSettings.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file> <file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
<file>images/controls/radio-button.svg</file>
<file>images/controls/radio-button-inner-circle.png</file>
<file>images/controls/radio-button-pressed.svg</file>
<file>images/controls/radio-button-inner-circle-pressed.png</file>
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -0,0 +1,156 @@
#include "exportController.h"
#include <QBuffer>
#include <QDataStream>
#include <QDesktopServices>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QImage>
#include <QStandardPaths>
#include "qrcodegen.hpp"
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator,
QObject *parent)
: QObject(parent)
, m_serversModel(serversModel)
, m_containersModel(containersModel)
, m_settings(settings)
, m_configurator(configurator)
{}
void ExportController::generateFullAccessConfig()
{
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
QJsonObject config = m_settings->server(serverIndex);
QByteArray compressedConfig = QJsonDocument(config).toJson();
compressedConfig = qCompress(compressedConfig, 8);
m_amneziaCode = QString("vpn://%1")
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig);
}
void ExportController::generateConnectionConfig()
{
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(
m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig = qvariant_cast<QJsonObject>(
m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode e = ErrorCode::NoError;
for (Proto p : ContainerProps::protocolsForContainer(container)) {
QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, p);
QString cfg = m_configurator->genVpnProtocolConfig(credentials,
container,
containerConfig,
p,
&e);
if (e) {
cfg = "Error generating config";
break;
}
protoConfig.insert(config_key::last_config, cfg);
containerConfig.insert(ProtocolProps::protoToString(p), protoConfig);
}
QJsonObject config = m_settings->server(serverIndex);
if (!e) {
config.remove(config_key::userName);
config.remove(config_key::password);
config.remove(config_key::port);
config.insert(config_key::containers, QJsonArray{containerConfig});
config.insert(config_key::defaultContainer, ContainerProps::containerToString(container));
auto dns = m_configurator->getDnsForConfig(serverIndex);
config.insert(config_key::dns1, dns.first);
config.insert(config_key::dns2, dns.second);
} /*else {
set_textEditShareAmneziaCodeText(tr("Error while generating connection profile"));
return;
}*/
QByteArray compressedConfig = QJsonDocument(config).toJson();
compressedConfig = qCompress(compressedConfig, 8);
m_amneziaCode = QString("vpn://%1")
.arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals)));
m_qrCodes = generateQrCodeImageSeries(compressedConfig);
}
QString ExportController::getAmneziaCode()
{
return m_amneziaCode;
}
QList<QString> ExportController::getQrCodes()
{
return m_qrCodes;
}
void ExportController::saveFile()
{
QString fileExtension = ".vpn";
QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl fileName;
fileName = QFileDialog::getSaveFileUrl(nullptr,
tr("Save AmneziaVPN config"),
QUrl::fromLocalFile(docDir + "/" + "amnezia_config"),
"*" + fileExtension);
if (fileName.isEmpty())
return;
if (!fileName.toString().endsWith(fileExtension)) {
fileName = QUrl(fileName.toString() + fileExtension);
}
if (fileName.isEmpty())
return;
QFile save(fileName.toLocalFile());
save.open(QIODevice::WriteOnly);
save.write(m_amneziaCode.toUtf8());
save.close();
QFileInfo fi(fileName.toLocalFile());
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
}
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;
quint8 chunksCount = std::ceil(data.size() / k);
QList<QString> chunks;
for (int i = 0; i < data.size(); i = i + k) {
QByteArray chunk;
QDataStream s(&chunk, QIODevice::WriteOnly);
s << amnezia::qrMagicCode << chunksCount << (quint8) std::round(i / k) << data.mid(i, k);
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding
| QByteArray::OmitTrailingEquals);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 0));
chunks.append(svgToBase64(svg));
}
return chunks;
}
QString ExportController::svgToBase64(const QString &image)
{
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
}

View file

@ -0,0 +1,44 @@
#ifndef EXPORTCONTROLLER_H
#define EXPORTCONTROLLER_H
#include <QObject>
#include "configurators/vpn_configurator.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
class ExportController : public QObject
{
Q_OBJECT
public:
explicit ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator,
QObject *parent = nullptr);
public slots:
void generateFullAccessConfig();
void generateConnectionConfig();
QString getAmneziaCode();
QList<QString> getQrCodes();
void saveFile();
signals:
void generateConfig(bool isFullAccess);
private:
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
QString svgToBase64(const QString &image);
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QString m_amneziaCode;
QList<QString> m_qrCodes;
};
#endif // EXPORTCONTROLLER_H

View file

@ -27,6 +27,7 @@ public slots:
signals: signals:
void importFinished(); void importFinished();
void importErrorOccurred(QString errorMessage); void importErrorOccurred(QString errorMessage);
private: private:
QJsonObject extractAmneziaConfig(QString &data); QJsonObject extractAmneziaConfig(QString &data);
QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractOpenVpnConfig(const QString &data);

View file

@ -9,8 +9,7 @@ InstallController::InstallController(const QSharedPointer<ServersModel> &servers
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings, const std::shared_ptr<Settings> &settings,
QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings)
{ {}
}
void InstallController::install(DockerContainer container, int port, TransportProto transportProto) void InstallController::install(DockerContainer container, int port, TransportProto transportProto)
{ {

View file

@ -24,7 +24,6 @@ namespace PageLoader
PageSettingsServerProtocol, PageSettingsServerProtocol,
PageSetupWizardStart, PageSetupWizardStart,
PageTest,
PageSetupWizardCredentials, PageSetupWizardCredentials,
PageSetupWizardProtocols, PageSetupWizardProtocols,
PageSetupWizardEasy, PageSetupWizardEasy,

View file

@ -0,0 +1,19 @@
#include "protocolSettingsController.h"
ProtocolSettingsController::ProtocolSettingsController(
const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
QObject *parent)
: QObject(parent)
, m_serversModel(serversModel)
, m_containersModel(containersModel)
, m_settings(settings)
{}
QByteArray ProtocolSettingsController::getOpenVpnConfig()
{
auto containerIndex = m_containersModel->index(
m_containersModel->getCurrentlyProcessedContainerIndex());
auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole);
}

View file

@ -0,0 +1,31 @@
#ifndef PROTOCOLSETTINGSCONTROLLER_H
#define PROTOCOLSETTINGSCONTROLLER_H
#include <QObject>
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
class ProtocolSettingsController : public QObject
{
Q_OBJECT
public:
explicit ProtocolSettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
public slots:
QByteArray getOpenVpnConfig();
signals:
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
std::shared_ptr<Settings> m_settings;
};
#endif // PROTOCOLSETTINGSCONTROLLER_H

View file

@ -25,7 +25,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
// return ContainerProps::containerHumanNames().value(container); // return ContainerProps::containerHumanNames().value(container);
case DescRole: case DescRole:
// return ContainerProps::containerDescriptions().value(container); // return ContainerProps::containerDescriptions().value(container);
case ConfigRole: case ConfigRole: //todo save to model also
m_settings->setContainerConfig(m_currentlyProcessedServerIndex, m_settings->setContainerConfig(m_currentlyProcessedServerIndex,
container, container,
value.toJsonObject()); value.toJsonObject());
@ -76,8 +76,8 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
return ContainerProps::easySetupDescription(container); return ContainerProps::easySetupDescription(container);
case IsInstalledRole: case IsInstalledRole:
return m_containers.contains(container); return m_containers.contains(container);
case IsCurrentlyInstalledRole: case IsCurrentlyProcessedRole:
return container == static_cast<DockerContainer>(m_currentlyInstalledContainerIndex); return container == static_cast<DockerContainer>(m_currentlyProcessedContainerIndex);
case IsDefaultRole: case IsDefaultRole:
return container == m_defaultContainerIndex; return container == m_defaultContainerIndex;
case IsSupportedRole: case IsSupportedRole:
@ -97,9 +97,9 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index)
emit defaultContainerChanged(); emit defaultContainerChanged();
} }
void ContainersModel::setCurrentlyInstalledContainerIndex(int index) void ContainersModel::setCurrentlyProcessedContainerIndex(int index)
{ {
m_currentlyInstalledContainerIndex = index; m_currentlyProcessedContainerIndex = index;
} }
DockerContainer ContainersModel::getDefaultContainer() DockerContainer ContainersModel::getDefaultContainer()
@ -112,9 +112,9 @@ QString ContainersModel::getDefaultContainerName()
return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); return ContainerProps::containerHumanNames().value(m_defaultContainerIndex);
} }
int ContainersModel::getCurrentlyInstalledContainerIndex() int ContainersModel::getCurrentlyProcessedContainerIndex()
{ {
return m_currentlyInstalledContainerIndex; return m_currentlyProcessedContainerIndex;
} }
void ContainersModel::removeAllContainers() void ContainersModel::removeAllContainers()
@ -153,7 +153,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const {
roles[EasySetupDescriptionRole] = "easySetupDescription"; roles[EasySetupDescriptionRole] = "easySetupDescription";
roles[IsInstalledRole] = "isInstalled"; roles[IsInstalledRole] = "isInstalled";
roles[IsCurrentlyInstalledRole] = "isCurrentlyInstalled"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[IsDefaultRole] = "isDefault"; roles[IsDefaultRole] = "isDefault";
roles[IsSupportedRole] = "isSupported"; roles[IsSupportedRole] = "isSupported";
return roles; return roles;

View file

@ -27,7 +27,7 @@ public:
EasySetupDescriptionRole, EasySetupDescriptionRole,
IsInstalledRole, IsInstalledRole,
IsCurrentlyInstalledRole, IsCurrentlyProcessedRole,
IsDefaultRole, IsDefaultRole,
IsSupportedRole IsSupportedRole
}; };
@ -45,8 +45,8 @@ public slots:
QString getDefaultContainerName(); QString getDefaultContainerName();
void setCurrentlyProcessedServerIndex(int index); void setCurrentlyProcessedServerIndex(int index);
void setCurrentlyInstalledContainerIndex(int index); void setCurrentlyProcessedContainerIndex(int index);
int getCurrentlyInstalledContainerIndex(); int getCurrentlyProcessedContainerIndex();
void removeAllContainers(); void removeAllContainers();
void clearCachedProfiles(); void clearCachedProfiles();
@ -59,7 +59,7 @@ private:
int m_currentlyProcessedServerIndex; int m_currentlyProcessedServerIndex;
int m_currentlyInstalledContainerIndex; int m_currentlyProcessedContainerIndex;
DockerContainer m_defaultContainerIndex; DockerContainer m_defaultContainerIndex;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;

View file

@ -75,6 +75,11 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index)
m_currenlyProcessedServerIndex = index; m_currenlyProcessedServerIndex = index;
} }
int ServersModel::getCurrentlyProcessedServerIndex()
{
return m_currenlyProcessedServerIndex;
}
bool ServersModel::isDefaultServerCurrentlyProcessed() bool ServersModel::isDefaultServerCurrentlyProcessed()
{ {
return m_defaultServerIndex == m_currenlyProcessedServerIndex; return m_defaultServerIndex == m_currenlyProcessedServerIndex;

View file

@ -37,6 +37,7 @@ public slots:
const int getServersCount(); const int getServersCount();
void setCurrentlyProcessedServerIndex(int index); void setCurrentlyProcessedServerIndex(int index);
int getCurrentlyProcessedServerIndex();
ServerCredentials getCurrentlyProcessedServerCredentials(); ServerCredentials getCurrentlyProcessedServerCredentials();
void addServer(const QJsonObject &server); void addServer(const QJsonObject &server);

View file

@ -59,7 +59,7 @@ ListView {
menuContent.currentIndex = index menuContent.currentIndex = index
containersDropDown.menuVisible = false containersDropDown.menuVisible = false
} else { } else {
ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index))
InstallController.setShouldCreateServer(false) InstallController.setShouldCreateServer(false)
goToPage(PageEnum.PageSetupWizardProtocolSettings) goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.menuVisible = false containersDropDown.menuVisible = false

View file

@ -8,8 +8,12 @@ import "../../Components"
Item { Item {
id: root id: root
implicitHeight: col.implicitHeight
implicitWidth: col.implicitWidth
ColumnLayout { ColumnLayout {
id: col
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 16 anchors.leftMargin: 16
@ -51,13 +55,79 @@ Item {
} }
DropDownType { DropDownType {
id: hash
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 74
rootButtonBorderWidth: 0
descriptionText: qsTr("Hash")
headerText: qsTr("Hash")
listView: ListViewType {
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") }
}
currentIndex: 0
clickedFunction: {
hash.text = selectedText
hash.menuVisible = false
}
Component.onCompleted: {
hash.text = selectedText
}
}
} }
DropDownType { DropDownType {
id: cipher
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 74
rootButtonBorderWidth: 0
descriptionText: qsTr("Cipher")
headerText: qsTr("Cipher")
listView: ListViewType {
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") }
}
currentIndex: 0
clickedFunction: {
cipher.text = selectedText
cipher.menuVisible = false
}
Component.onCompleted: {
cipher.text = selectedText
}
}
} }
CheckBoxType { CheckBoxType {

View file

@ -88,9 +88,10 @@ ListView {
onClicked: { onClicked: {
if (isInstalled) { if (isInstalled) {
ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index))
goToPage(PageEnum.PageSettingsServerProtocol) goToPage(PageEnum.PageSettingsServerProtocol)
} else { } else {
ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index))
InstallController.setShouldCreateServer(false) InstallController.setShouldCreateServer(false)
goToPage(PageEnum.PageSetupWizardProtocolSettings) goToPage(PageEnum.PageSetupWizardProtocolSettings)
} }

View file

@ -0,0 +1,177 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
DrawerType {
id: root
property var qrCodes: []
property alias configText: configContent.text
property alias headerText: header.headerText
width: parent.width
height: parent.height * 0.9
Item{
anchors.fill: parent
FlickableType {
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: content.height + 32
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
anchors.leftMargin: 16
anchors.rightMargin: 16
Header2Type {
id: header
Layout.fillWidth: true
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Save connection code")
onClicked: {
ExportController.saveFile()
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 8
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Copy")
onClicked: {
configContent.selectAll()
configContent.copy()
configContent.select(0, 0)
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 8
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
text: showContent ? qsTr("Collapse content") : qsTr("Show content")
onClicked: {
showContent = !showContent
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin
radius: 10
color: "#2C2D30"
visible: showContent
height: 24
TextField {
id: configContent
anchors.fill: parent
anchors.margins: 16
height: 24
color: "#D7D8DB"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
wrapMode: Text.Wrap
enabled: false
background: Rectangle {
anchors.fill: parent
color: "transparent"
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: width
Layout.topMargin: 20
color: "white"
Image {
anchors.fill: parent
smooth: false
Timer {
property int idx: 0
interval: 1000
running: qrCodes.length > 0
repeat: true
onTriggered: {
idx++
if (idx >= qrCodes.length) {
idx = 0
}
parent.source = qrCodes[idx]
}
}
Behavior on source {
PropertyAnimation { duration: 200 }
}
visible: qrCodes.length > 0
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 32
horizontalAlignment: Text.AlignHCenter
text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"")
}
}
}
}
}

View file

@ -11,6 +11,8 @@ Item {
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
visible: backButtonImage !== ""
RowLayout { RowLayout {
id: content id: content

View file

@ -19,11 +19,12 @@ Item {
property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImage: "qrc:/images/controls/chevron-down.svg"
property string rootButtonImageColor: "#494B50" property string rootButtonImageColor: "#494B50"
property string rootButtonDefaultColor: "#1C1D21" property string rootButtonDefaultColor: "#1C1D21"
property int rootButtonMaximumWidth property int rootButtonMaximumWidth: 0
property string rootButtonBorderColor: "#494B50" property string rootButtonBorderColor: "#494B50"
property int rootButtonBorderWidth: 1 property int rootButtonBorderWidth: 1
property real drawerHeight: 0.9
property Component listView property Component listView
property alias menuVisible: menu.visible property alias menuVisible: menu.visible
@ -55,7 +56,9 @@ Item {
Layout.leftMargin: 16 Layout.leftMargin: 16
LabelTextType { LabelTextType {
horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: root.descriptionText !== "" visible: root.descriptionText !== ""
@ -65,7 +68,9 @@ Item {
} }
ButtonTextType { ButtonTextType {
horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth
@ -115,7 +120,7 @@ Item {
id: menu id: menu
width: parent.width width: parent.width
height: parent.height * 0.9 height: parent.height * drawerHeight
ColumnLayout { ColumnLayout {
id: header id: header

View file

@ -0,0 +1,107 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "TextTypes"
ListView {
id: menuContent
property var rootWidth
property var selectedText
property string imageSource
property var clickedFunction
property bool dividerVisible: false
width: rootWidth
height: menuContent.contentItem.height
clip: true
interactive: false
ButtonGroup {
id: buttonGroup
}
delegate: Item {
implicitWidth: rootWidth
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.fill: parent
spacing: 16
RadioButton {
id: radioButton
implicitWidth: parent.width
implicitHeight: radioButtonContent.implicitHeight
hoverEnabled: true
indicator: Rectangle {
anchors.fill: parent
color: radioButton.hovered ? "#2C2D30" : "#1C1D21"
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
RowLayout {
id: radioButtonContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
z: 1
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
text: name
}
Image {
source: imageSource ? imageSource : "qrc:/images/controls/check.svg"
visible: imageSource ? true : radioButton.checked
width: 24
height: 24
Layout.rightMargin: 8
}
}
ButtonGroup.group: buttonGroup
checked: menuContent.currentIndex === index
onClicked: {
menuContent.currentIndex = index
menuContent.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
DividerType {
Layout.fillWidth: true
Layout.bottomMargin: 16
visible: dividerVisible
}
}
Component.onCompleted: {
if (menuContent.currentIndex === index) {
menuContent.selectedText = name
}
}
}
}

View file

@ -23,12 +23,6 @@ RadioButton {
property string defaultBodredColor: "transparent" property string defaultBodredColor: "transparent"
property int borderWidth: 0 property int borderWidth: 0
property string defaultCircleBorderColor: "#878B91"
property string selectedCircleBorderColor: "#A85809"
property string pressedCircleBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3)
property string defaultInnerCircleColor: "#FBB26A"
property string imageSource property string imageSource
property bool showImage property bool showImage
@ -61,80 +55,38 @@ RadioButton {
} }
Image { Image {
source: imageSource source: {
visible: showImage if (showImage) {
return imageSource
anchors.centerIn: parent } else if (root.pressed) {
return "qrc:/images/controls/radio-button-inner-circle-pressed.png"
width: 24
height: 24
}
Rectangle {
id: outerCircle
width: 24
height: 24
radius: 16
visible: !showImage
anchors.centerIn: parent
color: "transparent"
border.color: {
if (root.enabled) {
if (root.pressed) {
return pressedCircleBorderColor
} else if (root.checked) { } else if (root.checked) {
return selectedCircleBorderColor return "qrc:/images/controls/radio-button-inner-circle.png"
}
}
return defaultCircleBorderColor
} }
border.width: 1 return ""
Behavior on border.color {
PropertyAnimation { duration: 200 }
} }
Rectangle {
id: innerCircle
width: 12
height: 12
radius: 16
anchors.centerIn: parent anchors.centerIn: parent
color: "transparent" width: 24
border.color: defaultInnerCircleColor height: 24
border.width: {
if (root.enabled) {
if(root.checked) {
return 6
} }
return root.pressed ? 6 : 0 Image {
source: {
if (showImage) {
return ""
} else if (root.pressed || root.checked) {
return "qrc:/images/controls/radio-button-pressed.svg"
} else { } else {
return 0 return "qrc:/images/controls/radio-button.svg"
} }
} }
Behavior on border.width { anchors.centerIn: parent
PropertyAnimation { duration: 200 }
}
}
DropShadow { width: 24
anchors.fill: innerCircle height: 24
horizontalOffset: 0
verticalOffset: 0
radius: 12
samples: 13
color: "#FBB26A"
source: innerCircle
}
} }
} }

View file

@ -111,7 +111,7 @@ PageType {
id: menu id: menu
width: parent.width width: parent.width
height: parent.height * 0.90 height: parent.height * 0.9
ColumnLayout { ColumnLayout {
id: serversMenuHeader id: serversMenuHeader
@ -247,8 +247,8 @@ PageType {
ColumnLayout { ColumnLayout {
id: serverRadioButtonContent id: serverRadioButtonContent
anchors.fill: parent
anchors.fill: parent
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16

View file

@ -18,7 +18,64 @@ import "../Components/Protocols"
PageType { PageType {
id: root id: root
OpenVpnSettings { FlickableType {
id: fl
anchors.fill: parent anchors.fill: parent
contentHeight: content.height + openVpnSettings.implicitHeight
Column {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 16
ListView {
// todo change id naming
id: container
width: parent.width
height: container.contentItem.height
clip: true
interactive: false
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
filters: [
ValueFilter {
roleName: "isCurrentlyProcessed"
value: true
}
]
}
delegate: Item {
implicitWidth: container.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
HeaderType {
Layout.fillWidth: true
Layout.topMargin: 20
headerText: name
}
}
}
}
OpenVpnSettings {
id: openVpnSettings
width: parent.width
}
}
} }
} }

View file

@ -134,7 +134,7 @@ PageType {
text: qsTr("Continue") text: qsTr("Continue")
onClicked: function() { onClicked: function() {
ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer)
goToPage(PageEnum.PageSetupWizardInstalling); goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.install(containers.dockerContainer, InstallController.install(containers.dockerContainer,
containers.containerDefaultPort, containers.containerDefaultPort,

View file

@ -54,7 +54,7 @@ PageType {
sourceModel: ContainersModel sourceModel: ContainersModel
filters: [ filters: [
ValueFilter { ValueFilter {
roleName: "isCurrentlyInstalled" roleName: "isCurrentlyProcessed"
value: true value: true
} }
] ]

View file

@ -22,7 +22,7 @@ PageType {
sourceModel: ContainersModel sourceModel: ContainersModel
filters: [ filters: [
ValueFilter { ValueFilter {
roleName: "isCurrentlyInstalled" roleName: "isCurrentlyProcessed"
value: true value: true
} }
] ]

View file

@ -90,7 +90,7 @@ PageType {
buttonImage: "qrc:/images/controls/chevron-right.svg" buttonImage: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index))
goToPage(PageEnum.PageSetupWizardProtocolSettings) goToPage(PageEnum.PageSetupWizardProtocolSettings)
} }
} }

View file

@ -1,5 +1,330 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
Item { import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Components"
PageType {
id: root
Connections {
target: ExportController
function onGenerateConfig(isFullAccess) {
if (isFullAccess) {
ExportController.generateFullAccessConfig()
} else {
ExportController.generateConnectionConfig()
}
shareConnectionDrawer.configText = ExportController.getAmneziaCode()
shareConnectionDrawer.qrCodes = ExportController.getQrCodes()
}
}
property bool showContent: false
property list<QtObject> connectionTypesModel: [
amneziaConnectionFormat
]
QtObject {
id: amneziaConnectionFormat
property string name: qsTr("For the AmnesiaVPN app")
property var func: function() {
ExportController.generateConfig(false)
}
}
QtObject {
id: openVpnConnectionFormat
property string name: qsTr("OpenVpn native format")
property var func: function() {
console.log("Item 3 clicked")
}
}
QtObject {
id: wireGuardConnectionFormat
property string name: qsTr("WireGuard native format")
property var func: function() {
console.log("Item 3 clicked")
}
}
FlickableType {
anchors.top: root.top
anchors.bottom: root.bottom
contentHeight: content.height
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 16
anchors.leftMargin: 16
HeaderType {
Layout.fillWidth: true
Layout.topMargin: 20
headerText: qsTr("VPN Access")
}
Rectangle {
id: accessTypeSelector
property int currentIndex
Layout.topMargin: 32
implicitWidth: accessTypeSelectorContent.implicitWidth
implicitHeight: accessTypeSelectorContent.implicitHeight
color: "#1C1D21"
radius: 16
RowLayout {
id: accessTypeSelectorContent
spacing: 0
HorizontalRadioButton {
checked: accessTypeSelector.currentIndex === 0
implicitWidth: (root.width - 32) / 2
text: qsTr("Connection")
onClicked: {
accessTypeSelector.currentIndex = 0
}
}
HorizontalRadioButton {
checked: root.currentIndex === 1
implicitWidth: (root.width - 32) / 2
text: qsTr("Full")
onClicked: {
accessTypeSelector.currentIndex = 1
}
}
}
}
ParagraphTextType {
Layout.fillWidth: true
text: qsTr("VPN access without the ability to manage the server")
color: "#878B91"
}
DropDownType {
id: serverSelector
Layout.fillWidth: true
Layout.topMargin: 24
implicitHeight: 74
rootButtonBorderWidth: 0
drawerHeight: 0.4375
descriptionText: qsTr("Server and service")
headerText: qsTr("Server")
listView: ListViewType {
rootWidth: root.width
dividerVisible: true
imageSource: "qrc:/images/controls/chevron-right.svg"
model: ServersModel
currentIndex: ServersModel.getDefaultServerIndex()
clickedFunction: function() {
serverSelector.text = selectedText
ContainersModel.setCurrentlyProcessedServerIndex(currentIndex)
protocolSelector.visible = true
}
Component.onCompleted: {
serverSelector.text = selectedText
ContainersModel.setCurrentlyProcessedServerIndex(currentIndex)
}
}
DrawerType {
id: protocolSelector
width: parent.width
height: parent.height * 0.5
ColumnLayout {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
BackButtonType {
backButtonImage: "qrc:/images/controls/arrow-left.svg"
backButtonFunction: function() {
protocolSelector.visible = false
}
}
}
FlickableType {
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight
Column {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 16
Header2TextType {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
text: qsTr("Protocols and services")
wrapMode: Text.WordWrap
}
ListViewType {
rootWidth: root.width
dividerVisible: true
imageSource: "qrc:/images/controls/chevron-right.svg"
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
filters: [
ValueFilter {
roleName: "isInstalled"
value: true
}
]
}
currentIndex: 0
clickedFunction: function () {
serverSelector.text += ", " + selectedText
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
protocolSelector.visible = false
serverSelector.menuVisible = false
fillConnectionTypeModel()
}
Component.onCompleted: {
serverSelector.text += ", " + selectedText
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
fillConnectionTypeModel()
}
function fillConnectionTypeModel() {
connectionTypesModel = [amneziaConnectionFormat]
if (currentIndex === ContainerProps.containerFromString("OpenVpn")) {
connectionTypesModel.push(openVpnConnectionFormat)
} else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) {
connectionTypesModel.push(amneziaConnectionFormat)
}
}
}
}
}
}
}
DropDownType {
id: connectionTypeSelector
property int currentIndex
Layout.fillWidth: true
Layout.topMargin: 16
implicitHeight: 74
rootButtonBorderWidth: 0
drawerHeight: 0.4375
visible: accessTypeSelector.currentIndex === 0
enabled: connectionTypesModel.length > 1
descriptionText: qsTr("Connection format")
headerText: qsTr("Connection format")
listView: ListViewType {
id: connectionTypeSelectorListView
rootWidth: root.width
dividerVisible: true
imageSource: "qrc:/images/controls/chevron-right.svg"
model: connectionTypesModel
currentIndex: 0
clickedFunction: function() {
connectionTypeSelector.text = selectedText
connectionTypeSelector.currentIndex = currentIndex
connectionTypeSelector.menuVisible = false
}
Component.onCompleted: {
connectionTypeSelector.text = selectedText
connectionTypeSelector.currentIndex = currentIndex
}
}
}
ShareConnectionDrawer {
id: shareConnectionDrawer
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 32
text: qsTr("Share")
onClicked: {
if (accessTypeSelector.currentIndex === 0) {
connectionTypesModel[connectionTypeSelector.currentIndex].func()
} else {
ExportController.generateConfig(true)
}
shareConnectionDrawer.visible = true
}
}
}
}
} }

View file

@ -78,7 +78,9 @@ PageType {
TabImageButtonType { TabImageButtonType {
isSelected: tabBar.currentIndex === 1 isSelected: tabBar.currentIndex === 1
image: "qrc:/images/controls/share-2.svg" image: "qrc:/images/controls/share-2.svg"
onClicked: {} onClicked: {
tabBarStackView.goToTabBarPage(PageEnum.PageShare)
}
} }
TabImageButtonType { TabImageButtonType {
isSelected: tabBar.currentIndex === 2 isSelected: tabBar.currentIndex === 2